Flask文件上传中的路径拼接陷阱:从原理到实战的深度解析
最近在复盘一些CTF题目和实际渗透测试案例时,我发现一个看似简单却极具迷惑性的安全问题——Flask应用中os.path.join函数的路径拼接特性被恶意利用,从而绕过文件上传限制,直接读取系统敏感文件。这个问题不仅出现在CTF赛题中(比如NISACTF 2022的babyupload),在真实世界的Web应用里也时有发生。很多开发者在使用这个函数时,往往只关注其“拼接”功能,却忽略了它在特定输入下的“路径重置”行为,这给应用安全埋下了不小的隐患。
今天,我们就来深入拆解这个漏洞的完整链条。我会从Flask的文件上传机制讲起,结合os.path.join的底层行为、SQLite数据库的交互,以及如何构造有效的攻击载荷(PoC),最终给出几种不同维度的防御思路。无论你是Web开发者想加固自己的应用,还是安全研究人员想深入理解这类漏洞的利用技巧,这篇文章都会提供足够多的实战细节和思考角度。
1. 漏洞场景重现:一个典型的Flask文件上传应用
为了让大家有更直观的感受,我们先来看一个简化但功能完整的Flask文件上传应用。这个应用模拟了常见的用户头像上传、文档分享等场景:用户上传一个文件,后端为其生成一个唯一的ID(UUID)并存储,之后用户可以通过这个ID来访问自己上传的文件。
from flask import Flask, request, redirect, g, send_from_directory
import sqlite3
import os
import uuid
app = Flask(__name__)
# 初始化数据库,存储文件ID和原始文件名
SCHEMA = """CREATE TABLE files (
id text primary key,
path text
);
"""
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect("database.db")
return db
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return redirect('/')
file = request.files['file']
# 关键限制:文件名不能包含点号(.)
if "." in file.filename:
return "Bad filename!", 403
conn = get_db()
cur = conn.cursor()
file_id = uuid.uuid4().hex # 生成唯一UUID
try:
cur.execute("INSERT INTO files (id, path) VALUES (?, ?)", (file_id, file.filename))
except sqlite3.IntegrityError:
return "Duplicate file"
conn.commit()
# 保存文件到 uploads/ 目录下
file.save('uploads/' + file.filename)
return redirect('/file/' + file_id)
@app.route('/file/<id>')
def serve_file(id):
conn = get_db()
cur = conn.cursor()
cur.execute("SELECT path FROM files WHERE id=?", (id,))
result = cur.fetchone()
if result is None:
return "File not found", 404
filename = result[0]
# 关键操作:拼接上传目录和文件名,然后读取文件内容
filepath = os.path.join("uploads/", filename)
with open(filepath, "r") as f:
return f.read()
这个代码看起来逻辑清晰,甚至做了一些安全考虑:比如禁止文件名中包含点号(.),这通常是为了防止上传.php、.jsp等可执行脚本。文件通过UUID访问,隐藏了真实的存储路径和文件名,似乎增加了安全性。
注意:这里为了演示漏洞,代码简化了错误处理和文件存储逻辑。在实际应用中,还需要考虑文件大小限制、MIME类型检查、文件名重命名等问题。
但正是这个看起来合理的os.path.join("uploads/", filename)操作,成为了整个安全链条中最脆弱的一环。我们接下来就深入看看,这个函数到底做了什么。
2. 核心漏洞原理:os.path.join的“绝对路径陷阱”
os.path.join是Python中用于拼接路径的常用函数,它的基本行为是将多个路径组件智能地连接起来,并自动处理不同操作系统下的路径分隔符(比如Windows用\,Linux/macOS用/)。大多数开发者对它的认知停留在“智能拼接”上,却忽略了官方文档中一个关键描述:
如果某个组件是一个绝对路径,则在该组件之前的所有组件都会被丢弃,并从该绝对路径组件开始继续拼接。
这句话是理解整个漏洞的关键。我们通过几个简单的例子来感受一下:
import os
# 案例1:常规的相对路径拼接
print(os.path.join("up


1600

被折叠的 条评论
为什么被折叠?



