High severity8.1NVD Advisory· Published Jul 10, 2013· Updated Apr 29, 2026
CVE-2013-2115
CVE-2013-2115
Description
Apache Struts 2 before 2.3.14.2 allows remote attackers to execute arbitrary OGNL code via a crafted request that is not properly handled when using the includeParams attribute in the (1) URL or (2) A tag. NOTE: this issue is due to an incomplete fix for CVE-2013-1966.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.struts:struts2-coreMaven | >= 2.0.0, < 2.3.14.2 | 2.3.14.2 |
org.apache.struts.xwork:xwork-coreMaven | >= 2.0.0, < 2.3.14.2 | 2.3.14.2 |
Affected products
1Patches
4d934c6e7430bMerged from STRUTS_2_3_14_X
4 files changed · +127 −68
core/src/main/java/org/apache/struts2/views/util/DefaultUrlHelper.java+37 −39 modified@@ -241,47 +241,45 @@ public void buildParametersString(Map<String, Object> params, StringBuilder link private String buildParameterSubstring(String name, String value) { StringBuilder builder = new StringBuilder(); - builder.append(translateAndEncode(name)); + builder.append(encode(name)); builder.append('='); - builder.append(translateAndEncode(value)); + builder.append(encode(value)); return builder.toString(); } - /** - * Translates any script expressions using {@link com.opensymphony.xwork2.util.TextParseUtil#translateVariables} and - * encodes the URL using {@link java.net.URLEncoder#encode} with the encoding specified in the configuration. - * - * @param input - * @return the translated and encoded string - */ - public String translateAndEncode(String input) { - String translatedInput = translateVariable(input); - try { - return URLEncoder.encode(translatedInput, encoding); - } catch (UnsupportedEncodingException e) { - if (LOG.isWarnEnabled()) { - LOG.warn("Could not encode URL parameter '#0', returning value un-encoded", input); - } - return translatedInput; - } - } - - public String translateAndDecode(String input) { - String translatedInput = translateVariable(input); - try { - return URLDecoder.decode(translatedInput, encoding); - } catch (UnsupportedEncodingException e) { - if (LOG.isWarnEnabled()) { - LOG.warn("Could not encode URL parameter '#0', returning value un-encoded", input); - } - return translatedInput; - } - } - - private String translateVariable(String input) { - ValueStack valueStack = ServletActionContext.getContext().getValueStack(); - return TextParseUtil.translateVariables(input, valueStack); - } + /** + * Encodes the URL using {@link java.net.URLEncoder#encode} with the encoding specified in the configuration. + * + * @param input the input to encode + * @return the encoded string + */ + public String encode( String input ) { + try { + return URLEncoder.encode(input, encoding); + } catch (UnsupportedEncodingException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Could not encode URL parameter '#0', returning value un-encoded", input); + } + return input; + } + } + + /** + * Decodes the URL using {@link java.net.URLDecoder#decode(String, String)} with the encoding specified in the configuration. + * + * @param input the input to decode + * @return the encoded string + */ + public String decode( String input ) { + try { + return URLDecoder.decode(input, encoding); + } catch (UnsupportedEncodingException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Could not decode URL parameter '#0', returning value un-decoded", input); + } + return input; + } + } public Map<String, Object> parseQueryString(String queryString, boolean forceValueArray) { Map<String, Object> queryParams = new LinkedHashMap<String, Object>(); @@ -299,8 +297,8 @@ public Map<String, Object> parseQueryString(String queryString, boolean forceVal paramValue = tmpParams[1]; } if (paramName != null) { - paramName = translateAndDecode(paramName); - String translatedParamValue = translateAndDecode(paramValue); + paramName = decode(paramName); + String translatedParamValue = decode(paramValue); if (queryParams.containsKey(paramName) || forceValueArray) { // WW-1619 append new param value to existing value(s)
core/src/test/java/org/apache/struts2/views/jsp/URLTagTest.java+85 −20 modified@@ -21,16 +21,12 @@ package org.apache.struts2.views.jsp; -import java.io.File; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.servlet.http.HttpSession; -import javax.servlet.jsp.JspWriter; - +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.DefaultActionInvocation; +import com.opensymphony.xwork2.DefaultActionProxyFactory; +import com.opensymphony.xwork2.inject.Container; import org.apache.struts2.ServletActionContext; import org.apache.struts2.components.URL; import org.apache.struts2.dispatcher.ApplicationMap; @@ -40,15 +36,14 @@ import org.apache.struts2.dispatcher.mapper.ActionMapping; import org.apache.struts2.dispatcher.mapper.DefaultActionMapper; -import com.mockobjects.dynamic.Mock; -import com.opensymphony.xwork2.ActionContext; -import com.opensymphony.xwork2.ActionProxy; -import com.opensymphony.xwork2.DefaultActionInvocation; -import com.opensymphony.xwork2.DefaultActionProxy; -import com.opensymphony.xwork2.DefaultActionProxyFactory; -import com.opensymphony.xwork2.config.providers.XWorkConfigurationProvider; -import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; -import com.opensymphony.xwork2.inject.Container; +import javax.servlet.http.HttpSession; +import javax.servlet.jsp.JspWriter; +import java.io.File; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Unit test for {@link URLTag}. @@ -619,6 +614,69 @@ public void testEmptyActionCustomMapper() throws Exception { } + public void testEmbeddedParamTagExpressionGetsEvaluatedCorrectly() throws Exception { + request.setRequestURI("/public/about"); + request.setQueryString("section=team&company=acme inc"); + + tag.setAction("team"); + tag.setIncludeParams("all"); + + tag.doStartTag(); + + Foo foo = new Foo("test"); + stack.push(foo); + + // include nested param tag + ParamTag paramTag = new ParamTag(); + paramTag.setPageContext(pageContext); + paramTag.setName("title"); + paramTag.setValue("%{title}"); + paramTag.doStartTag(); + paramTag.doEndTag(); + + tag.doEndTag(); + + assertEquals("/team.action?section=team&company=acme+inc&title=test", writer.toString()); + } + + public void testAccessToStackInternalsGetsHandledCorrectly() throws Exception { + Map<String, Object> params = new HashMap<String, Object>(); + params.put("aaa", new String[] {"1${#session[\"foo\"]='true'}"}); + params.put("aab", new String[] {"1${#session[\"bar\"]}"}); + params.put("aac", new String[] {"1${#_memberAccess[\"allowStaticMethodAccess\"]='true'}"}); + params.put("aad", new String[] {"1${#_memberAccess[\"allowStaticMethodAccess\"]}"}); + + request.setParameterMap(params); + request.setRequestURI("/public/about"); + request.setQueryString("aae${%23session[\"bar\"]}=1%24%7B%23session%5B%22bar%22%5D%7D"); + session.put("bar", "rab"); + + tag.setAction("team"); + tag.setIncludeParams("all"); + + tag.doStartTag(); + tag.doEndTag(); + + Object allowMethodAccess = stack.findValue("\u0023_memberAccess['allowStaticMethodAccess']"); + assertNotNull(allowMethodAccess); + assertEquals(Boolean.FALSE, allowMethodAccess); + + assertNull(session.get("foo")); + + assertEquals("/team.action?" + + "aab=1%24%7B%23session%5B%22bar%22%5D%7D" + + "&" + + "aac=1%24%7B%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3D%27true%27%7D" + + "&" + + "aaa=1%24%7B%23session%5B%22foo%22%5D%3D%27true%27%7D" + + "&" + + "aad=1%24%7B%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%7D" + + "&"+ + "aae%24%7B%23session%5B%22bar%22%5D%7D=1%24%7B%23session%5B%22bar%22%5D%7D" + , writer.toString() + ); + } + protected void setUp() throws Exception { super.setUp(); @@ -635,7 +693,14 @@ protected void setUp() throws Exception { public static class Foo { private String title; - public void setTitle(String title) { + public Foo() { + } + + public Foo( String title ) { + this.title = title; + } + + public void setTitle(String title) { this.title = title; }
core/src/test/java/org/apache/struts2/views/util/DefaultUrlHelperTest.java+4 −4 modified@@ -378,17 +378,17 @@ public void testParseNullQuery() throws Exception { } - public void testTranslateAndEncode() throws Exception { + public void testEncode() throws Exception { setProp(StrutsConstants.STRUTS_I18N_ENCODING, "UTF-8"); - String result = urlHelper.translateAndEncode("\u65b0\u805e"); + String result = urlHelper.encode("\u65b0\u805e"); String expectedResult = "%E6%96%B0%E8%81%9E"; assertEquals(result, expectedResult); } - public void testTranslateAndDecode() throws Exception { + public void testDecode() throws Exception { setProp(StrutsConstants.STRUTS_I18N_ENCODING, "UTF-8"); - String result = urlHelper.translateAndDecode("%E6%96%B0%E8%81%9E"); + String result = urlHelper.decode("%E6%96%B0%E8%81%9E"); String expectedResult = "\u65b0\u805e"; assertEquals(result, expectedResult);
xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java+1 −5 modified@@ -32,7 +32,7 @@ */ public class SecurityMemberAccess extends DefaultMemberAccess { - private boolean allowStaticMethodAccess; + private final boolean allowStaticMethodAccess; Set<Pattern> excludeProperties = Collections.emptySet(); Set<Pattern> acceptProperties = Collections.emptySet(); @@ -45,10 +45,6 @@ public boolean getAllowStaticMethodAccess() { return allowStaticMethodAccess; } - public void setAllowStaticMethodAccess(boolean allowStaticMethodAccess) { - this.allowStaticMethodAccess = allowStaticMethodAccess; - } - @Override public boolean isAccessible(Map context, Object target, Member member, String propertyName) {
1 file changed · +2 −2
core/src/test/java/org/apache/struts2/views/util/DefaultUrlHelperTest.java+2 −2 modified@@ -380,15 +380,15 @@ public void testParseNullQuery() throws Exception { public void testTranslateAndEncode() throws Exception { setProp(StrutsConstants.STRUTS_I18N_ENCODING, "UTF-8"); - String result = urlHelper.translateAndEncode("\u65b0\u805e"); + String result = urlHelper.encode("\u65b0\u805e"); String expectedResult = "%E6%96%B0%E8%81%9E"; assertEquals(result, expectedResult); } public void testTranslateAndDecode() throws Exception { setProp(StrutsConstants.STRUTS_I18N_ENCODING, "UTF-8"); - String result = urlHelper.translateAndDecode("%E6%96%B0%E8%81%9E"); + String result = urlHelper.decode("%E6%96%B0%E8%81%9E"); String expectedResult = "\u65b0\u805e"; assertEquals(result, expectedResult);
1 file changed · +37 −39
core/src/main/java/org/apache/struts2/views/util/DefaultUrlHelper.java+37 −39 modified@@ -241,47 +241,45 @@ public void buildParametersString(Map<String, Object> params, StringBuilder link private String buildParameterSubstring(String name, String value) { StringBuilder builder = new StringBuilder(); - builder.append(translateAndEncode(name)); + builder.append(encode(name)); builder.append('='); - builder.append(translateAndEncode(value)); + builder.append(encode(value)); return builder.toString(); } - /** - * Translates any script expressions using {@link com.opensymphony.xwork2.util.TextParseUtil#translateVariables} and - * encodes the URL using {@link java.net.URLEncoder#encode} with the encoding specified in the configuration. - * - * @param input - * @return the translated and encoded string - */ - public String translateAndEncode(String input) { - String translatedInput = translateVariable(input); - try { - return URLEncoder.encode(translatedInput, encoding); - } catch (UnsupportedEncodingException e) { - if (LOG.isWarnEnabled()) { - LOG.warn("Could not encode URL parameter '#0', returning value un-encoded", input); - } - return translatedInput; - } - } - - public String translateAndDecode(String input) { - String translatedInput = translateVariable(input); - try { - return URLDecoder.decode(translatedInput, encoding); - } catch (UnsupportedEncodingException e) { - if (LOG.isWarnEnabled()) { - LOG.warn("Could not encode URL parameter '#0', returning value un-encoded", input); - } - return translatedInput; - } - } - - private String translateVariable(String input) { - ValueStack valueStack = ServletActionContext.getContext().getValueStack(); - return TextParseUtil.translateVariables(input, valueStack); - } + /** + * Encodes the URL using {@link java.net.URLEncoder#encode} with the encoding specified in the configuration. + * + * @param input the input to encode + * @return the encoded string + */ + public String encode( String input ) { + try { + return URLEncoder.encode(input, encoding); + } catch (UnsupportedEncodingException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Could not encode URL parameter '#0', returning value un-encoded", input); + } + return input; + } + } + + /** + * Decodes the URL using {@link java.net.URLDecoder#decode(String, String)} with the encoding specified in the configuration. + * + * @param input the input to decode + * @return the encoded string + */ + public String decode( String input ) { + try { + return URLDecoder.decode(input, encoding); + } catch (UnsupportedEncodingException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Could not decode URL parameter '#0', returning value un-decoded", input); + } + return input; + } + } public Map<String, Object> parseQueryString(String queryString, boolean forceValueArray) { Map<String, Object> queryParams = new LinkedHashMap<String, Object>(); @@ -299,8 +297,8 @@ public Map<String, Object> parseQueryString(String queryString, boolean forceVal paramValue = tmpParams[1]; } if (paramName != null) { - paramName = translateAndDecode(paramName); - String translatedParamValue = translateAndDecode(paramValue); + paramName = decode(paramName); + String translatedParamValue = decode(paramValue); if (queryParams.containsKey(paramName) || forceValueArray) { // WW-1619 append new param value to existing value(s)
1 file changed · +1 −5
xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java+1 −5 modified@@ -32,7 +32,7 @@ */ public class SecurityMemberAccess extends DefaultMemberAccess { - private boolean allowStaticMethodAccess; + private final boolean allowStaticMethodAccess; Set<Pattern> excludeProperties = Collections.emptySet(); Set<Pattern> acceptProperties = Collections.emptySet(); @@ -45,10 +45,6 @@ public boolean getAllowStaticMethodAccess() { return allowStaticMethodAccess; } - public void setAllowStaticMethodAccess(boolean allowStaticMethodAccess) { - this.allowStaticMethodAccess = allowStaticMethodAccess; - } - @Override public boolean isAccessible(Map context, Object target, Member member, String propertyName) {
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
13- struts.apache.org/development/2.x/docs/s2-014.htmlnvdVendor AdvisoryWEB
- www.securityfocus.com/bid/60167nvdThird Party AdvisoryVDB Entry
- cwiki.apache.org/confluence/display/WW/S2-014nvdVendor AdvisoryWEB
- github.com/advisories/GHSA-7ghm-rpc7-p7g5ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2013-2115ghsaADVISORY
- bugzilla.redhat.com/show_bug.cginvdIssue TrackingWEB
- cwiki.apache.org/confluence/display/WW/S2-013ghsaWEB
- github.com/apache/struts/commit/d7804297e319c7a12245e1b536e565fcea6d650ghsaWEB
- github.com/apache/struts/commit/d934c6e7430b7b98e43a0a085a2304bd31a75c3dghsaWEB
- github.com/apache/struts/commit/ea96d18d0f75c390d2595648efa3563785c272c6ghsaWEB
- github.com/apache/struts/commit/fed4f8e8a4ec69b5e7612b92d8ce3e476680474ghsaWEB
- issues.apache.org/jira/browse/WW-4063ghsaWEB
- web.archive.org/web/20140212000331/http://www.securityfocus.com/bid/60167ghsaWEB
News mentions
0No linked articles in our index yet.