最新一直在做java的代码审计
无目的的挖洞让人好疲惫
为了让自己不能闲下来
不挖的时候觉得学习一下java的反序列化也是不错的
那么就从最开始的Commons Collections反序列化来学习
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
学习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中刚好使用了一个反射
不过他把他们封装了一下
成为了一个对象
其中最重要的一个函数
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的原因
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 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那一段让我懵逼好久
得自己下断点不能跟着跑(牢记牢记@!)