Struts 2 Security Vulnerability - Dynamic Method Invocation
Introduction
The Struts 2 web application framework has a long-standing security vulnerability that may not be well known to new Struts 2 developers. By default the framework enables a technique called dynamic method invocation. This technique allows a developer to specify in a Struts 2 action url what method should be called in the Action class. The security problem is that any user of the Struts 2 web application can also use dynamic method invocation to call a public method that is in the Action class. The purpose of this article is to explain the vulnerability and how to fix it. Note this problem occurs through the Struts 2.2.1.1 release, which is the latest release as of February 2011.
Please note that this security issue is well-known to most experienced Struts 2 developers and to the Struts 2 framework committers. They are working on a permanent fix in a future Struts 2 release. You can learn more about this vulnerability and its fix by searching the Struts 2 developers mailing list. My goal with this article is to better inform new Struts 2 developers about this vulnerability and how to prevent it.
NOTE: With the release of Struts 2.3.1.1 in December 2011, the security issue with using dynamic method invocation was addressed. However, by default even in version 2.3.1.1, the vulnerability exists and developers must add additional markup to their package statement to prevent users being able to execute public methods. See http://struts.apache.org/2.3.1/docs/action-configuration.html#ActionConfiguration-DynamicMethodInvocation for more detail.
Dynamic Method Invocation
In Struts 2, you can use the bang (!) operator to specify which method should be executed in the Action class. For example, let's say I have an action named recoverpassword that is mapped to class RecoverPassword. In class RecoverPassword is public method getUserPassword. I can use the bang operator to tell the Struts 2 framework to execute method getUserPassword when action recoverpassword is specified in the URL. I can do this by constructing the URL as follows:
http://localhost:8080/mywebapp/recoverpassword!getUserPassword.action
The part after the bang operator is the method Struts 2 should execute in the class associated with the recoverpassword action.
Note that method getUserPassword must be public and have no parameters. In Struts 2, by default, dynamic method invocation is allowed. Any user of the web application can play around with calling your application's URLs trying to get Struts 2 to execute a method.
For more information about dynamic method invocation see: http://struts.apache.org/2.2.1.1/docs/action-configuration.html#ActionConfiguration-DynamicMethodInvocation.
Example Application
To help illustrate the vulnerability, I've created a simple example application which you can download or view here:
http://www.bpjava.net/struts2_security_vulnerability/
You can download the example project, named Struts2_Security_Vulnerability_Example. The project was created using Eclipse 3.6 and Maven. It is a standard Maven project so if you don't have Eclipse, you should be able to import the unzipped project into any Java IDE that supports Maven.
To build the application's war file run mvn clean package from the project's root folder. The war file is created in the target sub-folder. Copy the war file to your Servlet container (e.g. Tomcat, GlassFish) and then startup the Servlet container.
In a web browser go to:
http://localhost:8080/struts2_security_vulnerability/index.action
(note for this URL and the others that follow if you want to use the example application I deployed to my own server, replace localhost:8080 in the URL with www.bpjava.net - for example http://www.bpjava.net/struts2_security_vulnerability/index.action).
You should see a web page with Welcome to Struts 2!
Security Vulnerability Caused By Dynamic Method Invocation
The Action class RecoverPassword has a public method called getPassword and the project has not disabled dynamic method invocation. So a user entering this URL in the browser:
http://localhost:8080/struts2_security_vulnerability/recoverpassword!getPassword.action
will see the String returned by the method getPassword. Note that recoverpassword must be matched to an Action class that has a public getPassword method that has no parameters.
The result of the url:
http://localhost:8080/struts2_security_vulnerability/recoverpassword!getPassword.action
will either be a 404 error or a stack trace if the struts devmode property is true. The 404 error page will include this line:
No result defined for action edu.ku.it.si.struts2securityvulnerability.security.action.RecoverPassword and result user_secrect_password
The last part above is the String returned by method getPassword, which in this example is the user's password. The stack trace will include a similar line that exposes the user's password.
Any public method in the Action class that has no parameters is vulnerable, even if the method returns no value. For example enter this URL in the example application:
http://localhost:8080/struts2_security_vulnerability/changepassword!changePassword.action?newPassword=my_new_password&username=bruce
You will not see anything in the browser after the URL executes, but the above URL will cause method changePassword to be executed (see class ChangePassword ) and the password for user bruce will be changed to my_new_password. This URL completely bypasses the authentication check in the execute method of class ChangePassword. (To verify that method changePassword was called see the console output after executing the above URL.)
How To Fix The Dynamic Method Invocation Security Vulnerability
Include in struts.xml this Struts 2 property setting:
<constant name="struts.enable.DynamicMethodInvocation" value="false" />
or in struts.properties:
struts.enable.DynamicMethodInvocation = false
or in web.xml include this init-param node in the Struts 2 filter:
<init-param>
<param-name>struts.enable.DynamicMethodInvocation</param-name>
<param-value>false</param-value>
</init-param>
In the example application's struts.xml uncomment that property setting to fix the problem in the example.
The above setting will prevent Struts 2 from parsing the bang operator (!) in the URL, so then the whole part before .action will be used to match to a configured Struts 2 action.
For example with this URL
http://localhost:8080/struts2_security_vulnerability/recoverpassword!getPassword.action
instead of method getPassword being called on the Action class matched to action recoverpassword, the Struts 2 framework will try to find an action named "recoverpassword!getPassword" (which won't exist).
Other Good Practices To Follow When Designing Struts 2 Action classes
1. The only methods in the Action class that should be public are the those methods specifically matched to a Struts 2 action (e.g. execute, input) and get/set methods for the instance fields that are exposed to the view pages.
2. All methods that do some work on behalf of the action should be in a Service layer class and not in the ActionSupport class.
Summary
Hopefully this article will convince Struts 2 developers to always include setting the DynamicMethodInvocation property to false to prevent the use of the bang operator to call specific methods in the Action class.
Users don't have to do any work to enable the security vulnerability -- it's in Struts 2 by default -- the ! operator is enabled. Users must do extra work to turn it off. Since most users new to Struts 2 probably are not aware of the ! operator they don't know how to turn it off and reduce this security vulnerability.
I tested using the wildcard method selection and the same vulnerability does exist. However, a Struts 2 programmer must explicitly enable wildcard method selection in his struts.xml otherwise the wildcard method doesn't work and the vulnerability does not exist.
Dynamic method invocation is enabled by default and the struts 2 developer must turn it off through a property setting.
Either way it's very important to review your action class public methods to ensure you have not exposed anything that should not be accessible to the user.
i excpected that. i'm just wondering why strict-method-invocation is only implemented for DMI and not for wildcard method invocation (at least it's only documented for DMI).
I think i should ask this on the struts mailing list.