对ysoserial工具及java反序列化的一个阶段性理解

Posted ph4nt0mer

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对ysoserial工具及java反序列化的一个阶段性理解相关的知识,希望对你有一定的参考价值。

经过一段时间的琢磨与反思,以及重读了大量之前看不懂的反序列化文章,目前为止算是对java反序列化这块有了一个阶段性的小理解。

目前为止,发送的所有java反序列化的漏洞中。主要需要两个触发条件:

1、反序列化的攻击入口

2、反序列化的pop攻击链

这两个条件缺一不可。网上大量分析gadgets的文章方法,让人误以为有攻击链就可以反序列化。其实这块是有一定的误导性的。在我最初研究反序列化的时候,我觉得攻击链是最重要的。其实不然,反序列化的攻击入口才是至关重要的。因为现阶段的java环境加上java所在的金融、保险等大型项目中,往往组件jar包和jdk版本是更新最缓慢的,因为这块需要测试大量的兼容性,对大型java项目来说,攻击链基本上一定存在,除非该java程序最近才被开发、部署,且开发人员喜欢用最新的版本软件包。

 

0X01攻击入口

目前其实在白盒过程中要比较快的查找攻击入口的话。可以全局搜索是否有以下这类方法函数:

ObjectInputStream.readObject
ObjectInputStream.readUnshared
XMLDecoder.readObject
Yaml.load
XStream.fromXML
ObjectMapper.readValue
JSON.parseObject

 

通过搜到到这些函数方法所在位置,再反向追踪是否可控输入流,如果可控,那么恭喜,反序列化的第一步已经完成。

拿我前几篇的文章来讲,XMLDecoder.readObject这块函数其实可以当做入口来看,至于为啥可以执行命令,你就当他当做system()函数来看,如果要更加深层的知道为啥可以执行命令,就可以跟下去,不过跟下去的话,属于更加底层的研究。

我们做安全其实可以分好几个攻击层次。

1、攻击普通开发者(这里的普通开发者可以指敏捷开发人员,大部分使用框架,或者现有函数库来使用,极大的加快了开发速度,但是对于框架具体底层功能实现可能不会特意去了解,大部分通过开发手册和api接口即可了解表层意义上的功能实现)

2、框架、底层库开发者(这类开发人员往往开发不是为了某个特定的东西,往往是为了帮助大量普通开发者更快、更短的代码实现一个功能,减少普通开发者的代码量,降低程序开发门槛)

3、系统层函数开发者(这类的开发者衔接的是高级面向对象的语言与底层汇编,甚至指令集之间的库函数)

随着攻击层次的加高,我们的攻击范围也会越广。

在这三个层次里,往往攻击入口点在1和2都会出现,更多的是出现在1层,而gadget在1和2也会出现,但是往往出现更多的在2层。

拿weblogic的xml反序列化来讲,把oracle的开发人员当做普通的拿现有开源框架库基础上开发的人员来看的话,他们造成反序列化的攻击入口点就在1层面,序列化的gadget在2层面。

 

在cve-2019-2725中,_async目录下的其实就是一个攻击入口,因为攻击入口和wls-wsat相似,所以后续分析和10271很像。

但是由于有10271补丁在,所以在2725中需要利用未在黑名单里的标签来绕过补丁。

 

0X02POP攻击链

java的pop攻击链不是说寻找难度不大,而是在现有yso工具在的情况下,对已有的漏洞利用来说,已经方便了很多。但是寻找一个新的pop链还是很困难的。

CommonsCollections1的gadget来说吧,因为网上这个的分析文章最多,所以也是我了解的比较熟悉的一个gadget。

这个漏洞的最关键地方是org.apache.commons.collections.functors.InvokerTransformer类下面的transform函数

public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method ‘" + this.iMethodName + "‘ on ‘" + input.getClass() + "‘ does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method ‘" + this.iMethodName + "‘ on ‘" + input.getClass() + "‘ cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method ‘" + this.iMethodName + "‘ on ‘" + input.getClass() + "‘ threw an exception", var7);
            }
        }
    }

然后更关键的地方是

 Class cls = input.getClass();
 Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
 return method.invoke(input, this.iArgs);

为啥这块危险呢,首先这个地方利用了java的反射机制,如果你不了解,你可以直接死记硬背或者理解成这样子写就是有一定问题的,等同于system(xxx),而xxx根据transform的形参来看,也就是input是我们可控的。且之间没有任何过滤。

this.iMethodName, this.iParamTypes

    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName;
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }

这两个读一下InvokerTransformer类的实现代码,发现初始化InvokerTransformer的时候,这两个也可控。所以这块是有可能成为POP链的。

public class InvokerTransformer implements Transformer, Serializable {

根据类的接口来看,这个类是可以序列化的,所以条件都达成。

但是这个类没有readObject的方法,所以payload还需要一个能触发这个反序列化类的地方。

根据对yso的源码查看,我发现CommonsCollections系列的readObject都是在sun.reflect.annotation.AnnotationInvocationHandler里触发readObject。

至于这个类,是在gadget.java里写死的。

public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";

在CommonsCollections1里

final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

 

但是目前的构造还需要依赖于触发Map中某一项去调用setValue(),我们需要想办法通过readObject()直接触发
我们观察到java运行库中有这样一个类AnnotationInvocationHandler,这个类有一个成员变量memberValues是Map类型,如下所示
openjdkjdksrcshareclassessun eflectannotationAnnotationInvocationHandler.java

 

因此,我们只需要使用前面构造的Map来构造AnnotationInvocationHandler,进行序列化,当触发readObject()反序列化的时候,就能实现命令执行。另外需要注意的是,想要在调用未包含的package中的构造函数,我们必须通过反射的方式,综合生成任意代码执行的payload的代码如下

 ...待续

 

以上解释了如何通过Apache Commons Collections 3这个库中的代码,来构造序列化对象,使得程序在反序列化时可以立即实现任意代码执行,值得注意的是,POC之所以是通用的原因在于,只要目标系统中使用了Apache Commons Collections 3存在漏洞的代码库,并且调用了readObject(),不管具体的使用场景是怎么样的(即不管反序列化后强制转换为任何的对象),在readObject()被调用的时候攻击就已经成立了
我们可以直接使用工具ysoserial来生成payload,当中包含了4种通用的payload:Apache Commons Collections 3和4,Groovy,Spring,只要目标应用的Class Path中包含这些库,ysoserial生成的payload即可让readObject()实现任意命令执行。
ysoserial当中针对Apache Commons Collections 3的payload也是基于TransformedMap和InvokerTransformer来构造的,而在触发时,并没有采用上文介绍的AnnotationInvocationHandler,而是使用了java.lang.reflect.Proxy中的相关代码来实现触发

以上是关于对ysoserial工具及java反序列化的一个阶段性理解的主要内容,如果未能解决你的问题,请参考以下文章

反序列化工具ysoserial使用介绍

Java反序列化漏洞(ysoserial工具使用shiro反序列化利用)

java安全——ysoserial工具URLDNS链分析

java安全——ysoserial工具URLDNS链分析

java安全——ysoserial工具URLDNS链分析

java安全——ysoserial工具URLDNS链分析