CVE-2026-46529: 存在10年之久的Linux PDF查看器远程代码执行漏洞(XReader/Evince/Atril)

Lobsters Hottest 新闻

摘要

某安全研究人员发现了CVE-2026-46529,这是Linux PDF查看器XReader、Evince和Atril中一个存在10年之久的远程代码执行漏洞,原因是在生成子进程以打开远程文档链接时,参数引用不充分。

<p><a href="https://lobste.rs/s/rlpzut/cve_2026_46529_10_year_old_rce_linux_pdf">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/05/22 22:38

# CVE-2026-46529:Linux PDF 查看器(XReader/Evince/Atril)中存在十年的 RCE 漏洞 来源:https://medeiros.zip/posts/CVE-2026-46529-evince ## 引言 前段时间,我开始强烈想要分析开源应用程序代码以寻找漏洞,主要关注那些能让我更深入探索模糊测试、堆溢出、越界读写等技术的应用。 而激励我最多的,是 Calif 博客的一系列文章。Calif 是一家非常酷的网络安全公司,他们通过利用当前 AI(如 ChatGPT 5.5 和 Claude Opus)发现了一些非常有趣的漏洞。推荐阅读 https://blog.calif.io/ 上的文章。 我决定将研究重点放在流行的 Linux PDF 查看器上,于是我选择了 xreader/evince/atril。你总会看到这些斜杠分隔的名称,因为它们是我的主要研究对象,而且它们共享相同的代码库——通用阅读器 XREADER。 Evince 是一个与 GNOME 界面一起使用的非常流行的 PDF 阅读器,而 Atril 来自 MATE 界面,广泛用于 Linux Mint 和 Ubuntu LTS。 ## 模糊测试 Evince/Atril 嗯,不幸的是,或者也许是幸运的是,这个故事不会很长。我和小 Claude 一起对阅读器的许多组件进行了模糊测试,但是,我未能将发现的任何 bug 提升为可能的 RCE。我不知道这到底是技术上的限制,还是显示器与椅子之间的问题。所以我逐渐开始专注于枚举阅读器的功能,并鞭策 Claude 进行代码审查。 ## 注入 不幸的是,叙述将不会 100% 忠实,因为我丢失了展示如何通过 Claude 达到这一点的漏洞提示历史/流程。但我认为这里真正有价值的是技术内容本身的解释。 由于在发现内存损坏漏洞向量方面遇到困难,AI 分析流程开始转而分析应用程序包装器,即负责执行应用程序逻辑的部分。 在分析崩溃几天后,我们开始寻找其他向量,最终找到了 `ev_spawn` 函数。 `ev_spawn` 函数是一个内部函数,位于 `shell/ev-application.c` 中;它负责在程序需要打开远程文档时创建一个新的查看器进程:PDF 内部对另一个 PDF 的引用,就像 PDF 之间的链接。因此,用户点击 PDF 内部的这个链接,`/GoToR`(前往远程)动作会执行负责打开另一个文档的函数。 当用户点击链接时,程序会生成自身的一个新实例来打开目标文档,这正是 `ev_spawn` 执行的地方。 在分析函数文件时,经过几轮之后,Claude 注意到了以下内容: 源代码: Claude 发现代码中有 3 个参数没有使用 `g_shell_quote` 函数。这意味着攻击者控制的字符串在没有保护的情况下被传递。 > `g_shell_quote` 是 GLib(GNOME/GTK 生态系统的核心库)中的一个函数,它对字符串进行转义,以便在通过 shell 解析器传递时被解释为单个 argv 元素。 实际上,这意味着,不是将整个提供的字符串解释为单个参数,而是每个空格都会被解释为一个新的标志。下面是一个使用虚构标志 `inject` 的示例。 于是,当字符串被传递给 `g_app_info_create_from_commandline` 函数时,它将接收: 这样,我们确定我们能够将标志作为参数注入到点击后生成的子进程中。Claude 现在需要找到一个允许命令执行的标志。 GTK3 是一个由 GNOME 维护的图形界面库,在 Linux 上非常流行。它为图形界面提供了可视化构建块,如窗口、按钮、菜单等。许多发行版和桌面环境都基于它构建,如 Cinnamon、MATE、XFCE 等。 这里关于这个库的重点是,它在每个使用它的进程中加载自己的标志,任何在其上运行的应用程序都可以使用这些标志。进程某种程度上“继承”了这些标志。其中一个,我们感兴趣的那个,是 `--gtk-module` 标志。 这意味着 Linux 上有数百个二进制文件接受标准化的继承 GTK 标志目录:`--display`、`--screen`、`--gtk-debug`、`--name`、`--class`、`--sync`,以及我们关心的 `--gtk-module=PATH`。 它的实现有助于我们。在 GTK3 中,当 `gtk_init` 函数处理 `argv` 并发现 `--gtk-module=` 时,最终会到达这段代码: `g_module_open` 函数是一个使用 GLIBC 的 `dlopen(3)` 系统调用的函数。这里是最有趣的部分:当加载共享 ELF 文件时,`dlopen` 会自动执行所有标记为 `__attribute__((constructor))` 的符号。这意味着即使不导出任何函数,构造函数内的所有代码都将被执行。 因此,我们已经有用来升级的标志。我们需要构建一个恶意的 `.so` 库,该库将被 `--gtk-module` 标志处理并执行我们的恶意代码。 知道了这一点,利用的第一部分就完成了。我们可以传递 `--gtk-module=/tmp/evil.so` 标志,其中包含恶意 `.so` 库的路径,而不是 `--inject`,这将强制执行我们的代码。 带有注入标志的子进程执行将如下所示: 我们成功了!我们获得了命令执行! ## 多语种文件 PDF+ELF 现在我们有了另一个问题。攻击者不想发送一个包含 PDF 和同一目录中 `.so` 库的 zip 文件;显然,这非常可疑,我们需要消除这个要求。 与 Claude 讨论后,它给了我一个选择:创建多语种文件。 多语种文件是一种技术,其中文件以这样的方式构建,使得系统可以将其识别为两种不同的文件类型。在这种情况下,我们需要一个文件,既能被系统识别为 PDF,又能作为 `.so` 库加载:一个文件两种用途。 这样,`--gtk-module` 标志不再指向 `.so` 文件,而是直接指向 PDF 文件本身,并由系统作为库加载。 一个单一文件同时是 PDF(供 Atril 打开)和 ELF(供 `dlopen` 加载)。`%PDF-1.4` 魔术字节嵌入在偏移量 `0x1d8` 处的 `.note.gnu.build-id` 部分中,这是一个信息性的 20 字节槽位,`ld.so` 不会验证它,但仍然在 1024 字节窗口内。 由于我对多语种文件一无所知,我将脚本 100% 委托给了 Claude,然后它返回了 `build_polyglot.py`,我向它传递恶意库和要生成的 PDF 名称。 完成。现在我们有一个 PDF,当对其使用 `file` 命令时,它被看作是一个 ELF 可执行文件,但当被系统查看时,它被视为 PDF。 问题解决。Claude 为我生成了一个脚本,可以创建双文件,既是 `.so` 又是 `.pdf`。我们现在要做的就是将包含 `--gtk-module=` 指向 PDF 自身的注入插入到 PDF 中。 现在我们还有最后一个问题。我们将 PDF 文件的直接路径传递给 `--gtk-module`。如果我们想把它发送给受害者,我们需要知道文件在系统中的位置,而且由于路径包含 Linux 主目录,我们还需要知道用户名。这就引出了第三个问题。 ## 我在哪里? 我尝试了一些手动方法,比如使用 `/proc/self`(Linux 系统中自身进程的文件所在位置),但所有这些技术实际上都不起作用。我让 Claude 分析,但即便如此,它也无法利用 `/proc`。 在另一次尝试中,我交给 Claude 任务,让它阅读主要的 `ev-window.c` 代码,并尝试检查解决这个问题的方法。过了一段时间,它实际上带来了一个解决方案,总的来说,就在我眼前。 Claude 在 `shell/ev-window.c:6347-6350` 中发现了 `open_remote_link` 处理 `/GoToR` 的位置: 并且它还发现了一个我们之前遗漏的细节:构建的字符串(`"/usr/bin/atril --named-des..."`)并不直接接收 URI/PATH。它通过 `g_app_info_launch_uris` 函数单独传递,该函数在执行时,会让 GLib 在运行时替换命令行中的占位符,例如 `%u`(URI 形式)和 `%f`(本地路径)。 这意味着,我不需要传递字面路径,只需要传递占位符,遵循 Linux `.desktop` 文件使用的相同标准化。 然而,还有另一个问题: 在调查过程中,Claude 在 `ev-application.c:596` 中发现了: 上面的 `if` 基本上就是说,如果 `/F` 参数的值与文档 PATH 相同,那么 `ev_spawn`(我们需要触发的函数)不会触发。 逻辑很简单:`/GoToR` 参数指向另一个文档,因此生成一个新进程来打开另一个文档是有意义的,因为你想要同时阅读两者。但是,如果它指向你正在阅读的同一个文档,它会在文档内部导航,而不执行 `ev_spawn`,因此也就没有 RCE。 Claude 用简单的方法解决了这个问题,将 `/F = "x.pdf"` 改为 `/F = "x.pdf?1"`。`?1` 是一个空查询字符串;GLib 在运行时由系统解析时会忽略它,但是,在 `if` 内部进行比较时,结果是两者不同,这将强制 `ev_spawn` 的执行。 > strcmp() → 不同 → spawn 运行 现在当 GLib 通过上面提到的 `g_app_info_launch_uris` 函数进行解析时,它会忽略 `?1`,这可以防止我们的利用失败。 这样,就最终确定了 PDF 中的参数如下: 完美。现在我们有了一个完整的利用方式,我们解决了以下问题: - 我们不需要知道文件在系统中的字面 PATH;它将在运行时被解析。 - 我们不需要任意的 `.so` 文件;它包含在 PDF 自身内部,指向自身。 现在,当用户打开 PDF 并点击页面上的任何位置时,恶意命令将被执行。 ## 概念验证 ## 报告 该漏洞已报告给 Evince 和 Atril。 https://github.com/mate-desktop/atril/security/advisories/GHSA-vgv2-m826-8f6f 你可以在这里找到源代码: https://github.com/N1et/CVE-2026-46529

相似文章

CVE-2026-31431: Copy Fail

Lobsters Hottest

CVE-2026-31431(Copy Fail)是Linux内核中的一个本地提权漏洞,影响自2017年以来的所有主流发行版,允许非特权用户通过AF_ALG加密子系统对任何可读文件的页缓存进行确定性的4字节写入,从而获得root shell访问权限。