例题1.——bugku日志审计

sql server 语句审计 sql审计例题_安全


本题要点:sql盲注、python脚本编写、python正则表达式

先下载压缩包,解压发现里面是一个 access.log 日志文件。

sql server 语句审计 sql审计例题_sql server 语句审计_02


全选后在网上URL解码:

sql server 语句审计 sql审计例题_mysql_03


复制回notepad++中

sql server 语句审计 sql审计例题_sql server 语句审计_04


这样看起来就方便多了。(或者全选,插件—>MIME Tools—>URL Decode)

sql server 语句审计 sql审计例题_sql server 语句审计_05


接下来开始分析。

1.测试有没有注入漏洞

sql server 语句审计 sql审计例题_mysql_06


这理就是开始测试有没有sql注入漏洞的过程。下图这个就类似 and 1=1,页面返回正常的时候:

sql server 语句审计 sql审计例题_安全_07


下图这个就类似 and 1=2,页面返回错误或空白页等情况:

sql server 语句审计 sql审计例题_HTTP_08

2.对数据库表名、字段名、查询数据、用户名猜解等操作

这里我们可以看到这是在用 二分法 在进行 sql盲注。

sql server 语句审计 sql审计例题_数据库_09


SQL ORD()函数返回字符串第一个字符的 ASCII 值。

SQL MID() 函数用于得到一个字符串的一部分。

SQL MID() 函数语法为:SELECT MID(ColumnName, Start [, Length]) FROM TableName SQL IFNULL(expression, alt_value)函数用于判断第一个表达式是否为 NULL,如果为 NULL 则返回第二个参数的值,如果不为 NULL 则返回第一个参数的值。

SQL CAST()函数用于将某种数据类型的表达式显式转换为另一种数据类型。

语法:

CAST (expression AS data_type)

根据返回的状态码为200的分析可得数据库名个字符的ASCII码为:
有这两行

/?id=2' AND ORD(MID((IFNULL(CAST(DATABASE() AS CHAR),0x20)),1,1))>100 AND 'ZzhM'='ZzhM&Submit=Submit HTTP/1.1" 404
/?id=2' AND ORD(MID((IFNULL(CAST(DATABASE() AS CHAR),0x20)),1,1))>99 AND 'ZzhM'='ZzhM&Submit=Submit HTTP/1.1" 200

可知数据库名的第一个字符的ASCII码大于99,但不再大于100了,说明第一个字符的ASCII码就是100
根据这个原理就可以分析得的数据库名。

分析表名(可以看到开始先猜的表的数量):

sql server 语句审计 sql审计例题_mysql_10


分析字段名:

sql server 语句审计 sql审计例题_mysql_11


可以看到每一条语句返回的状态码有 200 或 404 。

如我们可以看到 200状态码对应的ASCII值是1765 , 404状态码对应的ASCII值是5476 。

这一步有利于后面编写解码脚本。

sql server 语句审计 sql审计例题_sql server 语句审计_12


我们往后继续看~

看到有 flag_is_here 的表…

sql server 语句审计 sql审计例题_HTTP_13


如何判断flag的第一个字符呢?

在sql盲注里测试的字符的 最后一条状态码为 200 的语句 的ASCII值再加1就是猜解正确的ASCII值,转换成字符就是我们需要的答案的其中一个字符。

下面以第一个字符作为例子:

sql server 语句审计 sql审计例题_数据库_14

…… flag_is_here ORDER BY flag LIMIT 0,1),1,1))>64 AND 'RCKM'='RCKM&Submit=Submit HTTP/1.1" 200 1765
…… flag_is_here ORDER BY flag LIMIT 0,1),1,1))>96 AND 'RCKM'='RCKM&Submit=Submit HTTP/1.1" 200 1765
…… flag_is_here ORDER BY flag LIMIT 0,1),1,1))>112 AND 'RCKM'='RCKM&Submit=Submit HTTP/1.1" 404 5476
…… flag_is_here ORDER BY flag LIMIT 0,1),1,1))>104 AND 'RCKM'='RCKM&Submit=Submit HTTP/1.1" 404 5476
…… flag_is_here ORDER BY flag LIMIT 0,1),1,1))>100 AND 'RCKM'='RCKM&Submit=Submit HTTP/1.1" 200 1765
…… flag_is_here ORDER BY flag LIMIT 0,1),1,1))>102 AND 'RCKM'='RCKM&Submit=Submit HTTP/1.1" 404 5476
…… flag_is_here ORDER BY flag LIMIT 0,1),1,1))>101 AND 'RCKM'='RCKM&Submit=Submit HTTP/1.1" 200 1765

