1919import org .apache .pdfbox .pdmodel .interactive .annotation .AnnotationFilter ;
2020import org .apache .pdfbox .pdmodel .interactive .annotation .PDAnnotation ;
2121import org .apache .pdfbox .pdmodel .interactive .annotation .PDAnnotationLink ;
22+ import org .apache .pdfbox .pdmodel .interactive .form .PDAcroForm ;
2223import org .apache .poi .poifs .filesystem .DirectoryEntry ;
2324import org .apache .poi .poifs .filesystem .POIFSFileSystem ;
2425import 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 }
0 commit comments