【PDF-XSS攻击】springboot项目-上传文件-解决PDF文件XSS攻击

问题:

1、pom.xml

java 复制代码
         <dependency>
                <groupId>org.apache.pdfbox</groupId>
                <artifactId>pdfbox</artifactId>
                <version>2.0.34</version>
            </dependency>

2、PdfUtils.java

java 复制代码
package com.hf.common.utils;

import com.hf.common.core.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPageTree;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.stream.IntStream;

@Slf4j
public class PdfUtils {

    /**
     * 校验pdf文件是否包含js脚本
     **/
    public static boolean containsJavaScript(File file) {
        PDDocument document = null;
        try {
            document = PDDocument.load(file);
        } catch (IOException e) {
            throw new BusinessException("检测pdf文件脚本失败");
        }
        return containsJavaScript(document);
    }

    /**
     * 通过流的方式校验pdf文件是否包含js脚本
     * 校验pdf文件是否包含js脚本
     **/
    public static boolean containsJavaScript(InputStream input) {
        PDDocument document = null;
        try {
            document = PDDocument.load(input);
        } catch (IOException e) {
            throw new BusinessException("检测pdf文件脚本失败");
        }
        return containsJavaScript(document);
    }

    /**
     * 文档的方式校验pdf文件是否包含js脚本
     */
    private static boolean containsJavaScript(PDDocument document) {
        if (document.getDocument().getTrailer().toString().contains("COSName{JS}")
                || document.getDocument().getTrailer().toString().contains("COSName{JavaScript}")) {
            return true;
        }
        PDPageTree pages = document.getPages();
        return IntStream.range(0, pages.getCount()).anyMatch(i -> {
            String pageContent = pages.get(i).getCOSObject().toString();
            return pageContent.contains("COSName{JS}") || pageContent.contains("COSName{JavaScript}");
        });
    }

    /**
     * 增强版PDF安全检查,检测空PDF和可能的恶意内容
     *
     * @param file PDF文件
     * @return 是否安全
     */
    public static boolean isSafePdf(File file) {
        PDDocument document = null;
        try {
            document = PDDocument.load(file);

            // 检查文件大小是否异常小
//            if (file.length() < 1024) { // 小于1KB的PDF文件可能有问题
//                log.error("PDF文件大小异常小: {} bytes", file.length());
//                return false;
//            }

            // 检查页面数量
            if (document.getNumberOfPages() == 0) {
                log.error("PDF文件没有页面");
                return false; // 空PDF文件
            }

            // 检查是否包含JavaScript
            if (containsJavaScript(document)) {
                log.error("PDF文件包含JavaScript");
                return false;
            }

            // 检查文档结构中的可疑元素
            String trailerString = document.getDocument().getTrailer().toString().toLowerCase();
            if (trailerString.contains("/js") ||
                    trailerString.contains("/javascript") ||
                    trailerString.contains("/action") ||
                    trailerString.contains("/launch") ||
                    trailerString.contains("/submitform") ||
                    trailerString.contains("/openaction")) {
                log.error("PDF文件包含可疑元素: {}", trailerString);
                return false;
            }

            return true;
        } catch (Exception e) {
            // 如果解析出错,认为文件不安全
            log.error("PDF文件解析失败", e);
            return false;
        } finally {
            if (document != null) {
                try {
                    document.close();
                } catch (IOException e) {
                    // 忽略关闭错误
                }
            }
        }
    }
}

3、serviceimpl

java 复制代码
 @Override
    public ObjectFile upload(MultipartFile file, String user) throws Exception {
        if (file == null || file.isEmpty()) {
            throw new BusinessException("上传文件为空");
        }

        String fileName = file.getOriginalFilename();
        String contentTypes = file.getContentType();
        // 检查PDF文件安全性
        if (fileName != null && fileName.toLowerCase().endsWith(".pdf")) {
            File tempFile = File.createTempFile("upload_", ".pdf");
            try {
                FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(tempFile));
                // 使用增强的PDF安全检查
                if (!PdfUtils.isSafePdf(tempFile)) {
                    throw new BusinessException("检测到不安全的PDF文件,上传失败");
                }
            } catch (IOException e) {
                throw new BusinessException("PDF文件安全检查失败: " + e.getMessage());
            } finally {
                tempFile.delete();
            }
        }
//上传minio服务器
  Map<String, String> map = minioService.uploadFile(file.getInputStream(), file.getSize(), fileName, contentTypes);
        if (MapUtil.isEmpty(map)) {
            throw new BusinessException("上传文件失败");
        }
     ObjectFile objectFile = new ObjectFile ();
        objectFile .setFileType(map.get("fileType"));
        objectFile .setNameOld(map.get("nameOld"));
        objectFile .setNameNew(map.get("nameNew"));
        objectFile .setPath(map.get("path"));
        objectFile .setInternalPath(map.get("internalPath"));
        if (StrUtil.isNotEmpty(user)) {
            objectFile .setCreateId(user);
        }

        iObjectFileService.save(objectFile );

        return objectFile ;
    }

使用python 生成的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("xss.pdf", "wb") as f:
    output_pdf.write(f)

文件2:生成带有内容的xss文件

python 复制代码
from PyPDF2 import PdfReader, PdfWriter
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from io import BytesIO

# 第一步:用 reportlab 创建带有文本的 PDF 页面
packet = BytesIO()
can = canvas.Canvas(packet, pagesize=letter)
can.drawString(100, 750, "123")  # 添加你要写入的文本
can.save()
packet.seek(0)

# 第二步:读取该页面并创建新的 PDF
new_pdf = PdfReader(packet)
output_pdf = PdfWriter()

# 添加页面(带文字)
output_pdf.add_page(new_pdf.pages[0])

# 添加 JavaScript(XSS 示例)
output_pdf.add_js("app.alert('xss');")

# 第三步:写入最终 PDF 文件
with open("xss23.pdf", "wb") as f:
    output_pdf.write(f)
相关推荐
zs宝来了6 分钟前
Spring Boot 内嵌 Tomcat 原理:Tomcat ServletWebServerFactory 源码解析
spring boot·tomcat·内嵌容器·webserverfactory
dd向上19 分钟前
【计算机毕设/课设】在职全栈开发工程师接单:Java(SpringBoot+Vue)/小程序/C++(Qt/MFC) 定制与辅导
java·spring boot·课程设计
优化控制仿真模型36 分钟前
【26专四】英语专业四级TEM4历年真题及答案解析电子版PDF(2009-2025年)
经验分享·pdf
AmyLin_200139 分钟前
【pdf2md-2:关键核心】PDF 转 Markdown 技术拆解:两阶段流水线、四级标题检测与段落智能合并
windows·python·pdf·pip·pdf2md
码农很忙1 小时前
Spring Boot 3.x 整合 Redis 实现高性能缓存的完整指南
java·spring boot·redis
DROm RAPS1 小时前
springboot整合libreoffice(两种方式,使用本地和远程的libreoffice);docker中同时部署应用和libreoffice
spring boot·后端·docker
快乐柠檬不快乐1 小时前
基于Java+SpringBoot+SSM攻防靶场实验室平台
java·开发语言·spring boot
爱丽_1 小时前
Spring Boot 启动链路:自动装配、条件注解与排错方法论
java·spring boot·后端
霸道流氓气质1 小时前
Springboot集成Kafka入门流程及示例代码
spring boot·kafka
weixin_425023001 小时前
Spring Boot 2.7+JDK8+WebSocket对接阿里云百炼Qwen3.5-Plus 实现流式对话+思考过程实时展示
java·spring boot·websocket·ai编程