一、风险描述
集团公司近期通知,根据相关安全纰漏,对Fastjson反序列化远程代码执行漏洞提出预警,开源Java开发组件Fastjson存在反序列化远程代码执行漏洞。攻击者可利用上述漏洞实施任意文件写入、服务端请求伪造等攻击行为,造成服务器权限被窃取、敏感信息泄漏等严重影响,特要求限期排查整改。
Fastjson是阿里巴巴公司开源的一款 JSON解析库/解析器,fastjson.jar是阿里开发的一款专门用于Java开发的包,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到Java Bean。由于具有执行效率高的特点,Fastjson被众多java软件作为组件集成,广泛存在于java应用的服务端代码中。尤其很多公司的web项目全是java写的,很多后台应用也是基于java,故本次风险影响较大,风险提示级别为高风险。
Fastjson官方发布的公告称:在1.2.80及以下版本中存在新的反序列化风险,在特定条件下可绕过默认autoType关闭限制,从而反序列化有安全风险的类,攻击者利用该漏洞可实现在目标机器上的远程代码执行。
风险影响: Fastjson ≤1.2.80 版本----->此次事件影响Fastjson 1.2.80及之前所有版本。目前, Fastjson最新版本1.2.83已修复该漏洞。
本文记录此过程,为后续小伙伴们提供类似经验参考。
二、现场核实确认
1)在web目录或后又java项目录下查找
find ./ -iname fastjson-*.jar //一般在你的项目下/tomcat-8.5.51_8081/webapps/project_name/WEB-INF/lib/
现场核实采用:fastjson-1.2.41
2)获得fastjson:
在maven中如何配置fastjson依赖, fastjson最新版本都会发布到maven中央仓库,我们可以直接依赖。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version> 1.2.83</version>
</dependency>
之后执行:
mvn dependency:tree -Dverbose -Dincludes=com.alibaba:fastjson
3)漏洞复现或扫描原理
寻找存在 Fastjson 漏洞的方法,就是先找到参数中内容是 json 数据的接口,然后使用构造好的测试 payload 进行提交验证,检测原理跟 sql 注入差不多,更多参看Fastjson漏洞复现。
参考2:Fastjson 反序列化漏洞反弹Shell详细步骤
三、漏洞修复
3.1 修复建议
1)用户可通过将版本升级到1.2.83修复此漏洞,但由于该版本涉及autotype行为变更,在某些场景会出现不兼容的情况,请综合考虑风险升级操作。v1.2.25 之前版本autotype默认开启的,从v1.2.25开始,fastjson默认关闭了autotype支持,并且加入了checkAutotype,加入了黑名单+白名单来防御autotype开启的情况。
wget --no-check-certificate https://github.com/alibaba/fastjson/archive/refs/tags/1.2.83.tar.gz
fastjson 三个主要的类:
1)JSONObject :代表 json 对象;JSONObject实现了Map接口, JSONObject底层操作应该是由Map实现的,JSONObject对应json对象,通过各种形式的get()方法可以获取json对象中的数据,也可利用诸如size(),isEmpty()等方法获取"键:值"对的个数和判断是否为空。其本质是通过实现Map接口并调用接口中的方法完成的;
2)JSONArray:代表 json 对象数组;内部是有List接口中的方法来完成操作的;
3)JSON:代表JSONObject和JSONArray的转化;JSON类源码分析与使用
仔细观察上述这些方法,主要是实现json对象,json对象数组,javabean对象,json字符串之间的相互转化;
springMVC.xml:
查看jar包下的pom.xml文件:(同样pom.properties里也有相关内容)
注意: 对于WEB-INF/lib下jar包的更新,升级前备份,可将要升级的jar移出lib目录,或修改jar包名称后缀为其他格式,否则还会加载;且根据java项目特性,.jar包的位置应放在web/WEB-INF/lib目录下才能生效;对于/common/lib/下的jar文件,若更新或新增了,需重启Tomcat服务器,才能重新加载jar包,使jar包生效,而jar包内单个文件可直接在线修改,无需这样;同样,对于application的WEB-INF/lib下的jar文件更新,则可以不重启tomcat便能使之生效,做法是修改$CATALINA_HOME/conf/context.xml 文件,在Context节点上添加属性reloadable=“true”,该属性默认是false。该属性设置为true,Tomcat服务器在运行状况下会监视在WEB-INF/classess和WEB-INF/lib目次下的class文件的改动,以及监视web应用的WEB-INF/web.xml文件的改动。要是检测到的class
文件或者web.xml文件被更新,服务器会自动加载Web应用。该属性的默认值为false.在web应用的开发和调试阶段,把reloadable设为true,可以方便对web应用的调试。在web应用正式发布阶段,把reloadable设为false,可以减低tomcat的运行负荷,提高Tomcat的运行性能。但在现场的Tomcat 8.5.51中检查context文件如下所示:
实际,Tomcat并不提供任何重新加载单个JAR的机制;但是,可以重新加载整个上下文,我们只需要告诉Tomcat在context.xml中监视我们的JAR文件即可,如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<Context override="true" swallowOutput="true" useNaming="false">
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>WEB-INF/lib/your.jar</WatchedResource>
<Manager pathname=""/>
</Context>
但生产环境中,相关经验表明,可能会存在应用程序编码较差,tomcat重新热部署jar包,会存在无法清理资源或在部署时卸载类不完全,这将导致某些类保留在内存中,重新部署应用程序的较新版本时,由于存在较旧版本的类,将会遇到一些异常的错误。为避免任何类似的问题,最好还是先关闭Tomcat,验证Tomcat的文件系统目录的"健全性",删除任何遗留的资源,重新启动Tomcat实例并重新部署应用程序。另外注意的是,高版本系统的fastjson序列化的类,在低版本系统无法反序列化,因此,系统如果存在多个fastjson实例通信 ,要全部保持版本一致性,全部升级。
修改后:
<Context
reloadable="true"
……
</Context>
升级链接:https://github.com/alibaba/fastjson/releases
2)1.2.68及之后版本的用户若无法通过版本升级来修复漏洞,可考虑配置开启safeMode。
在1.2.68版本中,fastjson增加了safeMode的支持。safeMode打开后,将完全禁用autoType。这可能会有兼容问题,请充分评估对业务影响后开启。
具体参考:https://github.com/alibaba/fastjson/wiki/fastjson_safemode。
最简单的是JVM启动参数增加: -Dfastjson.parser.safeMode=true
3)升级到Fastjson v2(也存在兼容1.x的风险)
Fastjson已经开源2.0版本,在2.0版本中,不再为了兼容提供白名单,提升了安全性。Fastjson v2代码已经重写,性能有了很大提升,不完全兼容1.x,升级需要做认真的兼容测试。
MAVEN依赖配置:
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.4</version>
</dependency>
1.x 兼容版本:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.4</version>
</dependency>
4)漏洞原理:
参看Fastjson漏洞以及原理分析。
3.2 排查
1)Maven:排查pom.xml,通过搜索Fastjson确定版本号
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
2)其他项目通过搜索jar文件确定Fastjson版本号
3.3 修复处理
替换掉项目下的fastjson.jar包,现场实际先替换后,再停止Tomcat后报如下错误,后经验证非该包原因:
注意替换后赋予执行权限:
chmod 640 ./webapps/project/WEB-INF/lib/fastjson-1.2.83.jar
四、验证处理
业务日志确认及页面功能确认,如果有json接口,可以postman测试。
github上有大佬编写了检测脚本:wget https://github.com/mrknow001/fastjson_rec_exploit/archive/refs/heads/master.zip
用法参照:https://github.com/mrknow001/fastjson_rec_exploit
五、附录
5.1 Tomcat加载jar包/类文件顺序
1)Tomcat的运行加载的jar包和类文件顺序
1:加载TOMCAT_HOME/lib文件中的jar包
2:加载TOMCAT_HOME/webapps/WEB-INF/lib中的jar包
3:加载TOMCAT_HOME/webapps/WEB-INF/class中的.class文件
注意:与java虚拟机的父类委托机制不一样,tomcat后加载的类会 覆盖 前面加载的 相同类
5.2 Tomcat servlet加载的历程操作
当服务器开启时,会读取web.xml中的配置信息,tomcat会创建一个servlet池(hashMap)
服务器启动时,servlet并未启动,当第一次访问该servlet时,才会创建
1) 第一次访问(创建):
1:执行servlet的构造函数
2:执行初始化init(ServletConfig config)函数
3:在访问时执行service(ServletRequest req, ServletResponse res)函数
2 )再次访问:
从servlet池中取出对应的servlet执行service(ServletRequest req, ServletResponse res)函数
服务器关闭:池中每一个servlet执行各自的destroy()函数
1: 服务器启动时,可是通过配置使得sevlet在访问之前创建完成
<load-on-startup>15</load-on-startup> #中间的要求是一个>0的数且不同,当多个servlet配置时,按照数字大小来 排队启动
2:配置参数: 通过init(ServletConfig config)函数的config.getInitparameten(“name”)来获取
<init-param>
<param-name>name</param-name>
<param-value>Jack</param-value>
</init-param>
5.3 jar包一些查看操作
jar tvf jar名称 | grep 目标文件名 #查询出目标文件在jar包中的目录
jar xvf jar名称 目标文件名(copy上面查出的全路径) #将目标文件及所在jar包中的目录解压到当前路径,修改目标文件的内容,或者将要新的目标文件替换掉提取出来的目标文件
jar uvf jar名称 目标文件名(上步骤中相同的目标文件名) #将新目标文件打包到jar包中
jar ftv jar名称 #查看jar包的目录结构
参数说明:
用法: jar {ctxui}[vfmn0PMe] [jar-file] [manifest-file] [entry-point] [-C dir] files ...
选项:
-c 创建新档案
-t 列出档案目录
-x 从档案中提取指定的 (或所有) 文件
-u 更新现有档案
-v 在标准输出中生成详细输出
-f 指定档案文件名
-m 包含指定清单文件中的清单信息
-n 创建新档案后执行 Pack200 规范化
-e 为捆绑到可执行 jar 文件的独立应用程序
指定应用程序入口点
-0 仅存储; 不使用任何 ZIP 压缩
-P 保留文件名中的前导 '/' (绝对路径) 和 ".." (父目录) 组件
-M 不创建条目的清单文件
-i 为指定的 jar 文件生成索引信息
-C 更改为指定的目录并包含以下文件
如果任何文件为目录, 则对其进行递归处理。
清单文件名, 档案文件名和入口点名称的指定顺序
与 'm', 'f' 和 'e' 标记的指定顺序相同。
示例 1: 将两个类文件归档到一个名为 classes.jar 的档案中:
jar cvf classes.jar Foo.class Bar.class
示例 2: 使用现有的清单文件 'mymanifest' 并将 foo/ 目录中的所有文件归档到 'classes.jar' 中:
jar cvfm classes.jar mymanifest -C foo/
- 创建可执行的 JAR 文件包:
制作一个可执行的 JAR 文件包来发布你的程序是 JAR 文件包最典型的用法。Java 程序是由若干个 .class 文件组成的。这些 .class 文件必须根据它们所属的包不同而分级分目录存放;运行前需要把所有用到的包的根目录指定给 CLASSPATH 环境变量或者 java 命令的 -cp 参数;创建可执行的 JAR 文件包,执行:
jar cvfm test.jar manifest.mf test
注意:
其中,test.jar 和 manifest.mf 两个文件,分别是对应的参数 f 和 m,其重头戏在 manifest.mf。因为要创建可执行的 JAR 文件包,光靠指定一个 manifest.mf 文件是不够的,因为 MANIFEST 是 JAR 文件包的特征,可执行的 JAR 文件包和不可执行的 JAR 文件包都包含 MANIFEST。关键在于可执行 JAR 文件包的 MANIFEST,其内容包含了 Main-Class 一项。在test 目录的上级目录中去使用 jar 命令来创建 JAR 文件包,完成后,test.jar 就是执行的 JAR 文件包。运行时只需要使用 java -jar test.jar 命令即可。如果不指定 manifest.mf,这时生成的jar包不能使用java -jar XXX.jar命令执行,因为MANIFEST文件中未指定程序入口。MANIFEST文件描述了打包后的jar文件的详细信息,存在于打包后的META-INF 的文件夹。主要就是Manifest-Version Main-Class Class-Path这三个属性,其中,.Manifest-Version 是版本号,照着写就行。Main-Class则是jar包的入口程序,指定运行的类的全称(一定要包含包名),这样可以使用java -jar name.jar直接运行jar包。第三个Class-Path是指的打包时需要依赖的其他jar包,打包的时候自己的程序中也可能含有其他的jar包所以要添加依赖。另外注意:每个MANIFEST属性冒号与内容之间都有一个空格,并且写完后最后还要留有一行空行,不然运行时还是出现找不到主清单属性的错误
- 将编译的class字节码文件进行打包输出
1)使用javac命令将这需要的class文件进行编译:
javac -d ./test.java ./test2.java #生产的文件除编译的class文件外,会多一个META-INF文件夹,里面有一个MANIFEST.MF(清单文件)的文件,这个文件的作用非常重要,其内容一般只包含清单版本与java版本。
2)用jar指令将编译的class文件打包
java cvf test.jar ./test.class ./test2.class #这种不指定清单文件的有问题,可按如下执行
比如将所有要打包的class文件存在的目录以及依赖的jar包全部放在一个根文件夹里面(比如是foo),,然后编写MANIFEST清单文件,指定程序入口以及其他添加的依赖的jar包。再执行指令:
jar cvfm classes.jar mymanifest -C foo/
5.4 windows 版的wget
http://www.interlog.com/~tcharron/wgetwin-1_5_3_1-binary.zip
然后配置环境变量,右键计算机->属性->高级系统设置->高级->环境变量->选中PATH->编辑:
1)设置一个名为“WGETRC”的环境变量,它指向 wgetrc 文件的完整路径名
2)设置一个名为“HOME”的环境变量(如果它不存在)指向一个目录。将您的 wgetrc 文件放在此目录中,并将其命名为“wgetrc”
用法:wget -P 目录 网址
举例来说,如果你要放到/root底下,你可以打下列的指令:
wget -P /root 网址
wget -P D:xxx.zip http://www.xdown.com/xxx.zip
wget -O “D:xxx.zip” http://www.xdown.com/xxx.zip
wget -r -p -k -np -nc -e robots=off http://www.example.com/mydir/
如果要想下载整个网站,最好去除-np参数。
wget -r -p -k -nc -e robots=off http://www.example.com/mydir/
-r 递归;对于HTTP主机,wget首先下载URL指定的文件,然后(如果该文件是一个HTML文档的话)递归下载该文件所引用(超级连接)的所有文件(递 归深度由参数-l指定)。对FTP主机,该参数意味着要下载URL指定的目录中的所有文件,递归方法与HTTP主机类似。
-c 指定断点续传功能。实际上,wget默认具有断点续传功能,只有当你使用别的ftp工具下载了某一文件的一部分,并希望wget接着完成此工作的时候,才 需要指定此参数。
-nc 不下载已经存在的文件
-np 表示不追溯至父目录,不跟随链接,只下载指定目录及子目录里的东西;
-p 下载页面显示所需的所有文件。比如页面中包含了图片,但是图片并不在/yourdir目录中,而在/images目录下,有此参数,图片依然会被正常下 载。
-k 修复下载文件中的绝对连接为相对连接,这样方便本地阅读。
-o down.log 记录日记到down.log
-e robots=off 忽略robots.txt