参数绑定(预编译语句)
虽然数据库自带的过滤是个不错的实现,但是我们还是处在“用户输入被当成 SQL语句的一部分 ”这么个圈子里,其实要跳出这个圈子还有一个实现,就是参数绑定。基本上所有的主流数据库都提供这种接口。这种方法提前预编译了SQL语句的逻辑,然后对 参数预留了位置(就是“ ?1 ?2” 这种的)。这样语句在执行的时候只是按照输入的参数表来执行。
Perl环境的例子:
- $sth = $dbh->prepare("SELECT email, userid FROM members WHERE email = ?;");
- $sth->execute($email);
感谢Stefan Wagner帮我写了个java的实现
不安全版
- Statement s = connection.createStatement();
- ResultSet rs = s.executeQuery("SELECT email FROM member WHERE name = "
- + formField); // *boom*
安全版
- PreparedStatement ps = connection.prepareStatement(
- "SELECT email FROM member WHERE name = ?");
- ps.setString(1, formField);
- ResultSet rs = ps.executeQuery();
这儿$email是从用户表单中获取来的数据,并且是做为位置参数#1(即第一个问号)传递进来的,因此在任 何情况下,这个变量的内容都可以解析为SQL语句。引号、分号、反斜杠、SQL注释表示法-其中的任何一个都不会产生任何特殊的效果,这个因为它们“只是 数据”。这不会对其他东西造成破坏,因此这个应用很大程度上防止了SQL注入攻击。
如果对这个查询进行多次重用(这个查询只解析一次)的话,还可以提高性能。然而与获得大量的安全方面的好处相比,这个是微不足道的。这还可能是我们保证互联网应用安全所采取的一个重要的措施。
限制数据库权限和隔离用户
在目前这种情况下,我们观察到只有两个交互式动作不在登录用户的上下文环境中:“登录”和“给我发送密码”。web应用应该使用尽可能少权限的数据库连接:仅对members表具有查询权限,对其它表没有任何访问权限的数据库连接。
这样做的结果是:即便是“成功地”进行了SQL注入攻击,也只能取得非常有限的成功。这种情况下,我们不能做最终授权给我们的任何更新(UPDATE)请求,因此为了能够实现更新(UPDATE)请求,我们不得不寻求其他解决方法。
一旦web应用确定了通过登录表单传递认证凭证是有效的话,那么它将把这个会话切换到一个具有更多权限的数据库连接上。
对任何web应用来说,从不使用sa权限几乎是理所当然的事情。
强调一下,虽然俺们在这次演示中选择“忘记密码”这么个功能点,不是因为这个功能点本身不安全,而是普遍存在各站点的几个容易遭受攻击的功能点之一。如果你把关注点放在如何通过“忘记密码”来进行渗透的话,那你就跑偏了。
这篇文章的本意不是全面覆盖SQL注入的精髓,甚至连教学都算不上。其实只是我们花了几个小时做的一单渗透测试的工作纪录。我们也见过其他的文章讲SQL注入的技术背景,但是都只是展示了渗透结束后的成果而没有细节。
对数据库的访问采用存储过程
当一个数据库服务器支持存储过程时,请使用存储过程执行这个应用的访问行为,这样(在存储过程编写正确的情况下)就完全不需要SQL了。
通过把诸如查询、更新、删除等某个动作的规则封装成一个单独的存储过程,你就可以根据这个单独的存储过程和所执行的商务规则额对其进行测试和归档。(例如,“增加新的订单”存储过程在客户超过了信用限制的情况下可能拒绝添加这个订单。)
对简单的查询来说,这么做可能仅仅获得很少的好处,不过当这个操作变的越来越复杂(或者是在多个地方使用这个操作)的情况下,给这样操作一个单独的定义就意味着维护这个操作将更简单而且这个操作的功能会更强壮。
注意:总可以编写动态构建查询语句的存储过程:这么做并不会防止SQL注入-它只不过把准备/执行过程正确地结合在一起,或者只不过把SQL语句与提供保护的变量直接捆绑在一起。
但是那些结果需要大量的背景知识才能看的懂的,而且我觉得渗透的细节也是很有价值的。我们正常情况下是拿不到源码的,所以渗透人员的逆向黑盒渗透的能力也是有价值的。