实例讲解黑客如何执行SQL注入攻击(5)

在实践中,无论如何这个方法都有诸多限制,因为能够彻底排除所有危险字符的字段实在太少了。对于“日期”或者“email地址”或者“整型”,上面的办法是有价值的,但对于真实的环境,我们不可避免地要使用其他方式来减轻危害。

输入项编码/转义

现在可以过滤电话号码和邮件地址了,但你不能通过同样的方法处理“name”字段,要不然可能会排除掉Bill O’Reilly这样的名字:对于这个字段,这里的引号是合法的输入。

有人就想到过滤到单引号的时候,再加上一个引号,这样就没问题了 – 但是这么干要出事啊!

预处理每个字符串来替换单引号:

SELECT fieldlist FROM customers WHERE name = 'Bill O''Reilly'; -- works OK

这个方法很容易出问题,因为大部分数据库都支持转码机制。像MySQL,允许输入’来替代单引号,因此如果输入 ‘; DROP TABLE users; 时,通过两次引号来“保护”数据库,那我们将得到:

SELECT fieldlist FROM customers WHERE name = '\''; DROP TABLE users; --'; -- Boom!

‘’’ 是一个完整的SQL语句(只包含一个引号),通常,恶意SQL代码就会紧跟其后。不光是反斜线符号的情况:像Unicode编码,其他的编码或者解析规则都会无意中给程序员挖坑。完美的过滤的很困难的,这就是为什么许多的数据库借口语言都提供函数给你使用。当同样的内容给“string quoting”和“string parsing”处理过后,会好一些,也更安全一些。

比如MySQL的函数mysql_real_escape_string()和perl DBD 的 $dbh->quote($value)方法,这些方法都是必用的。

参数绑定 (预编译语句)

尽管转义是一个有用的机制,但我们任然处于“用户输入被当做SQL语句”这么一个循环里。更好的方法是:预编译,本质上所有的数据库编程接口都支持预编译。技术上来说,SQL声明语句是用问号给每个参数占位创建的 – 然后在内部表中进行编译。

预编译查询执行时是按照参数列表来的:

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应用安全最重要的一步。

限制数据库权限和隔离用户

在这个案例中,我们观察到只有两个交互动作不在登录用户的上下文环境中:“登录”和“发密码给我”。web应用应该对数据库连接做权限的限制:对于members表只能读,并且无法操作其他表。

作用是即使一次“成功的”SQL注入攻击也只能得到非常有限的成功。噢,我们将不能做有授权的UPDATE请求,我们要求助于其他方法。

一旦web应用确定登录表单传递来的认证是有效的,它就会切换会话到一个有更多权限的用户上。

对任何web应用而言,不使用sa权限几乎是根本不用说的事。

对数据库的访问采用存储过程

如果数据库支持存储过程,请使用存储过程来执行数据库的访问行为,这样就不需要SQL了(假设存储过程编程正确)。

把查询,更新,删除等动作规则封装成一个单独的过程,就可以针对基础规则和所执行的商业规则来完成测试和归档(例如,如果客户超过了信用卡限额,“添加新记录”过程可能拒绝订单)。

对于简单的查询这样做可能仅仅能获得很少的好处,不过一旦操作变复杂(或者被用在更多地方),给操作一个单独的定义,功能将会变得更稳健也更容易维护。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/0442b404d36558d8832a8eaaca5d8e63.html