除了严格的输入验证之外,还应综合使用 ASP.NET 中的参数化查询、Java 中的准备语句或其他语言中的类似技术。在将 SQL 语句传递到底层数据库系统之前,每种技术都会执行所有必需的危险字符转义。
以下示例描述了 Java 中准备好的语句的使用,并说明了如何在没有用户提供的数据的情况下构建 SQL 语句,然后以无法更改 SQL 语句的结构和意图的方式使用数据进行扩充:
String sql = "select * from Users where (用户名 = ? 和密码 = ?)";
// 准备语句
prepareStmt = connection.prepareStatement(sql);
preparedStmt.setString(1, 提交的用户名);
preparedStmt.setString(2, 提交密码);
// 结果
preparedStmt.executeQuery();
请注意,准备好的陈述和类似技术并不是万能药;如果在没有绑定变量的情况下错误地使用它们,它们并不比传统构造的动态查询更安全。以下示例说明了 Java 中预准备语句的错误使用:
String sql = "select * from Users where (用户名 = '" + SubmittedUsername +
"' 和密码 = '" + SubmittedPassword + "')"; 准备语句prepareStmt = connection.prepareStatement(sql); 结果 = preparedStmt.executeQuery();
除了确保 SQL 语句的意图不会被用户提供的数据更改之外,应用程序还应该在 SQL 生成的错误消息到达最终用户之前捕获并删除它们。尽管这种保护措施可能会妨碍开发人员排除应用程序错误的能力(可以通过额外的后端日志记录轻松克服这种错误),但 SQL 错误的呈现将极大地帮助攻击者成功利用 SQL 注入漏洞。以下示例是一个过于冗长的错误消息:
com.mysql.jdbc.DBException。MySQL SyntaxErrorException:table“ sqlInjectionTest .test” doesn't exists
at com.mysql.jdbc.SQLError.createSQLException( SQLError.java:936 )
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2985)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1631)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1723)
at com.mysql.jdbc.Connection.execSQL(Connection.java:3277)
at com.mysql.jdbc.Connection.execSQL(Connection.java:3206)
at com.mysql.jdbc.Statement.executeQuery(Statement.java:1232)
at sqlInjectionBefore.main(before.java:28)
此错误消息表明应用程序正在使用 Java 编程语言和 MySQL 数据库平台,并且查询的数据库名为“sqlInjectionTest”。每条信息都可以帮助攻击者制作他们的应用程序输入,这增加了他们成功的几率。