声明
出品|博客(ID:moon_flower)
以下内容,来自博客moon_flower作者原创,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,长白山攻防实验室以及文章作者不承担任何责任。
环境搭建
github上拉了一个现成的spring+tmcat环境:https://github.com/winn-hu/interface。可以在其中添加实验用的model和controller。
漏洞成因
这次的CVE-2022-22965其实是CVE-2010-1622的绕过,由参数绑定造成的变量覆盖漏洞,通过更改tomcat服务器的日志记录属性,触发pipeline 机制实现任意文件写入。
SpringMVC的参数绑定机制
演示 demo:
HelloController.java
@Controller
public class HelloController {
@RequestMapping("/index")
public String index(User user) {
return user.toString();
}
}
User.java
package com.moonflower.model;
import com.moonflower.model.info;
public class User {
public String name;
public String age;
public com.moonflower.model.info info;
public User(String name, String age, com.moonflower.model.info info) {
this.name = name;
this.age = age;
this.info = info;
System.out.println("调用了User的有参构造");
}
public User() {
System.out.println("调用了User的无参构造");
}
public String getName() {
System.out.println("调用了User的getName");
return name;
}
public void setName(String name) {
System.out.println("调用了User的setName");
this.name = name;
}
public com.moonflower.model.info getInfo() {
System.out.println("调用了User的getInfo");
return info;
}
public void setInfo(com.moonflower.model.info info) {
System.out.println("调用了User的setInfo");
this.info = info;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", info=" + info +
'}';
}
}
info.java
package com.moonflower.model;
public class info {
public String QQ;
public String vx;
public info(String QQ, String vx) {
this.QQ = QQ;
this.vx = vx;
System.out.println("调用了info的有参构造");
}
public info() {
System.out.println("调用了info的无参构造");
}
public String getQQ() {
System.out.println("调用了info的getQQ");
return QQ;
}
public void setQQ(String QQ) {
System.out.println("调用了info的setQQ");
this.QQ = QQ;
}
public String getVx() {
System.out.println("调用了info的getvx");
return vx;
}
public void setVx(String vx) {
this.vx = vx;
System.out.println("调用了info的setvx");
}
@Override
public String toString() {
return "info{" +
"QQ='" + QQ + '\'' +
", vx='" + vx + '\'' +
'}';
}
}
首先尝试访问/index?
name=moonflower&info.QQ=123&info.vx=13,在执行完toString之后,可以看到传入的name自动绑定到了user.name上,而 info.QQ和info.vx也分别自动绑定到了user.info.QQ和user.info.vx上,这也表明了SpringMVC持多层嵌套的参数绑定。
再看一下输出的内容,能看出参数的绑定先get后set,而对于多层嵌套绑定(info.QQ),则是依次调用了User.getinfo->info.getQQ->info.setQQ
执行参数绑定的函数可以跟进
ServletRequestDataBinder类中
继续跟进到doBind中,发现其又调用了父类的doBind,
在applyPropertyValues中添加参数的值
首先调用getPropertyAccessor获取,Bean-WrapperImpl然后调用setPropertyValues赋值,在setPropertyValues中循环调用setPropertyValue,为每一个propertyname赋值
(图中已经是赋值完 QQ,开始赋值 vx)
然后在setPropertyValue中持续跟进,
一直到 getPropertyAccessorForPropertyPath,
在getPropertyAccessorForPropertyPath中解析了即将绑定的参数(info.vx)
再跟到 getPropertyValue 中
在getLocalPropertyHandler中,BeanWrapperImpl的方法拿到了info类
继续跟到setDefaultValue,在setDefaultValue 又会调用 createDefaultPropertyValue 中
在createDefaultPropertyValue的newValue中可以看到反射构造
这时看一下output,发现已经打印了调用info的无参构造
回到setDefaultValue中,接着调用里setPropertyValue方法,
继续跟进到解析对应的参数,而这里解析到的是一个info类,
就像刚开始说的那样,在当前要绑定的参数 (info) 无法直接赋值的时候,会进行多层嵌套的参数绑定,可以看到程序又会回到 getProperty-AccessorForPropertyPath 中,而且参数从 info.QQ 变成了 QQ,然后继续跟进,就可以看到给对应属性(QQ)的赋值操作
在后续的getValue函数中,通过反射的方法调用了对应的get方法(getQQ),
继续向下跟进到setValue 中,同样也是用反射调用了对应的set方法,此时 output中出现对应打印内容。
大致流程(图来自rui0师傅)
关于JavaBean
在上面的例子中声明的类(User, info)都是JavaBean,一种特殊的类。主要用于传递数据信息,要求方法符合某种命名规则,在这些bean中通常只有信息字段和存储方法,没有功能性方法。
对于JavaBean中的私有属性,可以通过getter/setter方法来访问/设置,在 jdk中提供了一套api来访问某个属性的getter/setter方法,也就是内省。
BeanInfo getBeanInfo(Class beanClass)
BeanInfo getBeanInfo(Class beanClass, Class stopClass)
在获得BeanInfo后,可以通过 PropertyDescriptors 类获取符合JavaBean 规范的对象属性和getter/setter方法。
(如果用IDEA调过前面参数绑定的过程,就会发现在Spring中对JavaBean 的操作不是用getBeanInfo(太麻烦了),而是用BeanWrapperImpl 这个类的各种方法来操作。
BeanWrapperImpl类是BeanWrapper接口的默认实现,可以看作前面提到的PropertyDescriptor的封装,BeanWrapperImpl 对Bean的属性访问和设置最终调用的是PropertyDescriptor。)
demo:
public class demo {
public static void main(String[] args) throws Exception {
BeanInfo userBeanInfo = Introspector.getBeanInfo(User.class);
PropertyDescriptor[] descriptors = userBeanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : descriptors) {
System.out.println("Property: " + pd.getName());
}
}
}
程序跑起来的时候可以发现,User的属性(name,info)及其方法都在 PropertyDescriptor中可以拿到,
但除此之外,还能拿到一个Class类,而且自带一个getClass方法。
这里是因为没有使用stopClass,访问该类的时候访问到了Object.class,而内省机制的判定规则是,只要由getter/setter方法中的一个,就会认为存在一个对应的属性,而碰巧的是,Java中的所有对象都会默认继承Object类,同时它也存在一个getClass方法,这样就解析到了class属性。
如果直接调用:
Introspector.getBeanInfo(Class.class)
可以获取更多信息,包括关键的classLoader。
参考
-
https://paper.seebug.org/1877/
-
http://rui0.cn/archives/1158
-
https://blog.csdn.net/weixin_45794666/article/details/123918066
-
https://www.iteye.com/topic/1123382
-
https://www.exploit-db.com/exploits/33142
-
https://github.com/BobTheShoplifter/Spring4Shell-POC/blob/0c557e85ba903c7ad6f50c0306f6c8271736c35e/poc.py
-
https://github.com/spring-projects/spring-framework/commit/002546b3e4b8d791ea6acccb81eb3168f51abb15
-
https://github.com/apache/tomcat/commit/8a904f6065080409a1e00606cd7bceec6ad8918c
欢迎关注长白山攻防实验室公众号
定期更新优质文章分享