美文网首页
flask debug配合文件上传getshell

flask debug配合文件上传getshell

作者: wuli_decade | 来源:发表于2018-08-27 21:50 被阅读113次

    前言

    在8月25日的安恒杯月赛题出现了一道flask debug配合任意文件读取的题,当时没有搞出来,作为萌新没见过这种题目,所以赛后经过讲解,自己本地复现了一波,收益良多。

    0x01

    此漏洞主要是利用Flask在debug会生成一个pin码。

    E:\1待处理\123\flaskfuxianaaa\venv\Scripts\python.exe -m flask run
     * Serving Flask app "app.py" (lazy loading)
     * Environment: development
     * Debug mode: on
     * Restarting with stat
     * Debugger is active!
     * Debugger PIN: 229-992-815
     * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
    
    image.png

    问题出在这个pin码的生成,在同一台机子上多次启动同一个Flask应用时,会发现这个pin码是固定的。

    0x02


    漏洞测试环境:
    windows
    python3.6
    Flask 1.0.2


    0x03

    现在来分析pin码是如何生成的,本人是用pycharm单步debug,下面只给出重要代码段。

    from flask import Flask
    
    app = Flask(__name__)
    
    
    @app.route('/')
    def hello_world():
        return 'Hellso World!'
    
    
    if __name__ == '__main__':
        app.run()
    

    跟进app.run函数。


    image.png

    跟进run_simple函数


    image.png
    image.png
    跟进DebuggedApplication类
    image.png

    跟进__init__函数,可以看到两个跟pin相关的参数


    image.png
    可以看到_get_pin函数钟有对pin赋值的地方。
    image.png
    跟进get_pin_and_cppkie_name函数
    image.png
    看到注释,可以肯定这个函数就是跟pin的生成有关,下面贴出源码。
    def get_pin_and_cookie_name(app):
        """Given an application object this returns a semi-stable 9 digit pin
        code and a random key.  The hope is that this is stable between
        restarts to not make debugging particularly frustrating.  If the pin
        was forcefully disabled this returns `None`.
    
        Second item in the resulting tuple is the cookie name for remembering.
        """
        pin = os.environ.get('WERKZEUG_DEBUG_PIN')
        rv = None
        num = None
    
        # Pin was explicitly disabled
        if pin == 'off':
            return None, None
    
        # Pin was provided explicitly
        if pin is not None and pin.replace('-', '').isdigit():
            # If there are separators in the pin, return it directly
            if '-' in pin:
                rv = pin
            else:
                num = pin
    
        modname = getattr(app, '__module__',
                          getattr(app.__class__, '__module__'))
    
        try:
            # `getpass.getuser()` imports the `pwd` module,
            # which does not exist in the Google App Engine sandbox.
            username = getpass.getuser()
        except ImportError:
            username = None
    
        mod = sys.modules.get(modname)
    
        # This information only exists to make the cookie unique on the
        # computer, not as a security feature.
        probably_public_bits = [
            username,
            modname,
            getattr(app, '__name__', getattr(app.__class__, '__name__')),
            getattr(mod, '__file__', None),
        ]
    
        # This information is here to make it harder for an attacker to
        # guess the cookie name.  They are unlikely to be contained anywhere
        # within the unauthenticated debug page.
        private_bits = [
            str(uuid.getnode()),
            get_machine_id(),
        ]
    
        h = hashlib.md5()
        for bit in chain(probably_public_bits, private_bits):
            if not bit:
                continue
            if isinstance(bit, text_type):
                bit = bit.encode('utf-8')
            h.update(bit)
        h.update(b'cookiesalt')
    
        cookie_name = '__wzd' + h.hexdigest()[:20]
    
        # If we need to generate a pin we salt it a bit more so that we don't
        # end up with the same value and generate out 9 digits
        if num is None:
            h.update(b'pinsalt')
            num = ('%09d' % int(h.hexdigest(), 16))[:9]
    
        # Format the pincode in groups of digits for easier remembering if
        # we don't have a result yet.
        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
    
        return rv, cookie_name
    

    可以看到,计算pin码时,是通过向本机取一下东西,经过md5等操作算出来的。


    image.png

    看看debug调试结果


    image.png

    分析出六个值分别位:

    wuli丶Decade
    flask.cli
    DispatchingApp------------------>run函数所属的类
    E:\\1待处理\\123\\flask11\\venv\\Lib\\site-packages\\flask\\cli.py------>根据报错信息得出路径
    xxxxx5457141305     ------->某网卡的mac地址的十进制
    xxxxxx-8fcb-44d5-be62-36049d2db881        分linux、windows、mac,Windows是从注册表中取一个值。
    
    image.png

    下面改脚本,在本地计算出pin值


    image.png

    回到题目本身

    由于pycharm版本的原因,构造的六个值稍微有点不同,下面直接给出路径和值。

    username ------>/etc/passwd
    中间两个一般固定
    路径(根据报错)
    网卡的mac地址一般存在 /sys/class/net/网卡/address
    linux---> /etc/machine-id or /proc/sys/kernel/random/boot_id

    'ctf'
    'flask.app'
    'Flask'
    '/usr/local/lib/python2.7/dist-packages/flask/app.pyc'
    '2485377892354'
    ''
    

    但是这里有两个坑
    1、路径报错是pyc。
    2、/etc/machine-id路径下的值确实是空的。

    image.png image.png
    image.png

    相关文章

      网友评论

          本文标题:flask debug配合文件上传getshell

          本文链接:https://www.haomeiwen.com/subject/fmvtwftx.html