[asp]Asp测试SQL防注入脚本

2010年10月14日星期四 | | |

近日有些客户向我咨询关于如何防御SQL注入的问题。
案例A:修改代码嘛?太'硬',需要一定的技术功底…
案例B:买附加的'应用层防火墙'产品?比较'软',但成本问题如何解决呢???
目前网上流传着通过防注入脚本来解决这方面的问题。的确,技术含量不高(因为技术含量都由产品的安全开发人员替您解决了…),而且是开源/免费的。

废话少说,先替客户测试一下。搜了一下目前比较常见的防注入脚本(本次测试主要针对最流行的ASP+ACCESS),最后锁定由neeao开发的《SQL通用防注入系统3.0(asp)》;自己'弄'了个有缺陷的脚本(--修改《E-style工作室全站程序 E.0》ASP+ACCESS脚本):
A、删除shownews.asp中以下内容(增加GET的SQL注入缺陷):
if not isInteger(request.querystring("news_id")) then
founderr=true
errmsg=errmsg+"<br>"+"<li>非法的新闻id参数。"
end if
B、替换lib/admin_body.asp的内容(增加POST的SQL注入缺陷):将
adminname=trim(replace(request("adminname"),"'",""))
adminpwd=trim(replace(request("adminpwd"),"'",""))
替换为
adminname=trim(request("adminname"))
adminpwd=trim(request("adminpwd"))

