环境

jdk 9+
tomcat 8.0.53
spring 5.3.17

正文

demo代码

User.java

class User {
    private String username;

    User() {
    }

    public String getUsername() {
        return this.username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

IndexController.java

@Controller
public class IndexController {

    @RequestMapping("/")
    public String test(User user) {
        return "ok";
    }
}

漏洞成因

调用栈分析

先上调用栈。

getPropertyDescriptor:391, CachedIntrospectionResults (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)
invokeAndHandle:117, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:895, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:808, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1067, DispatcherServlet (org.springframework.web.servlet)
doService:963, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doPost:909, FrameworkServlet (org.springframework.web.servlet)
service:661, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:742, HttpServlet (javax.servlet.http)
internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilter:52, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
invoke:198, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:496, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:140, StandardHostValve (org.apache.catalina.core)
invoke:81, ErrorReportValve (org.apache.catalina.valves)
invoke:650, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:87, StandardEngineValve (org.apache.catalina.core)
service:342, CoyoteAdapter (org.apache.catalina.connector)
service:803, Http11Processor (org.apache.coyote.http11)
process:66, AbstractProcessorLight (org.apache.coyote)
process:790, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1459, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1167, ThreadPoolExecutor (java.util.concurrent)
run:641, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:844, Thread (java.lang)

上述调用栈是spring对传入参数进行解析并和接口设置的javabean的所属类对应属性进行对比的代码实现过程,如存在则注入对应参数值。
在这之前的调用都是spring本身实现这个功能的代码,如感兴趣可以根据调用链查看对应逻辑,不一一赘述。

这里只分析对传入参数和javabean属性对比的核心逻辑如何产生漏洞的成因。
org.springframework.beans.CachedIntrospectionResults#getPropertyDescriptor
从下图debug过程截图可以看出这里是的name参数是从post请求参数分解出的属性值,
入参为class.module.classLoader.resources.context.parent.pipeline.first.suffix,
spring以"."为分割符解析出第一个对比属性为"class"。
这里使用了get方法对比class属性是否存在于org.springframework.beans.CachedIntrospectionResults#propertyDescriptors这个变量中,如存在则获取对应实例,进行后续的参数注入操作。

那么,propertyDescriptors这个变量的值是从哪里获取的呢?
查看org.springframework.beans.CachedIntrospectionResults类的构造方法可以发现进行了put操作,此处下断点发现是解析User类的属性描述获取的。这里虽然对属性做了检查,不过是只限制了class类的classLoader属性,使用module调用classLoader属性就绕过了这里的检查。

PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
这里原本的目的是获取bean的属性,而java类存在一个特性,存在内置的class属性,用于存储类实例,即开发中常用到的User.class这样的引用就是调用了这个属性。
获取到的class实例被作为属性进行属性注入操作,存入了org.springframework.beans.CachedIntrospectionResults#propertyDescriptors。
后续的调用则是迭代class属性,获取对应实例,从而完成变量注入操作修改Tomcat access log配置。

而要求jdk 9+的原因主要低版本的jdk的class类不存在module属性,从而导致网传payload无法利用。从这个原理上看,如果能找到新的属性应该可以扩大影响范围。

修复

spring 5.3.18版本
org.springframework.beans.CachedIntrospectionResults#CachedIntrospectionResults

从上图可以看出在解析获取实例的属性值后,做了属性类型是否是ClassLoader.class和ProtectionDomain.class子类的判断进行防御。
(Class.class != beanClass || "name".equals(pd.getName()) || pd.getName().endsWith("Name")) && (pd.getPropertyType() == null || !ClassLoader.class.isAssignableFrom(pd.getPropertyType()) && !ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))

总结

分析完这个漏洞之后,反思这个漏洞成因的本质就是两个java较为常见的常识的组合应用,形成的巧妙效果。
1.java 内置class属性
2.spring 的javabean的属性注入特性
同时对类和属性的检查不够全面,导致绕过。在平时学习中如果对一下框架的特性安全性有更深的思路,可能会有意外的收获。

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