我们可以看到当 LIMIT 0,1),1,1))>101 时,是第一个字符的最后一个 状态码为 200 的语句,这时的ASCII码是 101 ,那么 101+1=102 ,ASCII码为 102 对应的是 f ,因此flag的第一个字符就为 f 。

下面多看几个例子,红框中就是每个字符的最后一个状态码为 200 的语句。

sql server 语句审计 sql审计例题_数据库_15


101+1=102 -->f

107+1=108 -->l

96+1=97 -->a

102+1=103 -->g


由于字符比较多,我们就不一一列举了,以上就是flag的前四个字符。这样我们通过MID()函数中的数字便能知道当前猜测的是flag的第几个字符,并根据上述原理即可猜得flag的各位字符。

从下图看,这个flag就25个字符。

sql server 语句审计 sql审计例题_数据库_16


( 如果写不出脚本,也可以用手工的方法,一个一个的记下 flag 每一个 字符的 ascii 值,然后+1,然后再对应ASCII表)

下面是参考了一个大佬写的脚本:

# coding:utf-8
import re
import urllib
 
f = open('D:/access.log','r')  # 下载的access.log文件的绝对路径,笔者是存在了d盘根目录下~
lines = f.readlines()
datas = []
for line in lines:
    t = urllib.unquote(line)     # 就是将文本进行 urldecode 解码
    if '1765' in t and 'flag' in t:  # 过滤出与flag相关,正确的猜解(只要200的)
        datas.append(t)
 
flag_ascii = {}  
for data in datas:
    matchObj = re.search( r'LIMIT 0,1\),(.*?),1\)\)>(.*?) AND', data)   # 在date 中搜索符合正则表达的字符串并 将匹配的字符串存入变量 matchObj 中
    if matchObj:
        key = int(matchObj.group(1))  # 取变量matchObj 中 的第一个括号里的内容 (也就是上条语句中的 (.*?)中的内容)并转为10进制
        value = int(matchObj.group(2))+1  # 取变量matchObj中的第二个括号里的内容,并转为 10 进制
        flag_ascii[key] = value     # 使用字典,保存最后一次猜解正确的ascii码(这里经过几次循环不断更新后后最终保存的就是该字符最后一次猜解正确的ascii码)
        
flag = ''
for value in flag_ascii.values():
    flag += chr(value)
    
print flag

注意:以上脚本需要在python2的环境下运行

sql server 语句审计 sql审计例题_sql server 语句审计_17

例题2.——access.log(基于时间的盲注)

同理用notepad++打开先解码(全选,插件—>MIME Tools—>URL Decode)

测试有无sql注入漏洞(类似于1 and 1=1和and 1=2):

sql server 语句审计 sql审计例题_数据库_18

在表中,一个列可能会包含多个重复值,有时您也许希望仅仅列出不同(distinct)的值。 DISTINCT 用于返回唯一不同的值。

下图为猜数据库个数(可知利用了基于时间的盲注):

sql server 语句审计 sql审计例题_安全_19

