High severityCISA KEVNVD Advisory· Published Aug 22, 2018· Updated Oct 21, 2025
CVE-2018-11776
CVE-2018-11776
Description
Apache Struts versions 2.3 to 2.3.34 and 2.5 to 2.5.16 suffer from possible Remote Code Execution when alwaysSelectFullNamespace is true (either by user or a plugin like Convention Plugin) and then: results are used with no namespace and in same time, its upper package have no or wildcard namespace and similar to results, same possibility when using url tag which doesn't have value and action set and in same time, its upper package have no or wildcard namespace.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.struts:struts2-coreMaven | >= 2.0.4, < 2.3.35 | 2.3.35 |
org.apache.struts:struts2-coreMaven | >= 2.5, < 2.5.17 | 2.5.17 |
Affected products
1- Apache Software Foundation/Apache Strutsv5Range: 2.3 to 2.3.34
Patches
16e87474f9ad0Validates action, namespace and method in the same way
4 files changed · +150 −89
core/src/main/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapper.java+33 −2 modified@@ -31,7 +31,6 @@ import org.apache.struts2.RequestUtils; import org.apache.struts2.ServletActionContext; import org.apache.struts2.StrutsConstants; -import org.apache.struts2.StrutsException; import org.apache.struts2.util.PrefixTrie; import javax.servlet.http.HttpServletRequest; @@ -117,6 +116,10 @@ public class DefaultActionMapper implements ActionMapper { protected boolean allowSlashesInActionNames = false; protected boolean alwaysSelectFullNamespace = false; protected PrefixTrie prefixTrie = null; + + protected Pattern allowedNamespaceNames = Pattern.compile("[a-zA-Z0-9._/\\-]*"); + protected String defaultNamespaceName = "/"; + protected Pattern allowedActionNames = Pattern.compile("[a-zA-Z0-9._!/\\-]*"); protected String defaultActionName = "index"; @@ -202,6 +205,16 @@ public void setAlwaysSelectFullNamespace(String alwaysSelectFullNamespace) { this.alwaysSelectFullNamespace = BooleanUtils.toBoolean(alwaysSelectFullNamespace); } + @Inject(value = StrutsConstants.STRUTS_ALLOWED_NAMESPACE_NAMES, required = false) + public void setAllowedNamespaceNames(String allowedNamespaceNames) { + this.allowedNamespaceNames = Pattern.compile(allowedNamespaceNames); + } + + @Inject(value = StrutsConstants.STRUTS_DEFAULT_NAMESPACE_NAME, required = false) + public void setDefaultNamespaceName(String defaultNamespaceName) { + this.defaultNamespaceName = defaultNamespaceName; + } + @Inject(value = StrutsConstants.STRUTS_ALLOWED_ACTION_NAMES, required = false) public void setAllowedActionNames(String allowedActionNames) { this.allowedActionNames = Pattern.compile(allowedActionNames); @@ -389,10 +402,28 @@ protected void parseNameAndNamespace(String uri, ActionMapping mapping, Configur } } - mapping.setNamespace(namespace); + mapping.setNamespace(cleanupNamespaceName(namespace)); mapping.setName(cleanupActionName(name)); } + /** + * Checks namespace name against allowed pattern if not matched returns default namespace + * + * @param rawNamespace name extracted from URI + * @return safe namespace name + */ + protected String cleanupNamespaceName(final String rawNamespace) { + if (allowedNamespaceNames.matcher(rawNamespace).matches()) { + return rawNamespace; + } else { + LOG.warn( + "{} did not match allowed namespace names {} - default namespace {} will be used!", + rawNamespace, allowedActionNames, defaultActionName + ); + return defaultNamespaceName; + } + } + /** * Checks action name against allowed pattern if not matched returns default action name *
core/src/main/java/org/apache/struts2/StrutsConstants.java+5 −0 modified@@ -282,6 +282,11 @@ public final class StrutsConstants { public static final String STRUTS_EXPRESSION_PARSER = "struts.expression.parser"; + /** namespaces names' whitelist **/ + public static final String STRUTS_ALLOWED_NAMESPACE_NAMES = "struts.allowed.namespace.names"; + /** default namespace name to use when namespace didn't match the whitelist **/ + public static final String STRUTS_DEFAULT_NAMESPACE_NAME = "struts.default.namespace.name"; + /** actions names' whitelist **/ public static final String STRUTS_ALLOWED_ACTION_NAMES = "struts.allowed.action.names"; /** default action name to use when action didn't match the whitelist **/
core/src/test/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapperTest.java+111 −86 modified@@ -27,12 +27,12 @@ import com.opensymphony.xwork2.config.impl.DefaultConfiguration; import com.opensymphony.xwork2.inject.Container; import org.apache.struts2.ServletActionContext; -import org.apache.struts2.StrutsException; import org.apache.struts2.StrutsInternalTestCase; import org.apache.struts2.result.StrutsResultSupport; import org.apache.struts2.views.jsp.StrutsMockHttpServletRequest; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -65,7 +65,7 @@ public Configuration getConfiguration() { }; } - public void testGetMapping() throws Exception { + public void testGetMapping() { req.setupGetRequestURI("/my/namespace/actionName.action"); req.setupGetServletPath("/my/namespace/actionName.action"); req.setupGetAttribute(null); @@ -79,7 +79,7 @@ public void testGetMapping() throws Exception { assertNull(mapping.getMethod()); } - public void testGetMappingWithMethod() throws Exception { + public void testGetMappingWithMethod() { req.setupGetParameterMap(new HashMap()); req.setupGetRequestURI("/my/namespace/actionName!add.action"); req.setupGetServletPath("/my/namespace/actionName!add.action"); @@ -95,7 +95,7 @@ public void testGetMappingWithMethod() throws Exception { assertEquals("add", mapping.getMethod()); } - public void testGetMappingWithSlashedName() throws Exception { + public void testGetMappingWithSlashedName() { req.setupGetRequestURI("/my/foo/actionName.action"); req.setupGetServletPath("/my/foo/actionName.action"); @@ -111,7 +111,7 @@ public void testGetMappingWithSlashedName() throws Exception { assertNull(mapping.getMethod()); } - public void testGetMappingWithSlashedNameAtRootButNoSlashPackage() throws Exception { + public void testGetMappingWithSlashedNameAtRootButNoSlashPackage() { req.setupGetRequestURI("/foo/actionName.action"); req.setupGetServletPath("/foo/actionName.action"); @@ -127,7 +127,7 @@ public void testGetMappingWithSlashedNameAtRootButNoSlashPackage() throws Except assertNull(mapping.getMethod()); } - public void testGetMappingWithSlashedNameAtRoot() throws Exception { + public void testGetMappingWithSlashedNameAtRoot() { config = new DefaultConfiguration(); PackageConfig pkg = new PackageConfig.Builder("myns") .namespace("/my/namespace").build(); @@ -158,7 +158,7 @@ public Configuration getConfiguration() { - public void testGetMappingWithNamespaceSlash() throws Exception { + public void testGetMappingWithNamespaceSlash() { req.setupGetRequestURI("/my-hh/abc.action"); req.setupGetServletPath("/my-hh/abc.action"); @@ -181,7 +181,7 @@ public void testGetMappingWithNamespaceSlash() throws Exception { assertEquals("my-hh/abc", mapping.getName()); } - public void testGetMappingWithUnknownNamespace() throws Exception { + public void testGetMappingWithUnknownNamespace() { req.setupGetRequestURI("/bo/foo/actionName.action"); req.setupGetServletPath("/bo/foo/actionName.action"); req.setupGetAttribute(null); @@ -195,7 +195,7 @@ public void testGetMappingWithUnknownNamespace() throws Exception { assertNull(mapping.getMethod()); } - public void testGetMappingWithUnknownNamespaceButFullNamespaceSelect() throws Exception { + public void testGetMappingWithUnknownNamespaceButFullNamespaceSelect() { req.setupGetRequestURI("/bo/foo/actionName.action"); req.setupGetServletPath("/bo/foo/actionName.action"); req.setupGetAttribute(null); @@ -210,32 +210,32 @@ public void testGetMappingWithUnknownNamespaceButFullNamespaceSelect() throws Ex assertNull(mapping.getMethod()); } - public void testGetMappingWithActionName_methodAndName() throws Exception { + public void testGetMappingWithActionName_methodAndName() { DefaultActionMapper mapper = new DefaultActionMapper(); mapper.setAllowDynamicMethodCalls("true"); ActionMapping mapping = mapper.getMappingFromActionName("actionName!add"); assertEquals("actionName", mapping.getName()); assertEquals("add", mapping.getMethod()); } - public void testGetMappingWithActionName_name() throws Exception { + public void testGetMappingWithActionName_name() { DefaultActionMapper mapper = new DefaultActionMapper(); ActionMapping mapping = mapper.getMappingFromActionName("actionName"); assertEquals("actionName", mapping.getName()); - assertEquals(null, mapping.getMethod()); + assertNull(mapping.getMethod()); } - public void testGetMappingWithActionName_noDynamicMethod() throws Exception { + public void testGetMappingWithActionName_noDynamicMethod() { DefaultActionMapper mapper = new DefaultActionMapper(); mapper.setAllowDynamicMethodCalls("false"); ActionMapping mapping = mapper.getMappingFromActionName("actionName!add"); assertEquals("actionName!add", mapping.getName()); - assertEquals(null, mapping.getMethod()); + assertNull(mapping.getMethod()); } - public void testGetMappingWithActionName_noDynamicMethodColonPrefix() throws Exception { + public void testGetMappingWithActionName_noDynamicMethodColonPrefix() { - Map parameterMap = new HashMap(); + Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put(DefaultActionMapper.METHOD_PREFIX + "someMethod", ""); StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest(); @@ -247,16 +247,16 @@ public void testGetMappingWithActionName_noDynamicMethodColonPrefix() throws Exc ActionMapping actionMapping = defaultActionMapper.getMapping(request, configManager); assertEquals("someServletPath", actionMapping.getName()); - assertEquals(null, actionMapping.getMethod()); + assertNull(actionMapping.getMethod()); } - public void testGetMappingWithActionName_null() throws Exception { + public void testGetMappingWithActionName_null() { DefaultActionMapper mapper = new DefaultActionMapper(); ActionMapping mapping = mapper.getMappingFromActionName(null); assertNull(mapping); } - public void testGetUri() throws Exception { + public void testGetUri() { req.setupGetParameterMap(new HashMap()); req.setupGetRequestURI("/my/namespace/actionName.action"); req.setupGetServletPath("/my/namespace/actionName.action"); @@ -268,7 +268,7 @@ public void testGetUri() throws Exception { assertEquals("/my/namespace/actionName.action", mapper.getUriFromActionMapping(mapping)); } - public void testGetUriWithSemicolonPresent() throws Exception { + public void testGetUriWithSemicolonPresent() { req.setupGetParameterMap(new HashMap()); req.setupGetRequestURI("/my/namespace/actionName.action;abc=123rty56"); req.setupGetServletPath("/my/namespace/actionName.action;abc=123rty56"); @@ -280,7 +280,7 @@ public void testGetUriWithSemicolonPresent() throws Exception { assertEquals("/my/namespace/actionName.action", mapper.getUriFromActionMapping(mapping)); } - public void testGetUriWithMethod() throws Exception { + public void testGetUriWithMethod() { req.setupGetParameterMap(new HashMap()); req.setupGetRequestURI("/my/namespace/actionName!add.action"); req.setupGetServletPath("/my/namespace/actionName!add.action"); @@ -293,8 +293,8 @@ public void testGetUriWithMethod() throws Exception { assertEquals("/my/namespace/actionName!add.action", mapper.getUriFromActionMapping(mapping)); } - public void testGetUriWithOriginalExtension() throws Exception { - ActionMapping mapping = new ActionMapping("actionName", "/ns", null, new HashMap()); + public void testGetUriWithOriginalExtension() { + ActionMapping mapping = new ActionMapping("actionName", "/ns", null, new HashMap<String, Object>()); ActionMapping orig = new ActionMapping(); orig.setExtension("foo"); @@ -304,7 +304,7 @@ public void testGetUriWithOriginalExtension() throws Exception { assertEquals("/ns/actionName.foo", mapper.getUriFromActionMapping(mapping)); } - public void testGetMappingWithNoExtension() throws Exception { + public void testGetMappingWithNoExtension() { req.setupGetParameterMap(new HashMap()); req.setupGetRequestURI("/my/namespace/actionName"); req.setupGetServletPath("/my/namespace/actionName"); @@ -320,7 +320,7 @@ public void testGetMappingWithNoExtension() throws Exception { assertNull(mapping.getMethod()); } - public void testGetMappingWithNoExtensionButUriHasExtension() throws Exception { + public void testGetMappingWithNoExtensionButUriHasExtension() { req.setupGetParameterMap(new HashMap()); req.setupGetRequestURI("/my/namespace/actionName.html"); req.setupGetServletPath("/my/namespace/actionName.html"); @@ -340,7 +340,7 @@ public void testGetMappingWithNoExtensionButUriHasExtension() throws Exception { // === test name & namespace === // ============================= - public void testParseNameAndNamespace1() throws Exception { + public void testParseNameAndNamespace1() { ActionMapping actionMapping = new ActionMapping(); DefaultActionMapper defaultActionMapper = new DefaultActionMapper(); @@ -350,7 +350,7 @@ public void testParseNameAndNamespace1() throws Exception { assertEquals(actionMapping.getNamespace(), ""); } - public void testParseNameAndNamespace2() throws Exception { + public void testParseNameAndNamespace2() { ActionMapping actionMapping = new ActionMapping(); DefaultActionMapper defaultActionMapper = new DefaultActionMapper(); @@ -360,7 +360,7 @@ public void testParseNameAndNamespace2() throws Exception { assertEquals(actionMapping.getNamespace(), "/"); } - public void testParseNameAndNamespace3() throws Exception { + public void testParseNameAndNamespace3() { ActionMapping actionMapping = new ActionMapping(); DefaultActionMapper defaultActionMapper = new DefaultActionMapper(); @@ -370,7 +370,7 @@ public void testParseNameAndNamespace3() throws Exception { assertEquals(actionMapping.getNamespace(), "/my"); } - public void testParseNameAndNamespace_NoSlashes() throws Exception { + public void testParseNameAndNamespace_NoSlashes() { ActionMapping actionMapping = new ActionMapping(); DefaultActionMapper defaultActionMapper = new DefaultActionMapper(); @@ -381,7 +381,7 @@ public void testParseNameAndNamespace_NoSlashes() throws Exception { assertEquals(actionMapping.getNamespace(), ""); } - public void testParseNameAndNamespace_AllowSlashes() throws Exception { + public void testParseNameAndNamespace_AllowSlashes() { ActionMapping actionMapping = new ActionMapping(); DefaultActionMapper defaultActionMapper = new DefaultActionMapper(); @@ -397,8 +397,8 @@ public void testParseNameAndNamespace_AllowSlashes() throws Exception { // === test special prefix === // =========================== - public void testActionPrefixWhenDisabled() throws Exception { - Map parameterMap = new HashMap(); + public void testActionPrefixWhenDisabled() { + Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "myAction", ""); StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest(); @@ -411,8 +411,8 @@ public void testActionPrefixWhenDisabled() throws Exception { assertEquals("someServletPath", actionMapping.getName()); } - public void testActionPrefixWhenEnabled() throws Exception { - Map parameterMap = new HashMap(); + public void testActionPrefixWhenEnabled() { + Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "myAction", ""); StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest(); @@ -426,8 +426,8 @@ public void testActionPrefixWhenEnabled() throws Exception { assertEquals("myAction", actionMapping.getName()); } - public void testActionPrefixWhenSlashesAndCrossNamespaceDisabled() throws Exception { - Map parameterMap = new HashMap(); + public void testActionPrefixWhenSlashesAndCrossNamespaceDisabled() { + Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "my/Action", ""); StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest(); @@ -442,8 +442,8 @@ public void testActionPrefixWhenSlashesAndCrossNamespaceDisabled() throws Except assertEquals("my/Action", actionMapping.getName()); } - public void testActionPrefixWhenSlashesButSlashesDisabledAndCrossNamespaceDisabled() throws Exception { - Map parameterMap = new HashMap(); + public void testActionPrefixWhenSlashesButSlashesDisabledAndCrossNamespaceDisabled() { + Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "my/Action", ""); StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest(); @@ -458,8 +458,8 @@ public void testActionPrefixWhenSlashesButSlashesDisabledAndCrossNamespaceDisabl assertEquals("Action", actionMapping.getName()); } - public void testActionPrefixWhenSlashesButSlashesDisabledAndCrossNamespace() throws Exception { - Map parameterMap = new HashMap(); + public void testActionPrefixWhenSlashesButSlashesDisabledAndCrossNamespace() { + Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "my/Action", ""); StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest(); @@ -475,8 +475,8 @@ public void testActionPrefixWhenSlashesButSlashesDisabledAndCrossNamespace() thr assertEquals("my/Action", actionMapping.getName()); } - public void testActionPrefixWhenCrossNamespace() throws Exception { - Map parameterMap = new HashMap(); + public void testActionPrefixWhenCrossNamespace() { + Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "/my/Action", ""); StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest(); @@ -491,8 +491,8 @@ public void testActionPrefixWhenCrossNamespace() throws Exception { assertEquals("/my/Action", actionMapping.getName()); } - public void testActionPrefix_fromImageButton() throws Exception { - Map parameterMap = new HashMap(); + public void testActionPrefix_fromImageButton() { + Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "myAction", ""); parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "myAction.x", ""); parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "myAction.y", ""); @@ -508,8 +508,8 @@ public void testActionPrefix_fromImageButton() throws Exception { assertEquals("myAction", actionMapping.getName()); } - public void testActionPrefix_fromIEImageButton() throws Exception { - Map parameterMap = new HashMap(); + public void testActionPrefix_fromIEImageButton() { + Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "myAction.x", ""); parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "myAction.y", ""); @@ -524,8 +524,8 @@ public void testActionPrefix_fromIEImageButton() throws Exception { assertEquals("myAction", actionMapping.getName()); } - public void testRedirectPrefix() throws Exception { - Map parameterMap = new HashMap(); + public void testRedirectPrefix() { + Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put("redirect:" + "http://www.google.com", ""); StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest(); @@ -540,8 +540,8 @@ public void testRedirectPrefix() throws Exception { assertNull(result); } - public void testUnsafeRedirectPrefix() throws Exception { - Map parameterMap = new HashMap(); + public void testUnsafeRedirectPrefix() { + Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put("redirect:" + "http://%{3*4}", ""); StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest(); @@ -556,8 +556,8 @@ public void testUnsafeRedirectPrefix() throws Exception { assertNull(result); } - public void testRedirectActionPrefix() throws Exception { - Map parameterMap = new HashMap(); + public void testRedirectActionPrefix() { + Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put("redirectAction:" + "myAction", ""); StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest(); @@ -573,8 +573,8 @@ public void testRedirectActionPrefix() throws Exception { assertNull(result); } - public void testUnsafeRedirectActionPrefix() throws Exception { - Map parameterMap = new HashMap(); + public void testUnsafeRedirectActionPrefix() { + Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put("redirectAction:" + "%{3*4}", ""); StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest(); @@ -590,8 +590,8 @@ public void testUnsafeRedirectActionPrefix() throws Exception { assertNull(result); } - public void testRedirectActionPrefixWithEmptyExtension() throws Exception { - Map parameterMap = new HashMap(); + public void testRedirectActionPrefixWithEmptyExtension() { + Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put("redirectAction:" + "myAction", ""); StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest(); @@ -608,8 +608,8 @@ public void testRedirectActionPrefixWithEmptyExtension() throws Exception { assertNull(result); } - public void testCustomActionPrefix() throws Exception { - Map parameterMap = new HashMap(); + public void testCustomActionPrefix() { + Map<String, Object> parameterMap = new HashMap<>(); parameterMap.put("foo:myAction", ""); final StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest(); @@ -627,39 +627,39 @@ public void execute(String key, ActionMapping mapping) { assertEquals(actionMapping.getName(), "myAction"); } - public void testDropExtension() throws Exception { + public void testDropExtension() { DefaultActionMapper mapper = new DefaultActionMapper(); String name = mapper.dropExtension("foo.action", new ActionMapping()); - assertTrue("Name not right: "+name, "foo".equals(name)); + assertEquals("Name not right: " + name, "foo", name); name = mapper.dropExtension("foo.action.action", new ActionMapping()); - assertTrue("Name not right: "+name, "foo.action".equals(name)); + assertEquals("Name not right: " + name, "foo.action", name); } - public void testDropExtensionWhenBlank() throws Exception { + public void testDropExtensionWhenBlank() { DefaultActionMapper mapper = new DefaultActionMapper(); mapper.setExtensions("action,,"); String name = mapper.dropExtension("foo.action", new ActionMapping()); - assertTrue("Name not right: "+name, "foo".equals(name)); + assertEquals("Name not right: " + name, "foo", name); name = mapper.dropExtension("foo", new ActionMapping()); - assertTrue("Name not right: "+name, "foo".equals(name)); + assertEquals("Name not right: " + name, "foo", name); assertNull(mapper.dropExtension("foo.bar", new ActionMapping())); assertNull(mapper.dropExtension("foo.", new ActionMapping())); } - public void testDropExtensionEmbeddedDot() throws Exception { + public void testDropExtensionEmbeddedDot() { DefaultActionMapper mapper = new DefaultActionMapper(); mapper.setExtensions("action,,"); String name = mapper.dropExtension("/foo/bar-1.0/baz.action", new ActionMapping()); - assertTrue("Name not right: "+name, "/foo/bar-1.0/baz".equals(name)); + assertEquals("Name not right: " + name, "/foo/bar-1.0/baz", name); name = mapper.dropExtension("/foo/bar-1.0/baz", new ActionMapping()); - assertTrue("Name not right: "+name, "/foo/bar-1.0/baz".equals(name)); + assertEquals("Name not right: " + name, "/foo/bar-1.0/baz", name); } - public void testGetUriFromActionMapper1() throws Exception { + public void testGetUriFromActionMapper1() { DefaultActionMapper mapper = new DefaultActionMapper(); ActionMapping actionMapping = new ActionMapping(); actionMapping.setMethod("myMethod"); @@ -670,7 +670,7 @@ public void testGetUriFromActionMapper1() throws Exception { assertEquals("/myNamespace/myActionName!myMethod.action", uri); } - public void testGetUriFromActionMapper2() throws Exception { + public void testGetUriFromActionMapper2() { DefaultActionMapper mapper = new DefaultActionMapper(); ActionMapping actionMapping = new ActionMapping(); actionMapping.setMethod("myMethod"); @@ -681,7 +681,7 @@ public void testGetUriFromActionMapper2() throws Exception { assertEquals("/myActionName!myMethod.action", uri); } - public void testGetUriFromActionMapper3() throws Exception { + public void testGetUriFromActionMapper3() { DefaultActionMapper mapper = new DefaultActionMapper(); ActionMapping actionMapping = new ActionMapping(); actionMapping.setMethod("myMethod"); @@ -693,7 +693,7 @@ public void testGetUriFromActionMapper3() throws Exception { } - public void testGetUriFromActionMapper4() throws Exception { + public void testGetUriFromActionMapper4() { DefaultActionMapper mapper = new DefaultActionMapper(); ActionMapping actionMapping = new ActionMapping(); actionMapping.setName("myActionName"); @@ -703,7 +703,7 @@ public void testGetUriFromActionMapper4() throws Exception { assertEquals("/myActionName.action", uri); } - public void testGetUriFromActionMapper5() throws Exception { + public void testGetUriFromActionMapper5() { DefaultActionMapper mapper = new DefaultActionMapper(); ActionMapping actionMapping = new ActionMapping(); actionMapping.setName("myActionName"); @@ -714,7 +714,7 @@ public void testGetUriFromActionMapper5() throws Exception { } // - public void testGetUriFromActionMapper6() throws Exception { + public void testGetUriFromActionMapper6() { DefaultActionMapper mapper = new DefaultActionMapper(); ActionMapping actionMapping = new ActionMapping(); actionMapping.setMethod("myMethod"); @@ -725,7 +725,7 @@ public void testGetUriFromActionMapper6() throws Exception { assertEquals("/myNamespace/myActionName!myMethod.action?test=bla", uri); } - public void testGetUriFromActionMapper7() throws Exception { + public void testGetUriFromActionMapper7() { DefaultActionMapper mapper = new DefaultActionMapper(); ActionMapping actionMapping = new ActionMapping(); actionMapping.setMethod("myMethod"); @@ -736,7 +736,7 @@ public void testGetUriFromActionMapper7() throws Exception { assertEquals("/myActionName!myMethod.action?test=bla", uri); } - public void testGetUriFromActionMapper8() throws Exception { + public void testGetUriFromActionMapper8() { DefaultActionMapper mapper = new DefaultActionMapper(); ActionMapping actionMapping = new ActionMapping(); actionMapping.setMethod("myMethod"); @@ -748,7 +748,7 @@ public void testGetUriFromActionMapper8() throws Exception { } - public void testGetUriFromActionMapper9() throws Exception { + public void testGetUriFromActionMapper9() { DefaultActionMapper mapper = new DefaultActionMapper(); ActionMapping actionMapping = new ActionMapping(); actionMapping.setName("myActionName?test=bla"); @@ -758,7 +758,7 @@ public void testGetUriFromActionMapper9() throws Exception { assertEquals("/myActionName.action?test=bla", uri); } - public void testGetUriFromActionMapper10() throws Exception { + public void testGetUriFromActionMapper10() { DefaultActionMapper mapper = new DefaultActionMapper(); ActionMapping actionMapping = new ActionMapping(); actionMapping.setName("myActionName?test=bla"); @@ -768,7 +768,7 @@ public void testGetUriFromActionMapper10() throws Exception { assertEquals("/myActionName.action?test=bla", uri); } - public void testGetUriFromActionMapper11() throws Exception { + public void testGetUriFromActionMapper11() { DefaultActionMapper mapper = new DefaultActionMapper(); ActionMapping actionMapping = new ActionMapping(); actionMapping.setName("myActionName.action"); @@ -778,7 +778,7 @@ public void testGetUriFromActionMapper11() throws Exception { assertEquals("/myActionName.action", uri); } - public void testGetUriFromActionMapper12() throws Exception { + public void testGetUriFromActionMapper12() { DefaultActionMapper mapper = new DefaultActionMapper(); ActionMapping actionMapping = new ActionMapping(); actionMapping.setName("myActionName.action"); @@ -788,7 +788,7 @@ public void testGetUriFromActionMapper12() throws Exception { assertEquals("/myActionName.action", uri); } - public void testGetUriFromActionMapper_justActionAndMethod() throws Exception { + public void testGetUriFromActionMapper_justActionAndMethod() { DefaultActionMapper mapper = new DefaultActionMapper(); ActionMapping actionMapping = new ActionMapping(); actionMapping.setMethod("myMethod"); @@ -799,7 +799,7 @@ public void testGetUriFromActionMapper_justActionAndMethod() throws Exception { assertEquals("myActionName!myMethod", uri); } - public void testGetUriFromActionMapperWhenBlankExtension() throws Exception { + public void testGetUriFromActionMapperWhenBlankExtension() { DefaultActionMapper mapper = new DefaultActionMapper(); mapper.setExtensions(",,"); ActionMapping actionMapping = new ActionMapping(); @@ -811,7 +811,7 @@ public void testGetUriFromActionMapperWhenBlankExtension() throws Exception { assertEquals("/myNamespace/myActionName!myMethod", uri); } - public void testSetExtension() throws Exception { + public void testSetExtension() { DefaultActionMapper mapper = new DefaultActionMapper(); mapper.setExtensions(""); assertNull(mapper.extensions); @@ -828,15 +828,40 @@ public void testSetExtension() throws Exception { assertEquals(Arrays.asList("html", "", "xml"), mapper.extensions); mapper.setExtensions("xml"); - assertEquals(Arrays.asList("xml"), mapper.extensions); + assertEquals(Collections.singletonList("xml"), mapper.extensions); mapper.setExtensions(","); - assertEquals(Arrays.asList(""), mapper.extensions); + assertEquals(Collections.singletonList(""), mapper.extensions); + + + } + + public void testAllowedNamespaceNames() { + DefaultActionMapper mapper = new DefaultActionMapper(); + + String namespace = "/"; + assertEquals(namespace, mapper.cleanupNamespaceName(namespace)); + + namespace = "${namespace}"; + assertEquals(mapper.defaultNamespaceName, mapper.cleanupNamespaceName(namespace)); + + namespace = "${${%{namespace}}}"; + assertEquals(mapper.defaultNamespaceName, mapper.cleanupNamespaceName(namespace)); + + namespace = "${#foo='namespace',#foo}"; + assertEquals(mapper.defaultNamespaceName, mapper.cleanupNamespaceName(namespace)); + + namespace = "/test-namespace/namespace/"; + assertEquals("/test-namespace/namespace/", mapper.cleanupNamespaceName(namespace)); + namespace = "/test_namespace/namespace-test/"; + assertEquals("/test_namespace/namespace-test/", mapper.cleanupNamespaceName(namespace)); + namespace = "/test_namespace/namespace.test/"; + assertEquals("/test_namespace/namespace.test/", mapper.cleanupActionName(namespace)); } - public void testAllowedActionNames() throws Exception { + public void testAllowedActionNames() { DefaultActionMapper mapper = new DefaultActionMapper(); String actionName = "action"; @@ -861,7 +886,7 @@ public void testAllowedActionNames() throws Exception { assertEquals("test!bar.action", mapper.cleanupActionName(actionName)); } - public void testAllowedMethodNames() throws Exception { + public void testAllowedMethodNames() { DefaultActionMapper mapper = new DefaultActionMapper(); assertEquals("", mapper.cleanupMethodName(""));
plugins/rest/src/main/java/org/apache/struts2/rest/RestActionMapper.java+1 −1 modified@@ -364,7 +364,7 @@ protected void parseNameAndNamespace(String uri, ActionMapping mapping, Configur name = uri.substring(namespace.length() + 1); } - mapping.setNamespace(namespace); + mapping.setNamespace(cleanupNamespaceName(namespace)); mapping.setName(name); }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
31- www.exploit-db.com/exploits/45260/mitreexploit
- www.exploit-db.com/exploits/45262/mitreexploit
- www.exploit-db.com/exploits/45367/mitreexploit
- github.com/advisories/GHSA-cr6j-3jp9-rw65ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-11776ghsaADVISORY
- packetstormsecurity.com/files/172830/Apache-Struts-Remote-Code-Execution.htmlghsaWEB
- www.arubanetworks.com/assets/alert/ARUBA-PSA-2018-005.txtghsaWEB
- www.oracle.com/technetwork/security-advisory/alert-cve-2018-11776-5072787.htmlghsaWEB
- www.oracle.com/technetwork/security-advisory/cpuoct2018-4428296.htmlghsaWEB
- www.securityfocus.com/bid/105125ghsavdb-entryWEB
- www.securitytracker.com/id/1041547ghsavdb-entryWEB
- www.securitytracker.com/id/1041888ghsavdb-entryWEB
- cwiki.apache.org/confluence/display/WW/S2-057ghsaWEB
- github.com/apache/struts/commit/6e87474f9ad0549f07dd2c37d50a9ccd0977c6eghsaWEB
- lgtm.com/blog/apache_struts_CVE-2018-11776ghsaWEB
- lists.apache.org/thread.html/r6d03e45b81eab03580cf7f8bb51cb3e9a1b10a2cc0c6a2d3cc92ed0c%40%3Cannounce.apache.org%3Eghsamailing-listWEB
- lists.apache.org/thread.html/r6d03e45b81eab03580cf7f8bb51cb3e9a1b10a2cc0c6a2d3cc92ed0c@%3Cannounce.apache.org%3EghsaWEB
- psirt.global.sonicwall.com/vuln-detail/SNWLID-2018-0012ghsaWEB
- security.netapp.com/advisory/ntap-20180822-0001ghsaWEB
- security.netapp.com/advisory/ntap-20181018-0002ghsaWEB
- web.archive.org/web/20180822160726/http://www.securityfocus.com/bid/105125ghsaWEB
- web.archive.org/web/20200807025819/http://www.securitytracker.com/id/1041888ghsaWEB
- web.archive.org/web/20201208145803/https://securitytracker.com/id/1041547ghsaWEB
- www.cisa.gov/known-exploited-vulnerabilities-catalogghsaWEB
- www.exploit-db.com/exploits/45260ghsaWEB
- www.exploit-db.com/exploits/45262ghsaWEB
- www.exploit-db.com/exploits/45367ghsaWEB
- www.oracle.com/security-alerts/cpujul2020.htmlghsaWEB
- www.oracle.com/technetwork/security-advisory/cpujan2019-5072801.htmlghsaWEB
- security.netapp.com/advisory/ntap-20180822-0001/mitre
- security.netapp.com/advisory/ntap-20181018-0002/mitre
News mentions
0No linked articles in our index yet.