首页 时政热点 科技头条 智能AI 安全攻防 数码硬件 开发者生态 归档 标签 关于

22 年之久的 PostgreSQL Bug 变成了远程代码执行

摘要

If you had told me in April 2026 one question “Mehmet, can we use managed PostgreSQL for this project?” would send me into the deepest rabbit hole of my life, I would have laughed. Months later, I fin...

the PostgreSQL that and you had one for this will
2026-11-13 1 阅读 约7分钟阅读 ozirus
分享:
字号:
如果您在 2026 年 4 月告诉我一个问题“Mehmet,我们可以在这个项目中使用托管 PostgreSQL 吗?”会把我送进人生最深的兔子洞,我会笑的。几个月后,我终于找到了那个兔子洞的底部。这是 PostgreSQL 核心中一个 22 年前的零日漏洞。在这篇博文中,您将阅读一个关于我如何最终发现 PostgreSQL 中一类新的类型混淆漏洞的故事。这是名为“托管 PostgreSQL 行业的系统性风险”的博客文章系列的第三部分。对于那些错过了前两部分的人,请随意从第一部分和第二部分开始,这将为您提供这种疯狂的完整背景。 TLDR:我在复合类型中给了 PostgreSQL 一个值,更改了其中一个字段的类型,并要求它读回该值。这种类型混淆导致了我所见过的最可靠的内存损坏漏洞利用之一。但我并不是从寻找 PostgreSQL 核心漏洞开始的。我正在利用 pg_clickhouse,并且让该漏洞发挥作用给我留下了一个关于 PostgreSQL 如何理解自己的数据的问题。在一位人工智能代理的指导下,我一次一个地解决了内部问题。然后,一场看似无关的对话给了我将这一切联系起来的想法,最终我发现自己盯着一个已经存在了一段时间的原始人。这是这些片段如何组合在一起的完整真实故事,我相信您会喜欢这个故事。 2026 年 8 月开始 在 6 月和 7 月期间,我一直在利用多个 PostgreSQL 扩展的内存损坏漏洞。我实现代码执行的扩展之一是 pg_clickhouse 。这个案例研究有它自己的故事,我将在将来分享。这里重要的是这个漏洞迫使我学习什么。 pg_clickhouse 将 ClickHouse 值转换为本机 PostgreSQL 值。 ClickHouse 数组不是 PostgreSQL 数组,对吗?因此,扩展必须将其元素重建为 PostgreSQL 期望的格式的 Datum 值。该转换中的一个错误最终导致了我的代码执行。这是我第一次在 PostgreSQL 领域遇到 Datum、OID 等东西。我学到的知识只够完成漏洞利用,但有一个问题一直萦绕在我的脑海中:PostgreSQL 本身如何决定一个值是什么?它是如何处理和表示的?大约在 7 月下旬的时候,我终于摆脱了扩展的束缚,跟随这个问题进入了 PostgreSQL 核心。免责声明:我向供应商报告了 pg_clickhouse oob 写入 + 读取链,他们于 6 月 23 日修复了该问题。 PostgreSQL 类型 101 我从一个非常简单的查询开始学习。当我运行最简单的查询 SELECT 1337 时会发生什么?虽然我避免了 Postgres 内部的所有黑魔法,但我还是将这个速成课程保留在数据类型的基础知识上。我从这个查询开始。设置 debug_print_parse = on ;选择 1337 ;数据库扫描器查看查询,将 SELECT 识别为关键字,将 1337 识别为整数标记。然后它为查询创建一个解析树。接下来,PostgreSQL 遍历该树并将其转换为类型化查询树。 { QUERY : commandType 1 :targetList ( { TARGETENTRY : expr { CONST : consttype 23 :consttypmod -1 :constlen 4 :constbyval true :constisnull false :location 7 :constvalue 4 [ 57 5 0 0 0 0 0 0 ] } } ) } 解析树如下所示。但我只是想更好地理解诸如 consttype 23 是什么?为什么 constvalue 有 [57 5 0 0 0 0 0 0] 我的 1337 值在哪里?此时,我要求 Opus 5 为我准备一门有关 PostgreSQL 类型的完整深入课程。在那里花了几天时间,与我的代理(我称之为 pg_expert)交谈,教会了我这些: consttype = 23 → int4 constlen = 4 → 四字节负载 constbyval = true → 按值存储 constisnull = false → 这不是 NULL constvalue 4 [ 57 5 0 0 0 0 0 0 ] 可以转换为十六进制,使值更容易识别: 57 十进制 = 0x39 5 十进制 = 0x05 在我的小端系统上,字节首先以最低有效位存储: 39 05 00 00 → 0x00000539 → 1337 所以 1337 并没有消失。但为什么我们有 8 个字节 [57 5 0 0 0 0 0 0] 而不是简单的 4 个字节呢?这是因为 constvalue 没有声明为 int32 。它被声明为 Datum ,它是 8 个字节我们的值 1337::int4 值存储在该通用载体的较低四个字节中。同样的 8 字节数据如何表示文本、数组或自定义类型?数据不知道它是什么 与我的 pg_expert 代理聊天教会了我更多。数据不知道它代表什么。携带1337的八个字节不包含“我是一个整数”的隐藏标签。 PostgreSQL 仅因为 consttype = 23 才知道其类型。该数字是一个 OID(对象标识符),指向 pg_type 系统目录中的一行 SELECT oid,typname,typlen,typbyval,typalign,typinput::regproc,typoutput::regproc FROM pg_type WHERE oid = 23 ;类 |类型名称 |类型 |典型的类型对齐 |打字输入 |打字输出
这篇文章对您有帮助吗?

订阅66必读

每日精选科技资讯,直达你的邮箱