Flask_PIN码伪造
漏洞概念及成因
Flask 是一个用 Python 编写的,基于 WSGI(Web Server Gateway Interface)和 Jinja2 模板引擎的轻量级 Web 应用框架。
当 Flask 应用以 Debug 模式启动后,同时也会启动一个调试控制台,可通过浏览器访问/console
路由进入,该调试控制台需要 PIN 码才能进入,而 PIN 码在服务端启动应用程序时会显示出来。进入调试控制台后,就可以执行 Python 语句和命令。
通常情况下,开发人员在开发阶段为了提高效率,会以 Debug 模式启动程序,而到了正式运行阶段,则会关闭 Debug 模式。但是有些粗心的开发人员或运维人员,在程序运行阶段,忘记关闭 Debug 模式,从而给攻击者以可乘之机。当然,进入并使用调试控制台,需要输入正确的 PIN 码。
我们在靶机上运行如下的 python 程序。
app.py
1 | from flask import Flask, request |
由于打开了调试模式,在服务端启动该应用程序时则会显示 PIN 码以便于进入调试控制台进行调试。
1 | root@VM-8-17-ubuntu:~# python3 app.py |
你可以尝试重新运行该程序,会发现显示的 PIN 码并没有发生改变,这说明,PIN 码是根据一些系统参数和相对固定的算法进行生成的。
漏洞利用
接下来,我们需要配合该网站在/fileread
路由下存在的任意文件读取漏洞,读取目标服务器的不同文件,获取重要参数,从而计算出正确的 PIN 码进入调试控制台。
http://192.168.184.200:8000/fileread?filename=1
尝试访问一个目标服务器中并不存在的文件,从而得到 Flask 报错页面,在这个页面上,我们可以得到一个路径/usr/local/lib/python3.6/dist-packages/flask/app.py
,这是 python 中 Flask 框架的文件路径,从这个路径名中可以得到 python 的版本号,也可以找到对应的生成 PIN 码的程序路径。
http://192.168.184.200:8000/fileread?filename=/usr/local/lib/python3.6/dist-packages/werkzeug/debug/__init__.py
在这个文件中,存有生成 PIN 码的关键函数get_pin_and_cookie_name()
,具体算法生成 PIN 码的逻辑我们在此不予以深究,后续会有相关脚本可以直接使用,需要注意的一点是,不同版本的 python 在生成 PIN 码的算法方面有所区别,所以后续的脚本也会有所不同。
生成 PIN 码需要哪些参数?
username
为启动该程序的用户名,通过读取文件/etc/passwd
的内容进行猜测。
modname
默认值为flask.app
。
appname
默认值为Flask
。
moddir
app.py 所在位置的绝对路径,即/usr/local/lib/python3.6/dist-packages/flask/app.py
。
uuidnode
当前网络 mac 地址的十进制数,一般通过读取文件/sys/class/net/eth0/address
的内容得到16进制结果,转化为10进制即可,其中eth0
为相应的网卡名字。
可以通过如下 python 程序进行转换
1 | str = "02:42:93:0a:ef:2e" |
machine_id
每一个机器都会有自已唯一的id,linux 的 id 一般存放在/etc/machine-id
或/proc/sys/kernel/random/boot_id
中,docker 靶机则存放在/proc/self/cgroup
中。
不同版本的 python 的 machine_id 构成不同
一般从以下文件/proc/self/cgroup、/etc/machine-id,、/proc/sys/kernel/random/boot_id中的相关内容进行拼接或者单独构成,暂未实验得出具体结果。
如果想使用/proc/self/cgroup
路径,但是self
被过滤了,其中的self
可以用相关进程的pid去替换,即/proc/1/cgroup
,如果cgroup
也被过滤了,可以使用mountinfo
或者cpuset
进行替换。
不同操作系统的 machine_id 所在路径
Linux
etc/machine-id,/proc/sys/kernel/random/boot_id,/proc/self/cgroupWindows
SOFTWARE\Microsoft\Cryptography
生成 PIN 码
获取到生成 PIN 码的相关参数后,使用脚本生成 PIN 码。
python3.8及以上
1 | #MD5 |
python3.8以下
1 | #sha1 |
调试控制台
进入调试控制台后可以执行 Python 语句和命令。
1 | import os; print(os.popen('ls').read()) |
实战拓展
在此简述笔者曾经做过的某题的情况,成功进入调试控制台后,由于用户权限不够,并不能直接查看到 flag 文件,需要利用调试控制台发起反弹 shell ,后续进一步进行提权操作。
那此题是通过 suid 提权成功拿到 flag 的,该如何操作呢?
suid提权
寻找具有 suid 权限的⽂件,因为目前用户权限有限,避免产生⼤量的报错信息,故将错误流重定向到/dev/null
,找到/usr/bin/find
文件具有 suid 权限,在gtfobins中查到 find 提权的相关 exp,使用即可提权成功。find . -exec /bin/sh -p \; -quit