哈。不幸的是,它几乎总是取决于适当的货币和管理决策,但是“它非常乏味”通常不被认为是一个有效的工程问题——它只是一个适当重构代码的借口.
同时,该问题要求使用非 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;
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();
后一个例子做了两件事。它首先创建一个已知的安全格式,然后将 那个 发送到数据库进行准备。那么,数据库返回了准备好的语句的句柄,我们绑定变量,最后执行语句。