前言:
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次)
- 潜在嵌套结构 (无限递归深度)
脚本检测:
- 检测到JS项的PDF对象:直接移除
- 检测到A/AA项中的JS动作:移除整个动作项
- 检测到Names中的JS:移除整个JS子字典
资源清理:
- 移除后的空容器会被清理
- 只移除脚本相关项,保留其他内容
对应的执行完成的和开始的对比
统一为1.6版本的pdf格式
如此便可以去除pdf文件中的js脚本代码,后续最好再head中添加
CSP和X-Content-Type-Options: nosniff
可以进一步防御xss攻击