CVE-2026-46529: 存在10年之久的Linux PDF查看器远程代码执行漏洞(XReader/Evince/Atril)
摘要
某安全研究人员发现了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
相似文章
Linux漏洞、禁运失效与补丁窗口缩短
一份关于2026年5月发现的三个严重Linux本地权限提升漏洞的报告,强调了披露模型的崩溃及其对生产环境的影响。
Dead.Letter (CVE-2026-45185) – XBOW 如何在 Exim 中发现未认证的远程代码执行漏洞
XBOW 披露了 CVE-2026-45185,这是一个存在于 Exim 邮件服务器中的严重未认证远程代码执行漏洞,由 TLS 处理中的释放后使用错误引起。本文详细阐述了技术漏洞的开发过程以及人工智能模型在漏洞发现中的作用。
CVE-2026-40369: 通过NtQuerySystemInformation实现任意内核地址递增
CVE-2026-40369 描述了 Windows 内核中 NtQuerySystemInformation 函数的一个漏洞,该漏洞允许任意内核地址递增,使无特权的进程(包括 Chrome 沙箱)能够提升权限。该利用在 Windows 11 24H2-25H2 上是确定性的。
CVE-2026-31431: Copy Fail
CVE-2026-31431(Copy Fail)是Linux内核中的一个本地提权漏洞,影响自2017年以来的所有主流发行版,允许非特权用户通过AF_ALG加密子系统对任何可读文件的页缓存进行确定性的4字节写入,从而获得root shell访问权限。
利用一个18年前的漏洞实现NGINX远程代码执行
研究人员利用自动化系统发现NGINX重写模块中一个自2008年以来存在的严重堆缓冲区溢出漏洞(CVE-2026-42945),可实现远程代码执行。多个CVE已获NGINX确认。