Apache Struts 1 ValidatorForm is a commonly used component in the JAVA EE Web Application that requires validated form fields input by a user, such as a login form, registration form, or other information form. By configuring the validation rules, Apache Struts can validate many different kinds of fields - username, email, credit card number, etc. However, a bug in Apache Struts 1 can be used to manipulate the property of ValidatorForm so as to modify the validation rules, or even worse, cause a denial of service or execute arbitrary code in the context of the Web Application.
This potential Input Validation Bypass or Denial Of Service vulnerability is caused by an error in RequestUtils.java when populating the properties of the JavaBean from an HTTP Request, and it will not be fixed in Apache Struts 1 since Apache Struts 1 is End-Of-Life. In this post we analyze the Apache Struts source codes and expose the root cause of this vulnerability.
Form Bean is a kind of JavaBean that is used by Apache Struts 1 as a specialized implementation of the ActionForm and its subclasses, such as ValidatorForm. It has all the properties of the ActionForm class and its subclasses, and the property can only be accessed by the get or set method offered by the related Form class. A Form Bean instance is created when the Form class is accessed via an HTTP request, and it is stored by Apache Struts 1. The lifecycle of the Form Bean instance is decided by the "scope" value in the configuration file "struts-config.xml". The default value of "scope" is "session", which means that all http requests with the same "JSESSIONID" will share the same Form Bean instance. In a multiple threads context, the property of ValidatorForm Bean set by one thread can be manipulated by another thread so as to modify the validation rules, even to cause denial of service or arbitrary code execution.
The following code snippet was taken from Apache Struts 1 1.2.9. Comments added by me have been highlighted.
From the above code snippet, we can see that a Form Bean instance will be created and stored in the session, and every thread that handles the HTTP Servlet Request in the same session can access it. "validatorResults" is a property of ValidatorForm Bean, which is a ValidatorResults JavaBean. It is used to return the validation results of validators. It is null before the "BeanUtils.populate()" function, which is used to populate the property of Form Bean from the HTTP Servlet Request, and sets the value after calling the "validate()" function, which performs the field validation. In a multiple thread context, if a thread returns from a "validate()" function, the "validatorResults" is a set value. Meanwhile, if another thread handling the HTTP Servlet Request in the same session accesses it during the "BeanUtils.populate()" function, the "validatorResults" can be manipulated to modify the validation rules, even causing a denial of service or executing arbitrary code.
The eclipse running time debugger can show that the "validatorResults" is a set value before "BeanUtils.populate()" in a multiple thread context. We can see that the highlighted validatorResults includes validation results even if a validation has not been done.
We use the following struts ValidatorForm example to show how the input validation bypass works.
In this example, we define an EmployeeForm class that extends the ValidatorForm class.
There are four validators (required, maxlength, minlength, mask) in the "depends" string, which means the "username" filed should not be null and its length should be between 3 and 100. Further, it can only be a word, number, dash, or underscore. If we change the "depends" string to "required", then any non-null "username" will be accepted and input validation is bypassed. The following image shows the normal conditions when input validation works:
We used the following "success.jsp" to show the username after bypassing the input validation. "success.jsp" gets the "username" property from the JavaBean and displays.
We sent the specially crafted HTTP POST request manually and the eclipse running time debugger shows how we can change the "depends" string:
Before we set the new "depends" value, we can find the original "depends" value read from "validation.xml":
After the "setProperty()" function is executed, we can change the "depends" value:
Since Apache Struts 1 will not be patched for this bug, we don't publish our PoC in this blog. The returned web content generated by " success.jsp" can be used to confirm that the input validation bypass happened.
During our test we found that "classLoader" can also be controlled by manipulating "validatorResults" or "resultValueMap"(another property of ValidatorForm), which means that arbitrary code execution is possible, like in CVE-2014-0114.
Please note that authentication is NOT required to exploit this vulnerability.
Fortinet released IPS signature Apache.Struts.ActionServlet.Validator.Security.Bypass to address this vulnerability.