PDF-XSS

发布于:2025-06-25 ⋅ 阅读:(15) ⋅ 点赞:(0)

前言:

PDF文件是一种复杂的文档格式,由一系列对象组成,包括字体、图像、页面内容等。PDF文件支持嵌入JavaScript代码,这使得PDF文件不仅可以显示静态内容,还可以执行动态操作。这种特性被攻击者利用来嵌入恶意脚本代码。

攻击者通过在PDF文件中嵌入恶意JavaScript代码,使得当用户打开该PDF文件时,恶意代码会在用户的浏览器或PDF阅读器中执行。

pdf-xss制作:

python代码如下:

from PyPDF2 import PdfReader, PdfWriter
# 创建一个新的 PDF 文档
output_pdf = PdfWriter()
# 添加一个新页面
page = output_pdf.add_blank_page(width=72, height=72)
# 添加js代码
output_pdf.add_js("app.alert('xss');")
# 将新页面写入到新 PDF 文档中
with open("alert.pdf", "wb") as f:
    output_pdf.write(f)

执行后会将脚本植入到pdf中,对应的pdf分析

 

当我们打开对应的pdf文件,可以看到弹出了对应的xss对话框

修复:

代码修复也很简单,就是要处理JavaScript方法,对应的处理的点如下

  • 识别并移除/Names/JavaScript结构
  • 处理嵌套字典中的JavaScript
  • 处理数组中的JavaScript动作
  • 递归检查所有可能的位置

对应的java代码如下

package org.example;

import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;

