如何用Apache PDFBox验证PDF外部引用及嵌入JavaScript?
Apache PDFBox 2.0.32 任务实现指南
一、检测PDF嵌入JavaScript的正确依赖
PDFBox 2.0.32处理JavaScript检测无需额外扩展依赖,核心pdfbox依赖即可覆盖所有JavaScript动作的解析需求。以下是主流构建工具的依赖配置:
Maven
<dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.32</version> </dependency>
Gradle
implementation 'org.apache.pdfbox:pdfbox:2.0.32'
若需处理JavaScript执行场景(非检测需求),可额外添加pdfbox-tools依赖,但仅做检测的话核心依赖足够。
二、检测PDF嵌入JavaScript的示例代码
JavaScript在PDF中通常分布在文档级动作、页面动作、表单字段动作、注释动作四类位置,以下是覆盖所有场景的检测代码:
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.interactive.action.PDAction; import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; import org.apache.pdfbox.pdmodel.interactive.action.PDAnnotationAdditionalActions; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDField; import java.io.File; import java.io.IOException; import java.util.List; public class PdfJavaScriptDetector { public static void detectJavaScript(String pdfPath) throws IOException { try (PDDocument document = PDDocument.load(new File(pdfPath))) { // 检测文档级打开动作 checkForJavaScript(document.getDocumentCatalog().getOpenAction(), "文档打开动作"); // 检测每页的打开/关闭动作 List<PDPage> pages = document.getPages(); for (int i = 0; i < pages.size(); i++) { PDPage page = pages.get(i); checkForJavaScript(page.getActions().getO(), "第" + (i+1) + "页打开动作"); checkForJavaScript(page.getActions().getC(), "第" + (i+1) + "页关闭动作"); } // 检测表单字段触发动作 PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); if (acroForm != null) { for (PDField field : acroForm.getFields()) { checkForJavaScript(field.getActions().getK(), "表单字段[" + field.getFullyQualifiedName() + "]动作"); } } // 检测注释触发动作 for (PDPage page : pages) { List<PDAnnotation> annotations = page.getAnnotations(); for (PDAnnotation annotation : annotations) { PDAnnotationAdditionalActions annotActions = annotation.getActions(); if (annotActions != null) { checkForJavaScript(annotActions.getU(), "页面注释激活动作"); } } } } } private static void checkForJavaScript(PDAction action, String location) { if (action instanceof PDActionJavaScript) { PDActionJavaScript jsAction = (PDActionJavaScript) action; System.out.println("找到JavaScript,位置:" + location); System.out.println("JS内容:" + jsAction.getAction()); } } public static void main(String[] args) throws IOException { detectJavaScript("test.pdf"); } }
三、PDF外部引用(URL链接)检测与安全验证
检测逻辑
PDF中的外部URL链接以PDActionURI类型的动作存在,需遍历文档、页面、表单、注释等所有可能的动作位置提取URL,再执行安全校验。
安全验证核心维度
- 协议限制:仅允许
http/https,禁止file:///ftp://等风险协议 - 域名白名单:仅允许访问预先信任的域名
- 跳转检测:拦截含跳转参数的可疑URL
示例代码
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.interactive.action.PDAction; import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDField; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; import java.util.List; public class PdfExternalLinkDetector { // 自定义信任域名白名单 private static final List<String> TRUSTED_DOMAINS = Arrays.asList("example.com", "github.com"); public static void detectAndValidateExternalLinks(String pdfPath) throws IOException { try (PDDocument document = PDDocument.load(new File(pdfPath))) { // 检测文档级动作中的URL checkForUriAction(document.getDocumentCatalog().getOpenAction(), "文档打开动作"); // 检测页面动作与注释中的URL List<PDPage> pages = document.getPages(); for (int i = 0; i < pages.size(); i++) { PDPage page = pages.get(i); checkForUriAction(page.getActions().getO(), "第" + (i+1) + "页打开动作"); checkForUriAction(page.getActions().getC(), "第" + (i+1) + "页关闭动作"); for (PDAnnotation annotation : page.getAnnotations()) { checkForUriAction(annotation.getAction(), "页面注释动作"); } } // 检测表单字段动作中的URL PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); if (acroForm != null) { for (PDField field : acroForm.getFields()) { checkForUriAction(field.getActions().getK(), "表单字段[" + field.getFullyQualifiedName() + "]动作"); } } } } private static void checkForUriAction(PDAction action, String location) { if (action instanceof PDActionURI) { PDActionURI uriAction = (PDActionURI) action; String urlStr = uriAction.getURI(); System.out.println("找到外部URL,位置:" + location); System.out.println("URL:" + urlStr); boolean isSafe = validateUrlSafety(urlStr); System.out.println("URL安全状态:" + (isSafe ? "安全" : "危险")); System.out.println("---"); } } private static boolean validateUrlSafety(String urlStr) { try { URL url = new URL(urlStr); // 验证协议合法性 String protocol = url.getProtocol(); if (!protocol.equalsIgnoreCase("http") && !protocol.equalsIgnoreCase("https")) { System.out.println("风险:使用非允许协议 " + protocol); return false; } // 验证域名是否在白名单 String host = url.getHost(); if (!TRUSTED_DOMAINS.contains(host)) { System.out.println("风险:域名 " + host + " 不在信任白名单中"); return false; } // 检测跳转参数(可选) if (url.getQuery() != null && url.getQuery().contains("redirect")) { System.out.println("风险:URL包含跳转参数"); return false; } return true; } catch (MalformedURLException e) { System.out.println("风险:无效URL格式"); return false; } } public static void main(String[] args) throws IOException { detectAndValidateExternalLinks("test.pdf"); } }
内容的提问来源于stack exchange,提问作者Ravi Dobariya
相关产品推荐
相关产品推荐

