最新一直在做java的代码审计
无目的的挖洞让人好疲惫
为了让自己不能闲下来
不挖的时候觉得学习一下java的反序列化也是不错的
那么就从最开始的Commons Collections反序列化来学习
java的反序列化操作的函数
java有writeObject()函数可以来执行序列化操作
public class ser {
public static void main(String[] args) throws Exception {
String test = "ckj123";
FileOutputStream fos = new FileOutputStream("ser.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(test);
oos.close();
FileInputStream fis = new FileInputStream("ser.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Object result = ois.readObject();
ois.close();
System.out.println(result);
}
}
这只是对一个字符串的序列化当然也可以对对象进行序列化
对象需要一个Serializable
这个接口
import java.io.*;
class ckj123 implements Serializable{
String test = "test";
}
public class ser {
public static void main(String[] args) throws Exception {
ckj123 test = new ckj123();
FileOutputStream fos = new FileOutputStream("ser.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(test);
oos.close();
FileInputStream fis = new FileInputStream("ser.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
ckj123 result = (ckj123)ois.readObject();
ois.close();
System.out.println(result.test);
}
}
当然也可以重写readObject
defaultReadObject是反序列化要执行的
就可以让他在反序列化的时候弹一个notepad
java的反射机制
学习Commons Collections得先了解一下java的反序列化
java里面执行命令的对象是Runtime
public class cmd {
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
runtime.exec("notepad.exe");
}
}
就会跳出来一个记事本
这样也可以通过java的反射机制来执行
import java.lang.reflect.Method;
public class cmd {
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
Class cls = runtime.getClass();
Method method = cls.getMethod("exec", String.class);
method.invoke(runtime,"notepad.exe");
}
}
也可以通过两次反射来实现
import java.lang.reflect.Method;
public class cmd {
public static void main(String[] args) throws Exception {
Object runtime = Class.forName("java.lang.Runtime").getMethod("getRuntime",new Class[]{}).invoke(null);
Class.forName("java.lang.Runtime").getMethod("exec",String.class).invoke(runtime,"notepad.exe");
}
}
getMethod("方法","方法类型");
invoke("对象实例","参数");
这样就可以任意命令执行了
Commons Collections
Commons Collections中刚好使用了一个反射
不过他把他们封装了一下
成为了一个对象
其中最重要的一个函数
public O transform(final Object input) {
if (input == null) {
return null;
}
try {
final Class<?> cls = input.getClass();
final Method method = cls.getMethod(iMethodName, iParamTypes);
return (O) method.invoke(input, iArgs);
} catch (final NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +
input.getClass() + "' does not exist");
} catch (final IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +
input.getClass() + "' cannot be accessed");
} catch (final InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +
input.getClass() + "' threw an exception", ex);
}
}
可以发现getMethod
和invoke
两个函数都齐了
只需要iMethodName
, iParamTypes
, iArgs
三个参数就行了
他的另外一个函数
刚刚好可以给这三个赋值了
那么来试一下
public class test {
public static void main(String[] args) throws Exception {
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{new String("notepad.exe")});
Object result = invokerTransformer.transform(Runtime.getRuntime()) ;
}
}
都在了=。=
命令执行
看完上面的感觉和反序列化没什么关系呀
在mac上测试的时候发现只有Commons Collections 5 和 Commons Collections 6可以
可能是jdk1.8的原因
Commons Collections 5
ChainedTransformer
的transformer可以调用每一个transformer
的transform
函数
然后就可以用InvokerTransformer
构造一个利用链
Lazymap在每一次调用get的时候都会调用transform函数调用ChainedTransformer的transform
factory通过初始化Lazymap的时候赋值了
TiedMapEntry的getValue函数中使用了get函数就可以调用LazyMap
TiedMapEntry的tostring函数又会调用getvalue
BadAttributeValueExpException的readobject会调用valObj的tostring函数
于是有了利用链
BadAttributeValueExpException -> readobject
TiedMapEntry -> tostring
Lazymap -> get
ChainedTransformer -> transform
InvokerTransformer -> transform
最后成功执行
Runtime runtime = Runtime.getRuntime();
runtime.exec("calc.exe");
Commons Collections 6
有了Commons Collections 5的经验Commons Collections 6就好分析很多了
这次不用BadAttributeValueExpException了
跟了一下这次调用的还是
Lazymap -> get
ChainedTransformer -> transform
InvokerTransformer -> transform
不过 TiedMapEntry 使用的就不是tostring了
而是
这样就可以调用Lazymap的get函数了
那么是怎么来的呢=。=
hashmap的hash函数调用了key的hashcode
只要key可控就可以
可以找到
hashmap的put函数
key和value都是传进来的然后是public方法还会调用 hash(key)
完满
这下只要找一个readobject带有put函数的就行了
一个完美的函数hashset
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
.... ....
// Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
可以看到他的map调用了put函数
而且使用了hashmap
不过进行了判断当前的map是否LinkedHashSet 如果不是就new一个hashmap
hashset的构造函数就是把map初始化hashmap
那么利用链也有了
HashSet -> readobject
HashMap -> put
HashMap -> hash
TiedMapEntry -> hashcode
Lazymap -> get
ChainedTransformer -> transform
InvokerTransformer -> transform
get it!
后记
一直在想为啥传进来字符串foo就变成Runtime了
发现
原来在这里,不管传什么进来都还给你一样的=。=太坏了
到最后终于跟通了=。=
真不容易。。。
这个debug的时候还会提前跳出来计算机让我懵逼了一个礼拜
最后把断点下到这里才行=。=
然而跟的时候他一直走的是下面的else那一段让我懵逼好久
得自己下断点不能跟着跑(牢记牢记@!)