高级SQL注入
通常情况下,一个web应用程序将会过滤单引号(或其他符号),或者限定用户提交的数据的长度。
在这部分,我们讨论一些能帮助攻击者饶过那些明显防范SQL注入,躲避被记录的技术。
[没有单引号的字符串]
有时候开发人员会通过过滤所有的单引号来保护应用程序,他们可能使用VBScript中的replace函数或类似:
function escape(input) input=replace(input,"'","''") escape=input end function |
无可否认地这防止了我们所有例子的攻击,再除去';'符号也可以帮很多忙。但是在一个大型的应用程序中,好象个别值期望用户输入的是数字。这些值没有被限定,因此为攻击者提供了一个SQL注入的弱点。
如果攻击者想不使用单引号产生一个字符串值,他可以使用char函数,例如:
insert into users values(666, char(0x63)+char(0x68)+char(0x72)+char90x69)+char(0x73), char(0x63)+char(0x68)+char(0x72)+char90x69)+char(0x73), 0xffff) |
这就是一个能够往表中插入字符串的不包含单引号的查询。
淡然,如果攻击者不介意使用一个数字用户名和密码,下面的语句也同样会起作用:
insert into users values(667, 123, 123, oxffff) |
SQL SERVER自动地将整型转化为varchar型的值。
[Second-Order SQL Injection]
即使应用程序总是过滤单引号,攻击者依然能够注入SQL同样通过应用程序使数据库中的数据重复使用。
例如,攻击者可能利用下面的信息在应用程序中注册:
Username:admin'— Password:password |
应用程序正确过滤了单引号,返回了一个类似这样的insert语句:
insert into users values(123,'admin''—','password',0xffff) |
我们假设应用程序允许用户修改自己的密码。这个ASP脚本程序首先保证用户设置新密码前拥有正确的旧密码。代码如下:
username = escape( Request.form("username") ); oldpassword = escape( Request.form("oldpassword") ); newpassword = escape( Request.form("newpassword") ); var rso = Server.CreateObject("ADODB.Recordset"); var sql = "select * from users where username = '" + username + "' and password = '" + oldpassword + "'"; rso.open( sql, cn ); if (rso.EOF) { … |
设置新密码的代码如下:
sql = "update users set password = '" + newpassword + "' where username = '" + rso("username") + "'" rso("username")为登陆查询中返回的用户名 |
当username为admin'—时,查询语句为:
update users set password = 'password' where username='admin'—' |
这样攻击者可以通过注册一个admin'—的用户来根据自己的想法来设置admin的密码。
这是一个非常严重的问题,目前在大型的应用程序中试图去过滤数据。最好的解决方法是拒绝非法输入,这胜于简单地努力去修改它。这有时会导致一个问题,非法的字符在那里是必要的,例如在用户名中包含'符号,例如:O'Brien
从一个安全的观点来看,最好的解答是但引号不允许存在是一个简单的事实。如果这是无法接受的话,他们仍然要被过滤;在这种情况下,保证所有进入SQL查询的数据都是正确的是最好的方法。
如果攻击者不使用任何应用程序莫名其妙地往系统中插入数据,这种方式的攻击也是可能的。应用程序可能有email接口,或者可能在数据库中可以存储错误日志,这样攻击者可以努力控制它。验证所有数据,包括数据库中已经存在的数据始终是个好的方法。确认函数将被简单地调用,例如:
if(not isValid("email",request.querystring("email"))) then response.end |
或者类似的方法。
[长度限制]
为了给攻击者更多的困难,有时输入数据的长度是被限制的。当这个阻碍了攻击时,一个小的SQL可以造成很严重的危害。例如:
这样只用12个输入字符就将停止SQL SERVER实例。另一个例子是:
如果限定长度是在过滤字符串后应用将会引发另一个问题。假设用户名被限定16个字符,密码也被限定16个字符,那么下面的用户名和密码结合将会执行上面提到的shutdown命令:
Username:aaaaaaaaaaaaaaa' Password:'; shutdown— |
原因是应用程序尝试去过滤用户名最后的单引号,但是字符串被切断成16个字符,删除了过滤后的一个单引号。这样的结果就是如果密码字段以单引号开始,它可以包含一些SQL语句。既然这样查询看上去是:
select * from users where username='aaaaaaaaaaaaaaa'' and password=''';shutdown— |
实际上,查询中的用户名已经变为:
aaaaaaaaaaaaaaa' and password=' |
因此最后的SQL语句会被执行。
[审计]
SQL SERVER包含了丰富的允许记录数据库中的各种事件的审计接口,它包含在sp_traceXXX类的函数中。特别有意思的是能够记录所有SQL语句,然后在服务器上执行的T-SQL的事件。如果这种审计是被激活的,我们讨论的所有注入的SQL查询都将被记录在数据库中,一个熟练的数据库管理员将能够知道发生了什么事。不幸地,如果攻击者追加以下字符串:Sp_password
到一个Transact-SQL语句中,这个审计机制记录日志如下:
--'sp_password' was found in the text of this event. -- The text has been replaced with this comment for security reasons. |
这种行为发生在所有的T-SQL日记记录中,即使'sp_password'发生在一个注释中。这个过程打算通过sp_password隐藏用户的密码,但这对于一个攻击者来说是非常有用的方法。
因此,为了隐藏所有注入,攻击者需要简单地在'—'注释字符后追加sp_password,例如:Username:admin'—sp_password
事实上一些被执行的SQL将被记录,但是查询本身将顺利地从日志中消失。
[防范]
这部分讨论针对记述的攻击的一些防范。我们将讨论输入确认和提供一些简单的代码,然后我们将从事SQL SERVER锁定。
[输入验证]
输入验证是一个复杂的题目。比较有代表性的是,自从过于严密地确认倾向于引起部分应用程序的暂停,输入确认问题很难被解决,在项目开发中投入很少的注意力在输入确认上。输入确认不是倾向于将它加入到应用程序的功能当中,因此它一般会被忽视。
下面是一个含有简单代码的讨论输入确认的大纲。这个简单的代码不能直接用于应用程序中,但是它十分清晰地阐明了不同的策略。
不同的数据确认方法可以按以下分类:
1) 努力修改数据使它成为正确的
2) 拒绝被认为是错误的输入
3) 只接收被认为是正确的输入
第一种情况有一些概念上的问题;首先,开发人员没必要知道那些是错误数据,因为新的错误数据的形式始终被发现。其次,修改数据会引起上面描述过的数据的长度问题。最后,二次使用的问题包括系统中已经存在数据的重新使用。
第二种情况也存在第一种情况中的问题;已知的错误输入随着攻击技术的发展变化。
第三种情况可能是三种中最好的,但是很难实现。
从安全角度看合并第二种方法和第三种方法可能是最好的方法——只允许正确的输入,然后搜索输入中已知的错误数据。
带有连接符号的姓名的问题对于体现合并两种方法的必要性是一个好的例子:
Quentin Bassington-Bassington
我们必须在正确输入中允许连接符号,但是我们也意识到字符序列'—'对SQL SERVER很重要。
当合并修改数据和字符序列确认时,会出现另一个问题。例如,如果我们应用一个错误过滤在除去单引号之后去探测'—','select'和'union',攻击者可以输入:
uni'on sel'ect @@version-'-
既然单引号被除去,攻击者可以简单地散布单引号在自己的错误的字符串中躲避被发现。
这有一些确认代码的例子:
方法一——过滤单引号
function escape(input) input=replace(input,"'","''") escape=input end function |
方法二——拒绝已知的错误输入
function validate_string(input) known_bad=array("select","insert","update","delete","drop","—","'") validate_string=true for i=lbound(known_bad) to ubound(known_bad) if(instr(1,input,known_bad(i),vbtextcompare)<>0) then validate_string=false exit function end if next end function |
方法三——只允许正确的输入
function validatepassword(input) good_password_chars=” abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789” validatepassword=true for i=1 to len(input) c=mid(input,I,1) if(InStr(good_password_chars,c)=0) then validatepassword=false exit function end if next end function |
[SQL SERVER锁定]
在这指出的重要一点是锁定SQL SERVER是必要的;外面的是不安全的。这是一个但创建SQL SERVER时需要做的事情的简短的列表:
1.确定连接服务器的方法
a.确定你所使用的网络库是可用的,那么使用"Network Utility"
2.确定哪些帐户是存在的
a.为应用程序的使用创建一个低权限的帐户
b.删除不必要的帐户
c.确定所有帐户有强壮的密码;执行密码审计
3.确定哪些对象存在
a.许多扩展存储过程能被安全地移除。如果这样做了,应该移除包含在扩展存储过程代码中的'.dll'文件
b.移除所有示例数据库——例如'northwind'和'pubs'数据库
4.确定哪写帐户能过使用哪些对象
a.应用程序进入数据库所使用的帐户应该有保证能够使用它需要的对象的最小权限
5.确定服务器的补丁
a.针对SQL SERVER有一些缓冲区溢出和格式化字符串攻击,也有一些其他的安全补丁发布。应该存在很多。
6.确定什么应该被日志记录,什么应该在日志中结束。
查看本文来源