Flask文件上传漏洞实战:利用os.path.join绕过限制读取系统文件

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值