Spring vulnerabilities
Sep 09, 2011Springsource recently released Spring 3.0.6 fixing a bunch of security vulnerabilities I reported that affect applications using Spring and serialization (cve-2011-2894). Today Springsource finally made a security announcement (CVE-2011-2894). Here are some details on these vulnerabilities.
Serializable proxy to bean factory
Affected: Applications that have Spring AOP on the classpath and deserialize a stream from an untrusted source
Result: Arbitrary code execution
Short version: The problem is that the JdkDynamicAopProxy, DefaultListableBeanFactory and some other Spring classes are Serializable and can be configured to execute arbitrary code when the application uses these deserialized objects.
JdkDynamicAopProxy is used internally by the DefaultAopProxyFactory. It is an InvocationHandler , so it can be used with a java.lang.reflect.Proxy to dynamically handle method calls. Which object the proxy should delegate calls to, the target, can be configured in the JdkDynamicAopProxy with a TargetSource.
Certain TargetSources can be configured to point to a bean in a BeanFactory, which can contain arbitrary code in the form of bean definitions.
All of these objects (Proxy, JdkDynamicAopProxy, AbstractBeanFactoryBasedTargetSource, DefaultListableBeanFactory, AbstractBeanDefinition) are Serializable, and the Proxy can be configured to implement any interface the application might expect. That means an attacker can send them in the place of any object in a stream, and when the receiving application calls any method on the deserialized object, it will trigger the execution of the arbitrary code.
A DefaultListableBeanFactory is under normal circumstances never included in a serialized stream. It has a writeReplace method that before it's being serialized replaces it with a SerializedBeanFactoryReference; a reference to an already existing bean factory. But it's only the serialization that is prevented (at the attacker's side, where it's easily overridden), not the deserialization.
The application doesn't even have to use Spring in its directly publically exposed part to be vulnerable. It just needs Spring AOP available on the classpath.
An application is vulnerable if it deserializes a stream from an untrusted source. That includes any application using RMI, or Spring's HttpInvoker.
The vulnerability has been fixed in Spring by making it impossible to deserialize a DefaultListableBeanFactory except through the SerializedBeanFactoryReference. And the id used by the SerializedBeanFactoryReference has been made easier to configure because it should not be predictable by a client.
Another way this issue can be avoided is by not allowing a java.reflect.Proxy or JdkDynamicAopProxy to be deserialized when it is received from a client. Unfortunately some Spring applications actually depend on that to work, so this has not been disabled in Spring itself. But for most applications completely blocking it should be fine, so that's what I strongly recommend. Starting from Spring 3.0.6, the RemoteInvocationSerializingExporter can be configured to not accept any java.lang.Proxy by setting acceptProxyClasses to false. The advantage of that solution is that it not only blocks this concrete attack, but also possible future variations. There is some danger that similar vulnerabilities might pop up again later, because accepting a JdkDynamicAopProxy means also accepting any serializable TargetSource, Advisor, Advise,... Those have not been written with security against this kind of attack in mind. Blocking it removes that unwanted attack surface.
Exposed ProxyFactory
Affected: Applications using HttpInvokerServiceExporter (and possibly other exporters)
Result: Arbitrary code execution
An HttpInvokerServiceExporter exposes the methods of a specified service interface over HTTP, using Java serialization.
But it accidentally not only exposes those methods, but also the methods of the Advised interface of the ProxyFactory it uses internally.
One of those exposed methods is setTargetSource, which can be abused to change the way that the exporter will handle all calls after that.
This can be exploited in a way similar to the previous vulnerability: by sending a targetSource that points to a bean factory that can contain arbitrary code.
It is fixed in Spring by making the proxy opaque.
About ContextPropagatingRemoteInvocation
Those were two vulnerabilities using Spring AOP. The rest of this post is about variants of one type of attack in Spring Security.
First I'll give a short introduction to the relevant parts of Spring Security and ContextPropagatingRemoteInvocation to make clear what this is all about.
In Spring Security, an Authentication object represents either a (not authenticated yet) request for authentication, or the result of the authentication. It usually contains a username, password and/or the list of authorities granted to the user.
The checking of an authentication request and building of the resulting authentication happens in an AuthenticationProvider.
The information inside the unauthenticated Authentication object should not be trusted, it still has to be checked. Usually that Authentication object is created by the application itself, and filled with the information provided by the user (usually just the username and password).
When using the ContextPropagatingRemoteInvocation, not only some specific attributes of this Authentication object are provided by the user, but he provides the whole Authentication object, in serialized form.
The ContextPropagatingRemoteInvocation can be used to authenticate requests to any service exposed with a HttpInvokerServiceExporter. It doesn't have to be enabled explicitly on the server; even applications that normally handle authentication in another way are still vulnerable to these problems.
The essence of all of the following problems is that the code is not written to handle such arbitrary untrusted Authentication objects.
The fix applied in Spring for this issue is not to allow such arbitrary Authentication requests in a ContextPropagatingRemoteInvocation anymore; but instead allow only a username and a password.
ContextPropagatingRemoteInvocation authentication
Affected: Applications using HttpInvokerServiceExporter, with Spring Security
Result: Privilege escalation: authenticate without credentials
This is the combination of a problem in Spring and the JDK. Because Oracle is still investigating their side of the issue, I'm still keeping the details private.
Password in BadCredentialsException
Affected: Applications using HttpInvokerServiceExporter with the DaoAuthenticationProvider (and possibly other providers).
Result: Privilege escalation: expose passwords
The DaoAuthenticationProvider.additionalAuthenticationChecks method throws a BadCredentialsException that contains as extraInformation the UserDetails, which also contain the (possibly hashed) correct password.
So when a user logs in with the wrong password, he receives a response that contains the right password.
There is already an option available in Spring to avoid this: AbstractAuthenticationManager.clearExtraInformation. But that's an obscure detail not documented anywhere except in the javadoc, so most applications don't enabled this.
PreAuthenticatedAuthenticationProvider
Affected: Applications using HttpInvokerServiceExporter, with the PreAuthenticatedAuthenticationProvider
Result: Privilege escalation: authenticate without credentials
The use for the PreAuthenticatedAuthenticationProvider, is that an application can be set up for example behind a trusted reverse proxy (usually to integrate into a single-sign-on solution) that provides an http header that contains a username. That header is then parsed by the RequestHeaderAuthenticationFilter that puts the username in a PreAuthenticatedAuthenticationToken. That token is then trusted (without having to provide a password) when authentication happens by the PreAuthenticatedAuthenticationProvider.
The javadoc of RequestHeaderAuthenticationFilter that this is only safe to be used in a setup where the source (proxy) of the request can be trusted. Still, it seems reasonable to assume it's safe to use with Spring remoting (i.e., in combination with a HttpInvokerServiceExporter).
The problem is that nothing prevents a PreAuthenticatedAuthenticationToken from being provided directly by a ContextPropagatingRemoteInvocation. The PreAuthenticatedAuthenticationProvider doesn't check that the PreAuthenticatedAuthenticationToken comes from a trusted source.
In this kind of setup the PreAuthenticatedGrantedAuthoritiesUserDetailsService could commonly be used to provide the details of the user that is logging in, and with that an attacker can give himself any authority he wants. This vulnerability probably applies to any AuthenticationUserDetailsService.
The intent of the PreAuthenticatedAuthenticationProvider is similar to the RunAsImplAuthenticationProvider: there is an Authentication coming from a trusted source, without real credentials. The RunAsImplAuthenticationProvider doesn't have the same problem though, because it requires a secret key to be provided in the token.
Authorities in JaasAuthenticationProvider
Affected: Applications using HttpInvokerServiceExporter, with the JaasAuthenticationProvider
Result: Privilege escalation: acquiring any GrantedAuthority
The JaasAuthenticationProvider blindly copies the GrantedAuthorities of a request Authentication into the result Authentication. So a client can tell the server which authorities he wants, and the server blindly grants them. There's nothing more to it than that.
Principal in JaasAuthenticationProvider
Affected: Applications using HttpInvokerServiceExporter, with the JaasAuthenticationProvider, and an exposed service with a particular behaviour
Result: Privilege escalation: authenticate as another user
In addition to the granted authorities (see above), the JaasAuthenticationProvider also copies the principal given in the request, although only its toString was authenticated using JAAS. The provided principal can be any object, it doesn't need to implement a particular interface. If it is an object whose toString changes afterwards, that also changes with which username you are logged in.
I don't know of any object in Spring or the JDK that could easily be (ab)used as principal for this; one that out of itself changes the result of its toString method.
So this is probably only exploitable in practice if the service being called changes the content of objects it receives as parameters in a way the attacker can control, so that its toString changes from one username to another one, and then still in the same request, looks up the name of the currently logged in user (which has then switched), and then for example returns a result that should only be visible to that user.
That's a not completely impossible, but a bit farfetched scenario.
Conclusion
If you are using Spring and receiving serialized streams from an untrusted source (with RMI, Spring Remoting with HttpInvoker,...) you should upgrade to a fixed version of Spring asap.
Standard Java serialization is a powerful and flexible mechanism. But that leads unexpected security consequences and many opportunities for abuse. Vulnerabilities related to it keep popping up. Most of those where about breaking out of a local sandbox, but here it applies in a remote context. I don't expect that stream of problems to end.
It continues to be a useful mechanism in the right situation, and applying these updates will avoid the specific issues here for now. But I recommend you stop using it altogether for untrusted remoting if you care about your security.
3 comments
[…] you might want to subscribe to the RSS feed for updates on this topic.Example from Springsource, as explained by Wouter Coekaerts, showing why clients should not be trusted. Affected: Applications that have Spring AOP on the […]
Recently I added domain model-specific authorities to my User principal (I feel Spring’s ACL is too heavy weight for us) and discovered that my own DaoAuthenticationProvider extension returns an Authentication containing the user-provided principal. (This wasn’t a huge issue before adding this extra information.) But I wouldn’t have expected the Spring security team to make such a mistake in the JaasAuthenticationProvider!
We have a client-server application that uses RMI with context propagation and runs only on our internal network. If I’m correct passwords must be sent unsalted for propagation to work. Does this imply that we have to use some sort of transport layer security to be really secure? Is it sufficient to swith to https invoker?
Do you think it is safe to give users the right to read passwords that are salted using bcrypt or should I spend some extra effort in preventing this?
Great post, thanks!
I was curious about all those serializable classes: Proxy, JdkDynamicAopProxy, AbstractBeanFactoryBasedTargetSource, DefaultListableBeanFactory, AbstractBeanDefinition. So I did a quick test and tried to serialize a BeanDefinition and found that at least one of its properties (constructorArgumentValues) is not serializable. Is this a theorical research or have you successfully serialized the whole proxy?
A “standard” BeanDefinition is not serializable indeed. If I remember correctly I made one where constructorArgumentValues was null or something… I don’t have the code available at the moment.
Better late than never: I plan on making the demo/exploit for all vulnerabilities here public soon (when the related JDK issue is resolved; it finally seems to be moving forward).