然后下载防注入脚本《SQL通用防注入系统3.0(asp)》:
(下载地址:http://www.neeao.com/blog/attachments/200504/25_215025_neeao.rar
解压缩后有四个文件,除去说明文件neeao.txt外,核心为Neeao_SqlIn.Asp的过滤引擎、用于管理的Neeao_sql_admin.asp脚本、用于存储被过滤日志/资料的数据库SqlIn.mdb。

首先看看关于GET的shownews.asp的内容:shownews.asp中屏蔽因为'非整型参数'的错误定义后直接就调用自定义函数rcount,其内容:
以下是引用片段:
function rcount()
OPENnews 
set rscount=conn.execute("select * from news")
sql="UPDATE news SET news_count = news_count + 1 where news_id="&request.querystring("news_id")
conn.execute (sql)
rscount.close
set rscount=nothing
end function
可以看到它是直接读取GET上去的news_id字段,直接利用的话:
http://127.0.0.1/shownews.asp?news_id=1%20and%201=1
http://127.0.0.1/shownews.asp?news_id=1%20and%201=2
加入SQL通用防注入系统--直接在shownews.asp中include file标签段内加入:
<!--#Include File="Neeao_SqlIn.Asp"-->

刷新后再试将载入Neeao_SqlIn.Asp内的GET过滤部分内容:
以下是引用片段:
If Request.QueryString<>"" Then
  For Each Fy_Get In Request.QueryString
    For Fy_Xh=0 To Ubound(Fy_Inf)
      If Instr(LCase(Request.QueryString(Fy_Get)),Fy_Inf(Fy_Xh))<>0 Then
        If WriteSql=True Then
          killSqlconn.Execute("insert into SqlIn(Sqlin_IP,SqlIn_Web,SqlIn_FS,SqlIn_CS,SqlIn_SJ) values('"&Request.ServerVariables("REMOTE_ADDR")&"','"&Request.ServerVariables("URL")&"','GET','"&Fy_Get&"','"&replace(Request.QueryString(Fy_Get),"'","''")&"')")
        killSqlconn.close
        Set killSqlconn = Nothing
        End If
        Response.Write "<Script Language=JavaScript>alert('SQL通用防注入系统提示你↓\n\n请不要在GET参数中包含非法字符尝试注入!\n\ nHttp://Www.wrsky.Com 系统版本:V3.0(ASP)版\n\nBy:Neeao');</Script>"
        Response.Write "非法操作!系统做了如下记录↓<br>"
        Response.Write "操作IP:"&Request.ServerVariables("REMOTE_ADDR")&"<br>"
        Response.Write "操作时间:"&Now&"<br>"
        Response.Write "操作页面:"&Request.ServerVariables("URL")&"<br>"
        Response.Write "提交方式:GET<br>"
        Response.Write "提交参数:"&Fy_Get&"<br>"
        Response.Write "提交数据:"&Request.QueryString(Fy_Get)
        Response.End
      End If
    Next
  Next
End If

以上脚本的工作模式是首先确认被加载的脚本目前是否有接收GET数据,若'有'则将其接收字段的值代入变量Fy_Get,然后设定一个循环,初始计数器变量 Fy_Xh为0,终点计数器变量为过滤数组Fy_Inf的长度。最后是辨别/过滤,首先用LCase函数将GET数据小写化,然后利用Instr函数搜索其内容是否包含Fy_Inf数组的元素,若找得到(Instr的返回值不等于0)则首先写入过滤数据库SqlIn.mdb并在页面中输出提示/警告信息。

另外,究竟写入'防SQL注入脚本'维护的数据库有什么用呢?在过滤引擎Neeao_SqlIn.Asp的最后一段定义了若浏览任何加载引擎脚本的页面时会检索数据内是否存在存在攻击者的IP地址,若'存在'则直接输出警告信息:
以下是引用片段:
  Dim Sqlin_IP,rsKill_IP,Kill_IPsql
  Sqlin_IP=Request.ServerVariables("REMOTE_ADDR")
  Kill_IPsql="select Sqlin_IP from SqlIn where Sqlin_IP='"&Sqlin_IP&"' and kill_ip=true"
  Set rsKill_IP=killSqlconn.execute(Kill_IPsql)
  If Not(rsKill_IP.eof or rsKill_IP.bof) Then
    Response.write "<Script Language=JavaScript>alert('SQL通用防注入系统提示你↓\n\n你的Ip已经被本系统自动锁定!\n\n如想访问本站请和管理员联系!\n\nHttp://Www.wrsky.Com \n\n系统版本:V3.0(ASP)\n\nBy:Neeao');</Script>"
  Response.End
  End If
  rsKill_IP.close

如果您发现再登陆不了了,就是已经进'黑名单'了,赶紧加/换个PROXY浏览吧。

再看看POST的怎么样。首先确认一下缺陷,浏览:
http://127.0.0.1/admin.asp
输入帐号、密码均为:
' or ''='
直接登陆没问题(确认缺陷存在)。开始测试时我把:
<!--#Include File="Neeao_SqlIn.Asp"-->
放入admin.asp的最后,结果报警信息照出、黑名单照进,但却等直接浏览注入后登陆的页面。究竟怎么回事???后来看了一下,其实是和脚本的构成有关,看看修改后的admin.asp的内容:


以下是引用片段:
<!--#include file="config.asp"-->
<!--#include file="conn.asp"-->
<!--#include file="error.asp"-->
<!--#include file="lib/nav.asp"-->
<!--#include file="lib/incjs.asp"-->
<!--#include file="lib/adminmenu.asp"-->
<!--#include file="lib/admin_body.asp"-->
<!--#include file="lib/adminfoot.asp"-->
<title><%=webname%>-管理登录</title>
<%
dim founderr,errmsg
founderr=false
errmsg=""
call admin_nav()
call admin_body()
call adminendpage()
%>


<!--#Include File="Neeao_SqlIn.Asp"-->
分析一下登陆流程,首先在admin.asp中登陆部分是嵌套在lib/admin_body.asp中,登陆的部分为:
以下是引用片段:
<form name="form1" method="post" action="admin.asp?action=adminlogin">
<tr> 
<td height="24" align="center" background="images/bg3.gif" class="diaryhead"> 
<font color="#333333">管理员登陆(如果您不是管理员请勿登录)</font></td>
</tr>
<tr> 
<td bgcolor="#F5F5F5" class="chinese" align="center">用户名 
<input type="text" name="adminname" class="textarea" size="20">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;密码 
<input type="password" name="adminpwd" class="textarea" size="20">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
<input type="submit" name="Submit" value="登录" class="button">
</td>
</tr>
</form>

一旦把数据POST到admin.asp时,admin.asp将执行lib/admin_body.asp中的admin_body()函数作认证查询,因此一个标准的绕过GET认证(输入' or ''=')为:
http://127.0.0.1/admin.asp?action=adminlogin&adminname=%27+or+%27%27%3D%27&adminpwd=%27+or+%27%27%3D%27&Submit=%B5%C7%C2%BC
这里adminname与adminpwd字段都肯定有Fy_Inf中过滤的内容,通过警告提示我们可以确认引擎脚本Neeao_SqlIn.Asp是处于工作状态的,但因为它在:
以下是引用片段:
<%
dim founderr,errmsg
founderr=false
errmsg=""
call admin_nav()
call admin_body()
call adminendpage()
%>
之下,所以饶过认证后的页面还是输出了(Neeao_SqlIn.Asp中警告信息的Respone.End无法局限输出),因此我们必须把<!- -#Include File="Neeao_SqlIn.Asp"-->放在它的上面才有效。当然放在lib/admin_body.asp中也是有效的。

最后说说几点需要注意的问题:
1)  默认情况下有没可能通过GOOGLE HACKING找到Neeao_sql_admin.asp页面的地址,暴破登陆管理平台的风险呢?总体上来说风险不大,最多是解除IP锁定与删除日志而已,不会威胁业务连续性。
2)  默认的SqlIn.mdb数据库呢?怕被下载?风险也不大,但有些朋友会把它的缀后改为ASP等动态脚本后缀用于防下载,感觉上这样的风险会更大,因为曾经有人用一句话木马插入这个数据库,从而引发新的风险。
3)  最后是<!--#Include File="Neeao_SqlIn.Asp"-->的放置位置,刚刚在admin.asp解决POST类型的SQL注入中例子就出现过这样的问题,如果您是怕麻烦的人,最好还是像开发者所说的那样,在conn.asp中添加。

0 评论:


所有文章收集于网络,如果有牵扯到版权问题请与本站站长联系。谢谢合作![email protected]