在不使用 Prepared 语句的情况下摆脱 SQL 注入

Getting rid of SQL injection without using Prepared statement

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

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

有没有更聪明的方法来避免 SQL 注入而不使用 Prepared 语句。如果这是一个重复的问题,请给我 link 来提问哪个有好的答案。感谢您的帮助。

哈。不幸的是,它几乎总是取决于货币和管理决定什么是合适的,但 "it is very tedious" 通常不被认为是有效的 工程 关注——它只是适当重构的借口代码。

同时,该问题要求使用非 PreparedStatement 方法:简而言之,如果您不能将工作卸载到库(例如准备好的语句),那么唯一的其他方法就是为每个 "injected"物品。无论哪种方式,都必须执行检查输入的工作。唯一的问题是它在哪里完成,以及程序员的专业知识制作了输入验证代码。

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

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

为了完整性,我们可以假设注入示例,其中 untrustedVar 是一个字符串,如 1 OR 11; DROP TABLE mytable;返回给调用者,或者现在丢失的数据库 table:

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 注入攻击,那么您无处可去,但向上。

与此同时,我敦促,敦促,敦促你重新考虑你对"We can't use prepared statements because reasons."的立场更重要的是,正如戈德汤普森在下面的评论中正确指出的那样, "it will be a considerable amount of work in any case, so why not just do it right and be done with it?"


编辑

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

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();

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