前言:
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. 處理文檔級JavaScriptprocessDocumentLevelJS(document);// 2. 處理頁面級JavaScriptprocessPagesJS(document);// 3. 保存處理后的文檔document.save(outputPath);} finally {document.close();}}// 處理文檔級的JavaScriptprivate static void processDocumentLevelJS(PDDocument document) {// 獲取文檔目錄PDDocumentCatalog catalog = document.getDocumentCatalog();COSDictionary catalogDict = catalog.getCOSObject();// 處理文檔開放動作catalog.setOpenAction(null);// 專門處理Names字典中的JavaScriptprocessNamesDictionaryJS(catalogDict);// 移除標準JS和AAremoveDictJavaScript(catalogDict);}// 專門處理Names字典中的JavaScriptprivate 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);}}}}}// 處理頁面級JavaScriptprivate static void processPagesJS(PDDocument document) throws IOException {for (PDPage page : document.getPages()) {// 處理頁面級JavaScriptCOSDictionary pageDict = page.getCOSObject();removeDictJavaScript(pageDict);// 處理頁面注釋List<PDAnnotation> annotations = page.getAnnotations();for (PDAnnotation annotation : annotations) {removeAnnotationJavaScript(annotation);}}}// 移除注釋中的JavaScriptprivate static void removeAnnotationJavaScript(PDAnnotation annotation) {COSDictionary dict = annotation.getCOSObject();removeDictJavaScript(dict);}// 核心方法:移除COSDictionary中的JavaScriptprivate static void removeDictJavaScript(COSDictionary dict) {// 1. 直接移除所有JavaScript項dict.removeItem(COSName.JS);// 2. 檢查并移除標準動作中的JavaScriptif (dict.containsKey(COSName.A)) {COSDictionary actionDict = dict.getCOSDictionary(COSName.A);if (isJavaScriptAction(actionDict)) {dict.removeItem(COSName.A);}}// 3. 檢查并移除附加動作中的JavaScriptif (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 為 /JavaScriptreturn COSName.JAVA_SCRIPT.equals(actionDict.getCOSName(COSName.S)) ||actionDict.containsKey(COSName.JS);}// 徹底清除字典中的所有JavaScriptprivate 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. 處理數組中的JavaScriptelse if (value instanceof COSArray) {processArrayForJavaScript((COSArray) value);}}}// 處理數組中的JavaScriptprivate 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次)
- 潛在嵌套結構 (無限遞歸深度)
-
?腳本檢測?:
- 檢測到JS項的PDF對象:直接移除
- 檢測到A/AA項中的JS動作:移除整個動作項
- 檢測到Names中的JS:移除整個JS子字典
-
?資源清理?:
- 移除后的空容器會被清理
- 只移除腳本相關項,保留其他內容
對應的執行完成的和開始的對比
?統一為1.6版本的pdf格式
如此便可以去除pdf文件中的js腳本代碼,后續最好再head中添加
CSP和X-Content-Type-Options: ?nosniff
可以進一步防御xss攻擊