前言:记录一篇自己入门java安全的故事,捋一下思路,轻量知识 ,重在调试 !
.
这篇文章四个部分:
引入篇:整理一下CVE-2022-22965漏洞的来龙去脉
基础篇:回顾Java中一些基础的内容
调试篇:阅读Spring MVC部分源码
分析篇:分析篇:分析CVE-2010-1622、CVE-2022-22965的漏洞成因
.
调试篇
( 紧接" 浅谈CVE-2022-22965漏洞成因(三)”,继续调试Spring MVC框架的执行流程 )
5、调试获取HandlerAdapter的过程
#490 HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler())
mappedHandler是一个处理器和拦截器的封装,调用HandlerExecutionChain的getHandler会把HandlerExecutionChain的Handler转成Object
getHandlerAdapter最终会返回一个RequestMappingHandlerAdapter
HandlerAdapter用于调用handler中的方法处理请求并返回一个ModelAndview,我们从handlerMappings获取到相应的handler后,都是通过HandlerAdapter来执行handler
程序执行时默认会初始化三个HandlerAdapter实现类(也就是我们上面提到)
以及RequestMappingHandlerAdapter(@RequestMapping等注解标注专用),主要有两个功能:
- 调用HttpMessageConverter、转换器、构造器将Http请求参数转换成java类型
- 使用HandlerMethodArgumentResolver进行参数绑定
HandlerAdapters是HandlerAdapter的集合(参考前面的handlerMappings)
HandlerMapping返回给DispatcherServlet一个HandlerExcutionChain时,DispatcherServlet就会调用getHandlerAdapter方法遍历HandlerAdapters,并调用每个HandlerAdapter的support方法,判断具体使用哪个HandlerAdapter。
6、调试HandlerAdapte调用handle的过程
下面我们带着目的进行调试:对于前端传过来的参数Spring MVC 是如何绑定到控制器方法参数上的,即:
nick=isee&hobby[0]=travel&job=mbricks
=>
FirstController info(People people)
=>
pojo.People
504 mv = ha.handle(processedRequest, response, mappedHandler.getHandler())
调用HandlerAdapter处理器适配器的handle方法,HandlerAdapter是一个接口,实际调用的是AbstractHandlerMethodAdapter的handle方法
可以看到handel方法里传入的handel是一个 controller .FirstController#info(People)
,接着调用this.handleInternal方法进行处理,同时在这里将handel强转成一个HandlerMethod,也就是说@RequestMapping标记的方法最终都是HandlerMethod类型的
对于HandlerMethod,形如@Controller、@RequestMapping这些注解标注的控制器方法最终的handeler是一个HandlerMethod
HandlerMethod中存放了控制器类以及其对应的方法,也就是说获得一个HandlerMethod就可以调用我们定义的class FirstController里的处理方法,如welcome()或info(People people)
public class HandlerMethod {
protected final Log logger = LogFactory.getLog(this.getClass());
private final Object bean;
private final BeanFactory beanFactory;
private final Class<?> beanType;
private final Method method;
private final Method bridgedMethod;
private final MethodParameter[] parameters;
private HttpStatus responseStatus;
private String responseStatusReason;
private HandlerMethod resolvedFromHandlerMethod;
private volatile List<Annotation[][]> interfaceParameterAnnotations;
private final String description;
...
}
handleInternal是一个抽象方法,接下来将调用其子类实现的handleInternal方法
RequestMappingHandlerAdapter类实现了handleInternal方法,并调用invokeHandlerMethod方法进行处理,也就是说HandlerAdapter的handle方法最终是通过子类适配器通过实现handleInternal方法来实现的
invokeHandlerMethod方法比较长,会进行返回值处理器以及请求响应封装,WebDataBinderFactory,ModelFactory,ModelAndViewContainer等的创建与配置,可大致归纳为:
```
......
将handlerMethod封装ServletInvocableHandlerMethod
510 ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod)
设置ServletInvocableHandlerMethod
包括配置已注册的参数解析列表、返回值处理器以及BinderFactory、ModelAndViewContainer相关
...
调用ServletInvocableHandlerMethod的invokeAndHandle方法
543 invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
......
```
将handlerMethod封装ServletInvocableHandlerMethod,后面的许多操作都是通过ServletInvocableHandlerMethod来做的
其他配置项
.
在RequestMappingHandlerAdapter中执行invocableMethod.invokeAndHandle后进入ServletInvocableHandlerMethod。
.
在ServletInvocableHandlerMethod的invokeAndHandle方法中,有handel最终执行前参数解析与绑定和handel执行后返回值解析与处理的逻辑
```
51 Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
...
68 this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
```
我们这里关注它的 invokeForRequest方法
ServletInvocableHandlerMethod继承自InvocableHandlerMethod,ServletInvocableHandlerMethod调用this.invokeForRequest方法会调用到InvocableHandlerMethod的invokeForRequest方法
InvocableHandlerMethod.invokeForRequest方法调用this.getMethodArgumentValues方法
getMethodArgumentValues方法中会遍历参数解析器列表,实际上是一个HandlerHethodArgumentResolverComposite,并调用它们的supportsParameter判读参数类型与参数解析器是否匹配,最后通过resolveArgument方法进行参数处理,实际上都是基于HandlerMethodArgumentResolver接口来做的
```
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter var1);
@Nullable
Object resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;
}
```
我们看一下InvocableHandlerMethod.getMethodArgumentValues中这两个下断点的地方:
```
74 if (!this.resolvers.supportsParameter(parameter)) {
...
79 args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory)
```
#74 this.resolvers.supportsParameter(parameter)
调用HandlerMethodArgumentResolverComposite的supportsParameter方法
supportsParameter方法调用getArgumentResolver方法获得了一个ServletModelAttributemethodProcessor参数解析器
#79 args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory)
调用HandlerMethodArgumentResolverComposite的resolveArgument方法
HandlerMethodArgumentResolverComposite再次调用getArgumentResolver方法获得ServletModelAttributemethodProcessor参数解析器
此时的resolver为ServletModelAttributemethodProcessor
ServletModelAttributemethodProcessor继承自ModelAttributeMethodProcessor
调用ServletModelAttributemethodProcessor.resolveArgument方法会调用到ModelAttributeMethodProcessor.resolveArgument方法
重点关注如下地方:
```
62String name = ModelFactory.getNameForParameter(parameter)
89 WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name)
92 this.bindRequestParameters(binder, webRequest)
105 bindingResult = binder.getBindingResult()
```
#62 String name = ModelFactory.getNameForParameter(parameter)
#89 WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name)
#92 this.bindRequestParameters(binder, webRequest)
从这里开始才真正进入参数解析与绑定阶段
#92 this.bindRequestParameters(binder, webRequest)
ServletModelAttributeMethodProcessor.bindRequestParameters具体实现lModelAttributeMethodProcessor.bindRequestParameters方法,之后调用ServletRequestDataBinder.bind方法
ServletRequestDataBinder.bind方法里开始获取并设置请求参数并执行this.doBind方法(父类WebDataBinder的doBind方法)
WebDataBinder的doBind方法,请求参数的信息之前已经被存储在PropertyValue和MutablePropertyValues相关类中
doBind之前检查请求参数是否合法
调用父类DataBinder的doBind方法,两次参数检查后调用this.applyPropertyValues方法
DataBinder.applyPropertyValues方法
调用this.getPropertyAccessor方法获取的PropertyAccessor是ConfigurablePropertyAccessor类型,该类的父类是PropertyAccessor
setPropertyValues方法是一个抽象方法,setPropertyValues方法具体的实现类为AbstractPropertyAccessor,因此DataBinder调用this.getPropertyAccessor().setPropertyValues最终会执行到AbstractPropertyAccessor.setPropertyValues方法
从这里起,将开始遍历参数并一个一个进行处理
在AbstractPropertyAccessor.setPropertyValues方法,调用this.setPropertyValue方法,该方法的具体实现类在AbstractNestablePropertyAccessor
进入AbstractNestablePropertyAccessor,AbstractNestablePropertyAccessor开始每个参数的解析
下断点的这两个地方非常重要,一个用来解析前端传过来的参数是否是递归的形式,一个用来对Bean属性赋值(这里是pojo.people)
```
153 nestedPa = this.getPropertyAccessorForPropertyPath(propertyName)
163 nestedPa.setPropertyValue(tokens, pv)
```
#153 nestedPa = this.getPropertyAccessorForPropertyPath(propertyName)
如果参数是hobby1.hobby2.hobby3=eat这种形式,将会进入pos的逻辑
#163 nestedPa.setPropertyValue(tokens, pv)
我们接着看看对于数组类型的参数,它是如何赋值的
AbstractNestablePropertyAccessor.processKeyedProperty
该方法里具体实现了根据请求参数获取Bean属性以及设置Bean属性的逻辑,对于数组类型的Bean属性,这里是直接通过数组底层赋值进行设置的,并不是通过反射调用Bean的Setter方法来进行设置
```
获取对应参数的Bean的实际属性值
180 Object propValue = this.getPropertyHoldingValue(tokens)
获取PropertyHandler用来操作Bean属性
181 AbstractNestablePropertyAccessor.PropertyHandler ph = this.getLocalPropertyHandler(tokens.actualName)
参数到Bean属性的类型转换
200 convertedValue = this.convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(), requiredType, ph.nested(tokens.keys.length))
```
#180 Object propValue = this.getPropertyHoldingValue(tokens)
AbstractNestablePropertyAccessor的getLocalPropertyHandler是一个接口,BeanWrapperImpl实现了该接口方法
PropertyDescriptor是通过CachedIntrospectionResults获取的,BeanWrapperImpl.getLocalPropertyHandler获取PropertyDescriptor后把它转成了一个BeanWrapperImpl.BeanPropertyHandler类型
BeanWrapperImpl.BeanPropertyHandler继承自AbstractNestablePropertyAccessor.PropertyHandler,里面的getValue/setValue具体实现了操作Bean属性的过程
AbstractNestablePropertyAccessor类下的PropertyHandler
```
protected abstract static class PropertyHandler {
private final Class<?> propertyType;
private final boolean readable;
private final boolean writable;
public PropertyHandler(Class<?> propertyType, boolean readable, boolean writable) {
this.propertyType = propertyType;
this.readable = readable;
this.writable = writable;
}
public Class<?> getPropertyType() {
return this.propertyType;
}
public boolean isReadable() {
return this.readable;
}
public boolean isWritable() {
return this.writable;
}
public abstract TypeDescriptor toTypeDescriptor();
public abstract ResolvableType getResolvableType();
@Nullable
public Class<?> getMapKeyType(int nestingLevel) {
return this.getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(new int[]{0});
}
@Nullable
public Class<?> getMapValueType(int nestingLevel) {
return this.getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(new int[]{1});
}
@Nullable
public Class<?> getCollectionType(int nestingLevel) {
return this.getResolvableType().getNested(nestingLevel).asCollection().resolveGeneric(new int[0]);
}
@Nullable
public abstract TypeDescriptor nested(int var1);
@Nullable
public abstract Object getValue() throws Exception;
public abstract void setValue(@Nullable Object var1) throws Exception;
}
```
BeanWrapperImpl类
BeanWrapperImpl是BeanWrapperI的实现类,BeanWrapperI是Bean的包装类,BeanWrapperImpl通过PropertyDescriptor(实际上是通过Spring自己的PropertyDescriptor)具体实现了Bean属性的获取和读取,BeanWrapperImpl获取或修改Bean的Property最终是通过反射调用Bean自己的getter/setter方法实现
```
private class BeanPropertyHandler extends PropertyHandler {
private final PropertyDescriptor pd;
public BeanPropertyHandler(PropertyDescriptor pd) {
super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null);
this.pd = pd;
}
public ResolvableType getResolvableType() {
return ResolvableType.forMethodReturnType(this.pd.getReadMethod());
}
public TypeDescriptor toTypeDescriptor() {
return new TypeDescriptor(BeanWrapperImpl.this.property(this.pd));
}
@Nullable
public TypeDescriptor nested(int level) {
return TypeDescriptor.nested(BeanWrapperImpl.this.property(this.pd), level);
}
@Nullable
public Object getValue() throws Exception {
Method readMethod = this.pd.getReadMethod();
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(() -> {
ReflectionUtils.makeAccessible(readMethod);
return null;
});
try {
return AccessController.doPrivileged(() -> {
return readMethod.invoke(BeanWrapperImpl.this.getWrappedInstance(), (Object[])null);
}, BeanWrapperImpl.this.acc);
} catch (PrivilegedActionException var3) {
throw var3.getException();
}
} else {
ReflectionUtils.makeAccessible(readMethod);
return readMethod.invoke(BeanWrapperImpl.this.getWrappedInstance(), (Object[])null);
}
}
public void setValue(@Nullable Object value) throws Exception {
Method writeMethod = this.pd instanceof GenericTypeAwarePropertyDescriptor ? ((GenericTypeAwarePropertyDescriptor)this.pd).getWriteMethodForActualAccess() : this.pd.getWriteMethod();
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(() -> {
ReflectionUtils.makeAccessible(writeMethod);
return null;
});
try {
AccessController.doPrivileged(() -> {
return writeMethod.invoke(BeanWrapperImpl.this.getWrappedInstance(), value);
}, BeanWrapperImpl.this.acc);
} catch (PrivilegedActionException var4) {
throw var4.getException();
}
} else {
ReflectionUtils.makeAccessible(writeMethod);
writeMethod.invoke(BeanWrapperImpl.this.getWrappedInstance(), value);
}
}
}
```
可以看出PropertyHandler实际就是操作Property的封装
获取到ph之后要再获取People的hobby属性的值就好办了,先判断hobby属性是否可读,可读直接ph.getValue拿值
#181 AbstractNestablePropertyAccessor.PropertyHandler ph = this.getLocalPropertyHandler(tokens.actualName)
#200 convertedValue = this.convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(), requiredType, ph.nested(tokens.keys.length))
对于数组类型的变量,之后直接调用底层赋值,第一个参数解析与绑定完成
第二参数解析与绑定开始
与第一个参数不同,此时的tokens.keys是一个null值,程序走的是this.processLocalProperty这个流程
对于AbstractNestablePropertyAccessor.processLocalProperty这个方法,正常的执行流程是
```
获取PropertyHandler
295 AbstractNestablePropertyAccessor.PropertyHandler ph = this.getLocalPropertyHandler(tokens.actualName)
获取Bean属性原始值
309 oldValue = ph.getValue()
参数类型转换
valueToApply = this.convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor())
对Bean赋值
ph.setValue(valueToApply)
```
但是由于People的job属性不可写,后面会直接异常捕捉并退出
最后进行第三个参数解析
此时的tokens.keys是一个null值,程序走的是this.processLocalProperty这个流程,之所以这样,可能是AbstractNestablePropertyAccessor.getPropertyNameTokens负责数组类型变量的多次解析因此给了个tokens值
再后面就很顺利了,AbstractNestablePropertyAccessor.processLocalProperty正常执行
```
获取PropertyHandler
295 AbstractNestablePropertyAccessor.PropertyHandler ph = this.getLocalPropertyHandler(tokens.actualName)
获取Bean属性原始值
309 oldValue = ph.getValue()
参数类型转换
valueToApply = this.convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor())
对Bean赋值
ph.setValue(valueToApply)
```
参数绑定完成后就是返回值封装与视图解析的过程,即返回ModelAndview并由DispatcherServlet调用ViewReslver作视图解析
这里我们就不作过多讨论,另外就是参数处理器调用resolveArgument处理请求时通常用这个接口进行HTTP参数到java类型的转换
.
最后再通过转换器或是格式化器就能将请求参数转为更加丰富类型
.
综上,我们对于Spring MVC的参数绑定过程有了一定认识,以及Spring MVC框架的变量覆盖BUG的成因有了一定的了解。即,对于数组类型的参数在绑定到pojo属性时,并不是通过getter/setter方法,而是直接进行了数组底层赋值。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论