Skip to content

Commit af97ecd

Browse files
committed
add check in isPDFSafe() method - #1
1 parent 353301e commit af97ecd

File tree

3 files changed

+35
-25
lines changed

3 files changed

+35
-25
lines changed

src/main/java/eu/righettod/SecurityUtils.java

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.apache.pdfbox.pdmodel.interactive.annotation.AnnotationFilter;
2020
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
2121
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
22+
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
2223
import org.apache.poi.poifs.filesystem.DirectoryEntry;
2324
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
2425
import org.apache.poi.poifs.macros.VBAMacroReader;
@@ -288,6 +289,7 @@ public boolean accept(PDAnnotation annotation) {
288289
* <li>No attachments.</li>
289290
* <li>No Javascript code.</li>
290291
* <li>No links using action of type URI/Launch/RemoteGoTo/ImportData.</li>
292+
* <li>No XFA forms in order to prevent exposure to XXE/SSRF like CVE-2025-54988.</li>
291293
* </ul>
292294
*
293295
* @param pdfFilePath Filename of the PDF file to check.
@@ -297,6 +299,9 @@ public boolean accept(PDAnnotation annotation) {
297299
* @see "https://github.com/jonaslejon/malicious-pdf"
298300
* @see "https://pdfbox.apache.org/"
299301
* @see "https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox"
302+
* @see "https://nvd.nist.gov/vuln/detail/CVE-2025-54988"
303+
* @see "https://github.com/mgthuramoemyint/POC-CVE-2025-54988"
304+
* @see "https://en.wikipedia.org/wiki/XFA"
300305
*/
301306
public static boolean isPDFSafe(String pdfFilePath) {
302307
boolean isSafe = false;
@@ -309,33 +314,38 @@ public static boolean isPDFSafe(String pdfFilePath) {
309314
PDDocumentCatalog documentCatalog = document.getDocumentCatalog();
310315
PDDocumentNameDictionary namesDictionary = new PDDocumentNameDictionary(documentCatalog);
311316
if (namesDictionary.getEmbeddedFiles() == null) {
312-
//Step 3: Check if the file contains Javascript code, in our case is not allowed
313-
if (namesDictionary.getJavaScript() == null) {
314-
//Step 4: Check if the file contains links using action of type URI/Launch/RemoteGoTo/ImportData, in our case is not allowed
315-
final List<Integer> notAllowedAnnotationCounterList = new ArrayList<>();
316-
AnnotationFilter notAllowedAnnotationFilter = new AnnotationFilter() {
317-
@Override
318-
public boolean accept(PDAnnotation annotation) {
319-
boolean keep = false;
320-
if (annotation instanceof PDAnnotationLink) {
321-
PDAnnotationLink link = (PDAnnotationLink) annotation;
322-
PDAction action = link.getAction();
323-
if ((action instanceof PDActionURI) || (action instanceof PDActionLaunch) || (action instanceof PDActionRemoteGoTo) || (action instanceof PDActionImportData)) {
324-
keep = true;
317+
//Step 3: Check if the file contains any XFA forms
318+
PDAcroForm acroForm = documentCatalog.getAcroForm();
319+
boolean hasForm = (acroForm != null && acroForm.getXFA() != null);
320+
if (!hasForm) {
321+
//Step 4: Check if the file contains Javascript code, in our case is not allowed
322+
if (namesDictionary.getJavaScript() == null) {
323+
//Step 5: Check if the file contains links using action of type URI/Launch/RemoteGoTo/ImportData, in our case is not allowed
324+
final List<Integer> notAllowedAnnotationCounterList = new ArrayList<>();
325+
AnnotationFilter notAllowedAnnotationFilter = new AnnotationFilter() {
326+
@Override
327+
public boolean accept(PDAnnotation annotation) {
328+
boolean keep = false;
329+
if (annotation instanceof PDAnnotationLink) {
330+
PDAnnotationLink link = (PDAnnotationLink) annotation;
331+
PDAction action = link.getAction();
332+
if ((action instanceof PDActionURI) || (action instanceof PDActionLaunch) || (action instanceof PDActionRemoteGoTo) || (action instanceof PDActionImportData)) {
333+
keep = true;
334+
}
325335
}
336+
return keep;
326337
}
327-
return keep;
328-
}
329-
};
330-
documentCatalog.getPages().forEach(page -> {
331-
try {
332-
notAllowedAnnotationCounterList.add(page.getAnnotations(notAllowedAnnotationFilter).size());
333-
} catch (IOException e) {
334-
throw new RuntimeException(e);
338+
};
339+
documentCatalog.getPages().forEach(page -> {
340+
try {
341+
notAllowedAnnotationCounterList.add(page.getAnnotations(notAllowedAnnotationFilter).size());
342+
} catch (IOException e) {
343+
throw new RuntimeException(e);
344+
}
345+
});
346+
if (notAllowedAnnotationCounterList.stream().reduce(0, Integer::sum) == 0) {
347+
isSafe = true;
335348
}
336-
});
337-
if (notAllowedAnnotationCounterList.stream().reduce(0, Integer::sum) == 0) {
338-
isSafe = true;
339349
}
340350
}
341351
}

src/test/java/eu/righettod/TestSecurityUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ public void isRelativeURL() {
168168

169169
@Test
170170
public void isPDFSafe() {
171-
List<String> unsafeFileList = Arrays.asList("test-putty.exe.pdf", "test-pdf-with-link-to-malicious-file.pdf", "test-dynamic-doc.pdf", "test-pdf-with-js-code.pdf");
171+
List<String> unsafeFileList = Arrays.asList("test-putty.exe.pdf", "test-pdf-with-link-to-malicious-file.pdf", "test-dynamic-doc.pdf", "test-pdf-with-js-code.pdf", "test-pdf-with-xxe-in-xfa-form.pdf");
172172
unsafeFileList.forEach(f -> {
173173
String testFile = getTestFilePath(f);
174174
assertFalse(SecurityUtils.isPDFSafe(testFile), String.format(TEMPLATE_MESSAGE_FALSE_NEGATIVE_FOR_FILE, testFile));
1012 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)