import java.io.File;
import java.io.IOException;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        String inputFile = "alert.pdf";
        String outputFile = "safe-output.pdf";

        try {
            removeJavaScript(inputFile, outputFile);
            System.out.println("PDF中的JavaScript已彻底移除!");
        } catch (IOException e) {
            System.err.println("处理失败: " + e.getMessage());
        }
    }

    public static void removeJavaScript(String inputPath, String outputPath) throws IOException {
        PDDocument document = Loader.loadPDF(new File(inputPath));

        try {
            // 1. 处理文档级JavaScript
            processDocumentLevelJS(document);

            // 2. 处理页面级JavaScript
            processPagesJS(document);

            // 3. 保存处理后的文档
            document.save(outputPath);
        } finally {
            document.close();
        }
    }

    // 处理文档级的JavaScript
    private static void processDocumentLevelJS(PDDocument document) {
        // 获取文档目录
        PDDocumentCatalog catalog = document.getDocumentCatalog();
        COSDictionary catalogDict = catalog.getCOSObject();

        // 处理文档开放动作
        catalog.setOpenAction(null);

        // 专门处理Names字典中的JavaScript
        processNamesDictionaryJS(catalogDict);

        // 移除标准JS和AA
        removeDictJavaScript(catalogDict);
    }

    // 专门处理Names字典中的JavaScript
    private static void processNamesDictionaryJS(COSDictionary dict) {
        if (dict.containsKey(COSName.NAMES)) {
            COSDictionary namesDict = dict.getCOSDictionary(COSName.NAMES);
            if (namesDict != null) {
                // 检查JavaScript字典
                if (namesDict.containsKey(COSName.JAVA_SCRIPT)) {
                    namesDict.removeItem(COSName.JAVA_SCRIPT);
                    System.out.println("已移除文档级的Named JavaScript字典");

                    // 如果Names字典变空(即没有其他条目了),则移除整个Names字典
                    if (namesDict.size() == 0) { // 修改这里:使用size()==0代替isEmpty()
                        dict.removeItem(COSName.NAMES);
                    }
                }
            }
        }
    }

    // 处理页面级JavaScript
    private static void processPagesJS(PDDocument document) throws IOException {
        for (PDPage page : document.getPages()) {
            // 处理页面级JavaScript
            COSDictionary pageDict = page.getCOSObject();
            removeDictJavaScript(pageDict);

            // 处理页面注释
            List<PDAnnotation> annotations = page.getAnnotations();
            for (PDAnnotation annotation : annotations) {
                removeAnnotationJavaScript(annotation);
            }
        }
    }

    // 移除注释中的JavaScript
    private static void removeAnnotationJavaScript(PDAnnotation annotation) {
        COSDictionary dict = annotation.getCOSObject();
        removeDictJavaScript(dict);
    }

    // 核心方法:移除COSDictionary中的JavaScript
    private static void removeDictJavaScript(COSDictionary dict) {
        // 1. 直接移除所有JavaScript项
        dict.removeItem(COSName.JS);

        // 2. 检查并移除标准动作中的JavaScript
        if (dict.containsKey(COSName.A)) {
            COSDictionary actionDict = dict.getCOSDictionary(COSName.A);
            if (isJavaScriptAction(actionDict)) {
                dict.removeItem(COSName.A);
            }
        }

        // 3. 检查并移除附加动作中的JavaScript
        if (dict.containsKey(COSName.AA)) {
            COSDictionary aaDict = dict.getCOSDictionary(COSName.AA);
            removeAllJavaScriptFromDict(aaDict);
        }

        // 4. 处理Names字典(针对嵌套的Names字典)
        processNamesDictionaryJS(dict);
    }

    // 检查是否为JavaScript动作
    private static boolean isJavaScriptAction(COSDictionary actionDict) {
        // JavaScript动作的标识:/S 为 /JavaScript
        return COSName.JAVA_SCRIPT.equals(actionDict.getCOSName(COSName.S)) ||
                actionDict.containsKey(COSName.JS);
    }

    // 彻底清除字典中的所有JavaScript
    private static void removeAllJavaScriptFromDict(COSDictionary dict) {
        // 遍历所有条目
        for (COSName key : dict.keySet().toArray(new COSName[0])) {
            COSBase value = dict.getItem(key);

            // 1. 直接移除JavaScript项
            if (value == COSName.JS || key == COSName.JS) {
                dict.removeItem(key);
            }
            // 2. 处理嵌套的JavaScript字典
            else if (value instanceof COSDictionary) {
                COSDictionary subDict = (COSDictionary) value;
                if (isJavaScriptAction(subDict)) {
                    dict.removeItem(key);
                } else {
                    // 递归处理嵌套字典
                    removeAllJavaScriptFromDict(subDict);
                }
            }
            // 3. 处理数组中的JavaScript
            else if (value instanceof COSArray) {
                processArrayForJavaScript((COSArray) value);
            }
        }
    }

    // 处理数组中的JavaScript
    private static void processArrayForJavaScript(COSArray array) {
        for (int i = 0; i < array.size(); i++) {
            COSBase element = array.get(i);

            if (element instanceof COSDictionary) {
                COSDictionary dict = (COSDictionary) element;
                if (isJavaScriptAction(dict)) {
                    array.remove(i); // 从数组中移除JavaScript字典
                    i--; // 调整索引
                }
            }
        }
    }
}

上述代码具体的执行逻辑如下: 

  1. 完整遍历​:

    • 文档级 (1次)
    • 页面级 (每页1次)
    • 注释级 (每个注释1次)
    • 潜在嵌套结构 (无限递归深度)
  2. 脚本检测​:

    • 检测到JS项的PDF对象:直接移除
    • 检测到A/AA项中的JS动作:移除整个动作项
    • 检测到Names中的JS:移除整个JS子字典
  3. 资源清理​:

    • 移除后的空容器会被清理
    • 只移除脚本相关项,保留其他内容

对应的执行完成的和开始的对比

 统一为1.6版本的pdf格式

如此便可以去除pdf文件中的js脚本代码,后续最好再head中添加

CSP和X-Content-Type-Options:  nosniff

可以进一步防御xss攻击


网站公告

今日签到

点亮在社区的每一天
去签到