Apache Struts: File upload component had a directory traversal vulnerability
Description
An attacker can manipulate file upload params to enable paths traversal and under some circumstances this can lead to uploading a malicious file which can be used to perform Remote Code Execution. Users are recommended to upgrade to versions Struts 2.5.33 or Struts 6.3.0.2 or greater to fix this issue.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Apache Struts file upload path traversal flaw allows remote code execution; upgrade to Struts 2.5.33 or 6.3.0.2+.
CVE-2023-50164 is a vulnerability in the Apache Struts web framework that stems from improper handling of file upload parameters. An attacker can manipulate these parameters to perform path traversal, bypassing intended directory restrictions. This weakness is rooted in how the framework's HttpParameters class manages parameter keys in a case-insensitive manner; changes introduced to address the flaw (commits d8c6969 and 162e29f) modify the remove(), contains(), and appendAll() methods to handle casing consistently, preventing attackers from using variant-cased parameter names to evade sanitization [3][4].
Exploitation does not require authentication and can be performed over the network. By crafting a malicious file upload request with specially crafted parameter names or values that traverse up the directory tree, an attacker can write a malicious file (such as a JSP web shell) to an arbitrary location on the server filesystem. The official description confirms that under some circumstances this can lead to uploading a malicious file which can then be used to achieve remote code execution [1].
Successful exploitation gives the attacker the ability to execute arbitrary commands on the target server within the context of the web application, potentially leading to full system compromise. The vulnerability has a high CVSS score, reflecting its ease of exploitation and critical impact [1].
Apache Struts has addressed the vulnerability in versions 2.5.33 and 6.3.0.2 and later. Users are strongly advised to upgrade to these versions or later immediately [1]. No public exploitation reports have been confirmed as of this writing, but given the history of Struts vulnerabilities being targeted by attackers, patching should be prioritized.
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.struts:struts2-coreMaven | >= 2.0.0, < 2.5.33 | 2.5.33 |
org.apache.struts:struts2-coreMaven | >= 6.0.0, < 6.3.0.2 | 6.3.0.2 |
Affected products
2- Apache Software Foundation/Apache Strutsv5Range: 2.0.0
Patches
2162e29fee913Makes HttpParameters case-insensitive
2 files changed · +104 −8
core/src/main/java/org/apache/struts2/dispatcher/HttpParameters.java+39 −8 modified@@ -25,20 +25,22 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; @SuppressWarnings("unchecked") -public class HttpParameters implements Map<String, Parameter>, Cloneable { +public class HttpParameters implements Map<String, Parameter> { private Map<String, Parameter> parameters; private HttpParameters(Map<String, Parameter> parameters) { this.parameters = parameters; } + @SuppressWarnings("rawtypes") public static Builder create(Map requestParameterMap) { return new Builder(requestParameterMap); } @@ -49,7 +51,15 @@ public static Builder create() { public HttpParameters remove(Set<String> paramsToRemove) { for (String paramName : paramsToRemove) { - parameters.remove(paramName); + String paramNameLowerCase = paramName.toLowerCase(); + Iterator<Entry<String, Parameter>> iterator = parameters.entrySet().iterator(); + + while (iterator.hasNext()) { + Map.Entry<String, Parameter> entry = iterator.next(); + if (entry.getKey().equalsIgnoreCase(paramNameLowerCase)) { + iterator.remove(); + } + } } return this; } @@ -61,7 +71,17 @@ public HttpParameters remove(final String paramToRemove) { } public boolean contains(String name) { - return parameters.containsKey(name); + boolean found = false; + String nameLowerCase = name.toLowerCase(); + + for (String key : parameters.keySet()) { + if (key.equalsIgnoreCase(nameLowerCase)) { + found = true; + break; + } + } + + return found; } /** @@ -78,7 +98,14 @@ public Map<String, String[]> toMap() { return result; } + /** + * Appends all the parameters by overriding any existing params in a case-insensitive manner + * + * @param newParams A new params to append + * @return a current instance of {@link HttpParameters} + */ public HttpParameters appendAll(Map<String, Parameter> newParams) { + remove(newParams.keySet()); parameters.putAll(newParams); return this; } @@ -109,11 +136,15 @@ public boolean containsValue(Object value) { @Override public Parameter get(Object key) { - if (parameters.containsKey(key)) { - return parameters.get(key); - } else { - return new Parameter.Empty(String.valueOf(key)); + if (key != null && contains(String.valueOf(key))) { + String keyString = String.valueOf(key).toLowerCase(); + for (Map.Entry<String, Parameter> entry : parameters.entrySet()) { + if (entry.getKey() != null && entry.getKey().equalsIgnoreCase(keyString)) { + return entry.getValue(); + } + } } + return new Parameter.Empty(String.valueOf(key)); } @Override @@ -206,7 +237,7 @@ public HttpParameters build() { * Alternate Builder method which avoids wrapping any parameters that are already * a {@link Parameter} element within another {@link Parameter} wrapper. * - * @return + * @return */ public HttpParameters buildNoNestedWrapping() { Map<String, Parameter> parameters = (parent == null)
core/src/test/java/org/apache/struts2/dispatcher/HttpParametersTest.java+65 −0 added@@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.dispatcher; + +import org.junit.Test; + +import java.util.HashMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class HttpParametersTest { + + @Test + public void shouldGetBeCaseInsensitive() { + // given + HttpParameters params = HttpParameters.create(new HashMap<String, Object>() {{ + put("param1", "value1"); + }}).build(); + + // then + assertEquals("value1", params.get("Param1").getValue()); + assertEquals("value1", params.get("paraM1").getValue()); + assertEquals("value1", params.get("pAraM1").getValue()); + } + + @Test + public void shouldAppendSameParamsIgnoringCase() { + // given + HttpParameters params = HttpParameters.create(new HashMap<String, Object>() {{ + put("param1", "value1"); + }}).build(); + + // when + assertEquals("value1", params.get("param1").getValue()); + + params = params.appendAll(HttpParameters.create(new HashMap<String, String>() {{ + put("Param1", "Value1"); + }}).build()); + + // then + assertTrue(params.contains("param1")); + assertTrue(params.contains("Param1")); + + assertEquals("Value1", params.get("param1").getValue()); + assertEquals("Value1", params.get("Param1").getValue()); + } + +} \ No newline at end of file
d8c69691ef1dMakes HttpParameters case-insensitive
2 files changed · +88 −8
core/src/main/java/org/apache/struts2/dispatcher/HttpParameters.java+23 −8 modified@@ -29,14 +29,15 @@ import java.util.TreeSet; @SuppressWarnings("unchecked") -public class HttpParameters implements Map<String, Parameter>, Cloneable { +public class HttpParameters implements Map<String, Parameter> { final private Map<String, Parameter> parameters; private HttpParameters(Map<String, Parameter> parameters) { this.parameters = parameters; } + @SuppressWarnings("rawtypes") public static Builder create(Map requestParameterMap) { return new Builder(requestParameterMap); } @@ -47,7 +48,7 @@ public static Builder create() { public HttpParameters remove(Set<String> paramsToRemove) { for (String paramName : paramsToRemove) { - parameters.remove(paramName); + parameters.entrySet().removeIf(p -> p.getKey().equalsIgnoreCase(paramName)); } return this; } @@ -59,12 +60,15 @@ public HttpParameters remove(final String paramToRemove) { } public boolean contains(String name) { - return parameters.containsKey(name); + return parameters.keySet().stream().anyMatch(p -> p.equalsIgnoreCase(name)); } /** * Access to this method can be potentially dangerous as it allows access to raw parameter values. + * + * @deprecated since 6.4.0, it will be removed with a new major release */ + @Deprecated private Map<String, String[]> toMap() { final Map<String, String[]> result = new HashMap<>(parameters.size()); for (Map.Entry<String, Parameter> entry : parameters.entrySet()) { @@ -73,7 +77,14 @@ private Map<String, String[]> toMap() { return result; } + /** + * Appends all the parameters by overriding any existing params in a case-insensitive manner + * + * @param newParams A new params to append + * @return a current instance of {@link HttpParameters} + */ public HttpParameters appendAll(Map<String, Parameter> newParams) { + remove(newParams.keySet()); parameters.putAll(newParams); return this; } @@ -100,8 +111,11 @@ public boolean containsValue(Object value) { @Override public Parameter get(Object key) { - if (parameters.containsKey(key)) { - return parameters.get(key); + if (key != null && contains(String.valueOf(key))) { + return parameters.entrySet().stream() + .filter(p -> p.getKey().equalsIgnoreCase(String.valueOf(key))) + .findFirst().map(Entry::getValue) + .orElse(new Parameter.Empty(String.valueOf(key))); } else { return new Parameter.Empty(String.valueOf(key)); } @@ -177,8 +191,8 @@ public Builder withComparator(Comparator<String> orderedComparator) { public HttpParameters build() { Map<String, Parameter> parameters = (parent == null) - ? new HashMap<>() - : new HashMap<>(parent.parameters); + ? new HashMap<>() + : new HashMap<>(parent.parameters); for (Map.Entry<String, Object> entry : requestParameterMap.entrySet()) { String name = entry.getKey(); @@ -197,8 +211,9 @@ public HttpParameters build() { * Alternate Builder method which avoids wrapping any parameters that are already * a {@link Parameter} element within another {@link Parameter} wrapper. * - * @return + * @deprecated since 6.4.0, use {@link #build()} instead */ + @Deprecated public HttpParameters buildNoNestedWrapping() { Map<String, Parameter> parameters = (parent == null) ? new HashMap<>()
core/src/test/java/org/apache/struts2/dispatcher/HttpParametersTest.java+65 −0 added@@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.dispatcher; + +import org.junit.Test; + +import java.util.HashMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class HttpParametersTest { + + @Test + public void shouldGetBeCaseInsensitive() { + // given + HttpParameters params = HttpParameters.create(new HashMap<String, Object>() {{ + put("param1", "value1"); + }}).build(); + + // then + assertEquals("value1", params.get("Param1").getValue()); + assertEquals("value1", params.get("paraM1").getValue()); + assertEquals("value1", params.get("pAraM1").getValue()); + } + + @Test + public void shouldAppendSameParamsIgnoringCase() { + // given + HttpParameters params = HttpParameters.create(new HashMap<String, Object>() {{ + put("param1", "value1"); + }}).build(); + + // when + assertEquals("value1", params.get("param1").getValue()); + + params = params.appendAll(HttpParameters.create(new HashMap<String, String>() {{ + put("Param1", "Value1"); + }}).build()); + + // then + assertTrue(params.contains("param1")); + assertTrue(params.contains("Param1")); + + assertEquals("Value1", params.get("param1").getValue()); + assertEquals("Value1", params.get("Param1").getValue()); + } + +} \ No newline at end of file
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
11- github.com/advisories/GHSA-2j39-qcjm-428wghsaADVISORY
- lists.apache.org/thread/yh09b3fkf6vz5d6jdgrlvmg60lfwtqhjghsavendor-advisorymailing-listWEB
- nvd.nist.gov/vuln/detail/CVE-2023-50164ghsaADVISORY
- packetstormsecurity.com/files/176157/Struts-S2-066-File-Upload-Remote-Code-Execution.htmlghsaWEB
- www.openwall.com/lists/oss-security/2023/12/07/1ghsaWEB
- cwiki.apache.org/confluence/display/WW/S2-066ghsaWEB
- github.com/apache/struts/commit/162e29fee9136f4bfd9b2376da2cbf590f9ea163ghsaWEB
- github.com/apache/struts/commit/d8c69691ef1d15e76a5f4fcf33039316da2340b6ghsaWEB
- security.netapp.com/advisory/ntap-20231214-0010ghsaWEB
- www.openwall.com/lists/oss-security/2023/12/07/1ghsaWEB
- security.netapp.com/advisory/ntap-20231214-0010/mitre
News mentions
0No linked articles in our index yet.