【问题标题】:Getting rid of SQL injection without using Prepared statement在不使用 Prepared 语句的情况下摆脱 SQL 注入
【发布时间】:2017-11-13 19:57:49
【问题描述】:

我的应用程序有很多使用 Statement 编写的 JDBC 查询,因此容易受到 SQL 注入的攻击。不幸的是,这个应用程序是在 10 年前开发的,可能开发人员不知道 Prepared statement。

我知道解决此问题的最佳方法是使用 PreparedStatement,但在整个应用程序中转换它非常繁琐。此外,为 SQL 注入编写任何类型的 Patten 匹配都可能非常棘手,因为 Select、insert、union 等关键字都是英文单词,也可能出现在用户键入的文本字段中。

有没有更聪明的方法可以在不使用 Prepared 语句的情况下避免 SQL 注入。如果这是一个重复的问题,请给我一个有很好答案的问题的链接。感谢您的帮助。

【问题讨论】:

标签: jdbc prepared-statement sql-injection


【解决方案1】:

哈。不幸的是,它几乎总是取决于适当的货币和管理决策,但是“它非常乏味”通常不被认为是一个有效的工程问题——它只是一个适当重构代码的借口.

同时,该问题要求使用非 PreparedStatement 方法:简而言之,如果您无法将工作卸载到库(例如准备好的语句),那么唯一的另一种方法是自己为每个“注入”项目执行此操作。无论哪种方式,都必须执行检查输入的工作。唯一的问题是它是在哪里完成的,以及输入验证代码的程序员的专业知识。

例如,考虑一个简单的 SELECT 语句:

    sql = "SELECT * FROM mytable WHERE id = " + untrustedVar;

为了完整起见,我们可以假设untrustedVar1 OR 11; DROP TABLE mytable; 之类的字符串的注入示例,显然这将导致不需要的行为,相对于所有返回给调用者的行,或者现在缺少数据库表:

SELECT * FROM mytable WHERE id = 1;
DROP mytable;

Obligatory XKCD reference

在这种情况下,您可以让语言语义至少确保 unstrustedVar 是一个整数,也许在您的函数定义中:

String[] selectRowById ( int untrustedVar ) { ...

或者如果它是一个字符串,你可以使用如下的正则表达式:

Pattern valid_id_re = Pattern.compile('^\d{1,10}$');  // ensure id is between 1 and 10 billion
Matcher m = valid_id_re.matcher( unstrustedVar );
if ( ! m.matches() )
    return null;

但是,如果您有较长的输入,但没有任何语法或结构保证(例如,Web 表单文本区域),那么您将需要进行较低级别的字符替换以转义潜在的错误字符。根据声明。每个变量。每个数据库风格(PostgreSQL、Oracle、MySQL、SQLite 等)。这……是一罐虫子。

当然,好处是,如果您没有使用准备好的语句,并且没有人完成工作以避免对您的应用程序进行 SQL 注入攻击,那么您别无选择。

同时,我敦促,敦促,敦促您重新考虑您对“我们不能使用准备好的陈述,因为原因”的立场。更重要的是,正如 Gord Thompson 在下面的 cmets 中正确指出的那样,“无论如何,这将是一项相当大的工作,所以为什么不把它做好并做好呢?”


编辑

写完上面的内容后,我突然想到,有些人可能会认为仅仅写一个准备好的语句 = 更好的安全性。实际上,是准备好的语句带有绑定参数 提高了安全性。例如,可以这样写:

String sql = "SELECT * FROM mytable WHERE id = " + untrustedVar;
PreparedStatment pstmt = dbh.prepareStatement( sql );
return pstmt.executeQuery();

此时,您所做的只是准备了一个已注入恶意代码的语句。相反,考虑参数的实际绑定:

String sql = "SELECT * FROM mytable WHERE id = ?";  // Raw string; never touched by "tainted" variable
PreparedStatment pstmt = dbh.prepareStatement( sql );
pstmt.setObject(1, p);  // Perform the actual binding.
return pstmt.executeQuery();

后一个例子做了两件事。它首先创建一个已知的安全格式,然后将 那个 发送到数据库进行准备。那么,数据库返回了准备好的语句的句柄,我们绑定变量,最后执行语句。

【讨论】:

  • 好答案。重要的是要强调无论如何这将是大量的工作,那么为什么不把它做好并做好呢?
  • 感谢大家的回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-22
  • 1970-01-01
  • 2013-09-08
  • 1970-01-01
  • 2016-11-05
  • 2018-03-21
  • 2018-09-08
相关资源
最近更新 更多