/?id=1' aNd (seLECT 6943 FroM (seLECT(SLeEP(5-(If(OrD(mID((seLECT IfNULL(CaSt(cOunT(DiSTiNCt(SchEma_NamE)) As Nchar),0x20) FroM INFORMATION_SCHEMA.SCHEMATA),1,1))>51,0,5)))))pwXA) aNd 'YJEB'='YJEB HTTP/1.1" 200 729 
/?id=1' ANd (seLECt 6943 FRoM (seLECt(sLeep(5-(If(orD(Mid((seLECt IfNULL(caSt(cOunt(DISTiNct(SChEmA_Name)) aS nchAr),0x20) FRoM INFORMATION_SCHEMA.SCHEMATA),1,1))>48,0,5)))))pwXA) ANd 'YJEB'='YJEB HTTP/1.1" 200 729 
/?id=1' And (SElect 6943 fROm (SElect(slEep(5-(iF(orD(Mid((SElect iFNULL(CAsT(COuNT(dIstinCT(sChEMA_nAme)) As nCHaR),0x20) fROm INFORMATION_SCHEMA.SCHEMATA),1,1))>49,0,5)))))pwXA) And 'YJEB'='YJEB HTTP/1.1" 200 729 
/?id=1' ANd (SEleCT 6943 FroM (SEleCT(SLEeP(5-(If(oRd(mId((SEleCT IfNULL(CaST(cOunT(dIstINCt(sCheMa_NAMe)) aS nChaR),0x20) FroM INFORMATION_SCHEMA.SCHEMATA),1,1))>50,0,5)))))pwXA) ANd 'YJEB'='YJEB HTTP/1.1" 200 729 
/?id=1' And (seLECT 6943 FrOm (seLECT(SlEEP(5-(If(OrD(MId((seLECT IfNULL(CaST(COunT(DISTInCt(SChEMa_NAME)) As ncHaR),0x20) FrOm INFORMATION_SCHEMA.SCHEMATA),1,1))!=50,0,5)))))pwXA) And 'YJEB'='YJEB HTTP/1.1" 200 729 
/?id=1' aNd (sElecT 6943 FRoM (sElecT(sLEeP(5-(If(Ord(mId((sElecT IfNULL(CaSt(Count(DisTiNCt(SChema_naMe)) As nchAR),0x20) FRoM INFORMATION_SCHEMA.SCHEMATA),2,1))>51,0,5)))))pwXA) aNd 'YJEB'='YJEB HTTP/1.1" 200 729 
/?id=1' AnD (SELecT 6943 From (SELecT(sleeP(5-(iF(oRD(Mid((SELecT iFNULL(CAst(CoUNt(dISTINcT(ScheMa_NAmE)) As NcHAR),0x20) From INFORMATION_SCHEMA.SCHEMATA),2,1))>48,0,5)))))pwXA) AnD 'YJEB'='YJEB HTTP/1.1" 200 729 
/?id=1' aND (sELect 6943 FrOm (sELect(sLeEp(5-(iF(oRd(MiD((sELect iFNULL(caSt(counT(DisTiNCT(sChEma_NaMe)) aS NchAr),0x20) FrOm INFORMATION_SCHEMA.SCHEMATA),2,1))>9,0,5)))))pwXA) aND 'YJEB'='YJEB HTTP/1.1" 200 729

可知利用了基于时间的盲注,这样就不能看状态码了,要看这个日志文件里记录的时间变化,如果猜的正确就有5秒延迟,不正确就无延迟。

sql server 语句审计 sql审计例题_安全_20


由上图可知从2020:09:33:23到2020:09:33:28延迟了5秒,所以在这一时间段的判断是正确的,这一时间段的判断为

sql server 语句审计 sql审计例题_安全_21


可知表的个数为50。

接下来就是判断数据库的名字组成了(基于时间判断即可):

sql server 语句审计 sql审计例题_mysql_22


其中一条语句为:

/?id=1' ANd (sELeCT 4617 frOM (sELeCT(sLEEP(5-(iF(ORd(mId((sELeCT DistINCt(iFNULL(CaST(ScHeMA_naME aS NCHaR),0x20)) frOM INFORMATION_SCHEMA.SCHEMATA LimiT 0,1),1,1))>64,0,5)))))NZHK) ANd 'twBx'='twBx HTTP/1.1" 200 729

同理一下为猜表的个数与表名:

sql server 语句审计 sql审计例题_HTTP_23


猜字段的个数与字段名:

sql server 语句审计 sql审计例题_安全_24


在得到的sqli库flag表中猜记录的条数:

sql server 语句审计 sql审计例题_sql server 语句审计_25


一下就是最终的猜解flag的过程了:

sql server 语句审计 sql审计例题_数据库_26


