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

Aru*_*abh 1 sql-injection jdbc prepared-statement

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

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

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

hun*_*eke 5

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

同时,该问题请求非PreparedStatement方法:简而言之,如果您不能将工作卸载到库(例如预处理语句),那么唯一的另一种方法是自己为每个"注入"项执行此操作.无论哪种方式,都必须执行检查输入的工作.唯一的问题是它在哪里完成,以及程序员的专业知识是什么产生了输入验证代码.

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

    sql = "SELECT * FROM mytable WHERE id = " + untrustedVar;
Run Code Online (Sandbox Code Playgroud)

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

SELECT * FROM mytable WHERE id = 1;
DROP mytable;
Run Code Online (Sandbox Code Playgroud)

强制性XKCD参考

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

String[] selectRowById ( int untrustedVar ) { ...
Run Code Online (Sandbox Code Playgroud)

或者,如果它是一个字符串,您可以使用正则表达式执行此操作:

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;
Run Code Online (Sandbox Code Playgroud)

但是如果您有更长的输入没有任何语法或结构保证(例如,Web表单textarea),那么您将需要执行较低级别的字符替换以逃避潜在的不良字符.每个声明.每变量.每个数据库的味道(PostgreSQL,Oracle,MySQL,SQLite等).这......是一种蠕虫病毒.

当然,好处是如果你没有使用准备好的语句,并且还没有人为你的应用程序做过其他工作以避免SQL注入攻击,那么你就无处可去了.

同时,我呼吁,督促,督促你重新考虑你的立场,"我们不能使用,因为原因准备好的语句." 更重要的是,正如戈德·汤普森在下面的评论中正确指出的那样,"无论如何,这将是一项相当多的工作,那么为什么不做正确的事情呢?"


编辑

在写完上述内容之后,我发现有些人可能会认为仅仅编写一份准备好的声明=更好的安全性.实际上,它是使用绑定参数准备的语句,提高了安全性.例如,人们可以这样写:

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

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

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();
Run Code Online (Sandbox Code Playgroud)

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

  • 好答案.重要的是要强调在任何情况下都会有相当多的工作,那么为什么不做正确的事情并完成它呢? (2认同)