我的应用程序有很多使用Statement编写的JDBC查询,因此容易受到SQL注入的攻击。不幸的是,这个应用程序是在大约10年前开发的,可能开发人员不知道Prepared声明。
我知道解决这个问题的最好方法是使用PreparedStatement,但在整个应用程序中转换它是非常繁琐的。此外,为SQL注入编写任何类型的Patten匹配可能非常棘手,因为Select,insert,union等关键字都是英文单词,也可能出现在用户输入的文本字段中。
如果不使用Prepared语句,是否有更智能的方法来避免SQL注入。如果这是一个重复的问题,请给我一个问题的链接,这个问题有一个很好的答案。谢谢你的帮助。
答案 0 :(得分:5)
哈。不幸的是,它几乎总是取决于适当的货币和管理决策,但“它非常乏味”通常不被视为有效的工程关注 - 它只是适当重构代码的借口
同时,该问题请求非PreparedStatement方法:简而言之,如果您不能将工作卸载到库(例如预处理语句),那么唯一的另一种方法是自己为每个“注入”项执行此操作。无论哪种方式,都必须执行检查输入的工作。唯一的问题是它在哪里完成,以及程序员的专业知识是什么产生了输入验证码。
例如,考虑一个简单的SELECT语句:
sql = "SELECT * FROM mytable WHERE id = " + untrustedVar;
为了完整起见,我们可以假设注入示例untrustedVar
是一个字符串,如1 OR 1
或1; DROP TABLE mytable;
显然这会导致不必要的行为,相对于返回的所有行调用者,或者现在缺少的数据库表:
SELECT * FROM mytable WHERE id = 1;
DROP mytable;
在这种情况下,您可以让语言语义至少确保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表单textarea),那么您将需要执行较低级别的字符替换以逃避潜在的不良字符。每个声明。每变量。每个数据库的味道(PostgreSQL,Oracle,MySQL,SQLite等)。这......是一堆蠕虫。
当然,好处是,如果你没有使用准备好的语句,并且还没有人完成这项工作以避免你的应用程序出现SQL注入攻击,那么你就无处可去了。
与此同时,我敦促,敦促,敦促你重新考虑你的立场“我们不能因为理由而使用准备好的陈述”。更重要的是,正如戈德·汤普森在下面的评论中正确指出的那样,“在任何情况下都会有相当多的工作,所以为什么不做正确的事情并完成它呢?”
在写完上述内容之后,我发现有些人可能会认为仅仅编写一份准备好的声明=更好的安全性。实际上,它准备了带有绑定参数的语句,提升了安全性。例如,人们可以这样写:
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();
后一个例子做了两件事。它首先创建一个已知的安全格式,并将那个发送到数据库进行准备。然后,在之后,DB返回了预处理语句的句柄,我们是否绑定变量,最后执行语句。