Commons-Collections5原理分析
CC5 依旧是 LazyMap 加 ChainedTransformer 的触发模式,只不过不再使用 AnnotationInvocationHandler 的动态代理来触发 LazyMap 的 get ,而是找到了其他的方式。
因为 jdk 在 1.8 之后对 AnnotationInvocationHandler 类进行了修复,所以在 jdk 1.8 版本就必须找出能替代 AnnotationInvocationHandler 的新的可以利用的类。
前置知识
TiedMapEntry
org.apache.commons.collections.keyvalue.TiedMapEntry 是一个 Map.Entry 的实现类,从名称中可以看到,这是一个绑定了底层 map 的 Entry,用来使一个 map entry 对象拥有在底层修改 map 的功能。
TiedMapEntry 中有一个成员属性 Map,这就是 Map.Entry 的底层 map,TiedMapEntry 的 getValue() 方法会调用底层 map 的 get() 方法,我们可以用来触发 LazyMap 的 get。
那谁会调用 getValue() 方法呢?我们发现 TiedMapEntry 的 equals/hashCode/toString 都可以触发。


equals/hashCode 让我们想到了 URLDNS 的 HashMap,不过在 CC5 中我们用的是 toString() 方法。
接下来需要找到一个类在反序列化时会触发 TiedMapEntry 的 toString() 方法。
BadAttributeValueExpException
于是找到了 javax.management.BadAttributeValueExpException 这个类,反序列化读取 val,当 System.getSecurityManager() == null 或 valObj 是除了 String 的其他基础类型时会调用 valObj 的 toString() 方法,完成上面 TiedMapEntry 的构造。
System.getSecurityManager是java安全管理器,默认关闭。有两种启动方式:
- 命令行参数启动
-Djava.security.manager - 代码启动
System.setSecurityManager(new SecurityManager());
POC
1 | public class CC5 { |

这里有个细节,虽然能够直接通过构造方法赋值给val,但在构造方法中有对入参做toString操作,那在序列化payload时,得到的val就是String而不是map了,所以只能通过反射的方式去赋值给val。

总结
利用说明:
反序列化 BadAttributeValueExpException的readObject 调用 TiedMapEntry 的 toString 方法,继而调用 TiedMapEntry 的 getValue 方法,然后调用了 LazyMap 的 get 方法,触发了后续的 Transformer 恶意执行链。
Gadget 总结:
1 | kick-off gadget:javax.management.BadAttributeValueExpException#readObject() |
调用链展示:
1 | BadAttributeValueExpException.readObject() |
依赖版本
commons-collections : 3.1~3.2.1
jdk 8u76 without a security manager