这里的时间延迟变成了1秒

其中一条:

/?id=1' And (SELEcT 4235 fROM (SELEcT(SlEep(1-(iF(OrD(miD((SELEcT iFNULL(CaSt(`flag` aS NcHAr),0x20) fROM sqli.`flag` OrDER bY `flag` limIt 0,1),1,1))>64,0,1)))))dfEI) And 'hbou'='hbou HTTP/1.1" 200 729

根据时间延迟手工一个一个的记下 flag 每一个 字符的 ascii 值,对照ascii表即可得到flag。

sql server 语句审计 sql审计例题_安全_27


我们可以发现在猜flag的时候有几条paylaod后面有!=,根据前面记录的时间可知并无延迟,可知每个!=后面的数字就是flag各位的ASCII码。

例题3——1.pcapng(流量分析中的日志审计、sql注入单向判断)

下载题目附件是一个流量包,用wireshark打开,在分组字节流中搜“flag”,发现一堆这样的包:

sql server 语句审计 sql审计例题_数据库_28


很明显这是sql注入的过程,我们筛选http协议,将全部的这种包筛选出来,Ctrl+A全选,复制,

sql server 语句审计 sql审计例题_HTTP_29


粘贴到notepad++中,再url解码(全选,插件—>MIME Tools—>URL Decode):

sql server 语句审计 sql审计例题_安全_30


这是sql盲注单向猜测flag的过程,即flag的每个字符都仅仅判断他的ASCII码大于或小于某个值,不存在大于和小于并行判断的过程(和例题一一样),且发现不能根据状态码进行判断,这样我们就要跟着入侵者的思路走,还原他注入的过程。接下来细细讲解。先看flag的第一个字符的猜测过程:

sql server 语句审计 sql审计例题_HTTP_31


先猜第一个字符ASCII <79,发现不对便扩大范围到102,发现还不对就继续扩大范围到114,发现对了,确实小于114,就又开始稍微缩小范围到<108,发现对了,在稍微缩小范围到<105,发现由对了,又缩小到<103,发现对了,而这时就已经可以确定这个字符的ASCII了,就是102再来看看第二个字符的猜测过程:

sql server 语句审计 sql审计例题_数据库_32


先猜第二个字符ASCII <79,发现不对便扩大范围到102,发现还不对就继续扩大范围到114,发现对了,确实小于114,就又开始稍微缩小范围到<108,发现又不对了,在稍微扩大范围到<111,发现由对了,再稍微缩小到<109,发现对了,而这时就已经可以确定这个字符的ASCII了,就是108

剩下的flag各位字符都是这样猜的,我们发现了一下规律:
在sql盲注里测试的flag每位字符的ASCII码就是这一位猜的最后这条payload里面ASCII码值再减一就是猜解正确的ASCII值,转换成字符就是我们需要的答案的其中一个字符

由以上规律写得Python脚本:

# coding:utf-8
import re
import urllib

f = open('log.txt', 'r')  # 下载的access.log文件的绝对路径,笔者是存在了d盘根目录下~
lines = f.readlines()
datas = []
for line in lines:
    t = urllib.unquote(line)  # 就是将文本进行 urldecode 解码
    datas.append(t)

flag_ascii = {}
for data in datas:
    matchObj = re.search(r'0,1\),(.*?),1\)\)<(.*?)#',data)  # 在date 中搜索符合正则表达的字符串并 将匹配的字符串存入变量 matchObj 中
    if matchObj:
        key = int(matchObj.group(1))  # 取变量matchObj 中 的第一个括号里的内容 (也就是上条语句中的 (.*?)中的内容)并转为10进制
        value = int(matchObj.group(2))-1  # 取变量matchObj中的第二个括号里的内容,并转为 10 进制
        flag_ascii[key] = value  # 使用字典,保存最后一次猜解正确的ascii码(这里经过几次循环不断更新后后最终保存的就是该字符最后一次猜解正确的ascii码)

flag = ''
for value in flag_ascii.values():
    flag += chr(value)

print flag

输出得到一个残缺的flag:

flag{527cdaa51ff257a457d2225d/42d8ca2|