声明
以下内容,均为文章作者原创,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,长白山攻防实验室以及文章作者不承担任何责任。
长白山攻防实验室拥有该文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的副本,包括版权声明等全部内容。声明长白山攻防实验室允许,不得任意修改或增减此文章内容,不得以任何方式将其用于商业目的。
0x01 前置知识
Introspector类
javabean通常是用来做数据封装的java类,为了方便数据的读取与写入,会有getter/setter方法来对数据进行操作,在jdk中有一个Introspector类,通过该类的方法可以获取javabean的相关信息,包括javabean中的属性值,以及属性值对应的getter/setter方法。
对于属性的获取,其实是通过对应的getter/setter方法来确定的,如果存在对应的getter/setter方法或其中的一个,都会认为存在对应的属性,下面通过例子来了解下其他的特性。
新建两个javabean
//OrgBean.java
package com.example.demo.model;
public class OrgBean {
private String orgname;
public String getOrgname() {
return orgname;
}
public void setOrgname(String orgname) {
this.orgname = orgname;
}
}
//TestBean.java
package com.example.demo.model;
public class TestBean extends OrgBean{
public String getTestbeanName() {
return testbeanName;
}
public void setTestbeanName(String testbeanName) {
this.testbeanName = testbeanName;
}
public String testbeanName;
}
//Test.java
package com.example.demo.model;
import java.beans.BeanInfo;
import java.beans.Introspector;
public class Test {
public static void main(String[] args) throws Exception {
BeanInfo beanInfo = Introspector.getBeanInfo(TestBean.class);
System.out.println(beanInfo);
}
}
运行Test.java,会发现beanInfo中的properties中存在着三个属性。
testbeanName是TestBean中的属性,orgname是TestBean继承的OrgBean类中的属性,那class属性是从何而来?
通过前两个属性可以看出getBeanInfo会同时获取到目标类所继承类的属性,而所有java类是继承与Object类的,Object存在着一个名为getClass的方法,所以会识别到class属性,这个class属性也是后面的关键。
点号赋值的过程
使用项目
https://github.com/DDuarte/springshell-rce-poc 来进行测试,稍微改动下代码,新增一个CommBean。
package com.example.demo.model;
public class CommBean {
public String age ;
public CommBean() {
System.out.println("CommBean init");
}
public void setAge(String age) {
System.out.println("commbean setage");
this.age = age;
}
public String getAge() {
System.out.println("commbean GETage");
return age;
}
}
改动下EvalBean
package com.example.demo.model;
public class EvalBean {
public EvalBean() throws ClassNotFoundException {
System.out.println("[+] EvalBean.EvalBean");
}
public CommBean name = new CommBean();
public CommBean getName() {
System.out.println("[+] EvalBean.getName");
return name;
}
public void setName(CommBean name) {
System.out.println("[+] EvalBean.setName");
this.name = name;
}
}
在controller中绑定了EvalBean之后,可以通过如下方式来对CommBean中的age赋值
http://127.0.0.1:8080/index?name.age=xxx 下图是控制台打印结果
下面来分析下是如何设置值的,根据控制台打印的调用顺序,在getName方法处断点,向上寻找会发现在
org.springframework.beans.AbstractNestablePropertyAccessor#getPropertyAccessorForPropertyPath方法中会以点为分割,来解析属性
在解析到最后一个属性前都会进入if分支内,一路会跟到
org.springframework.beans.AbstractNestablePropertyAccessor#getPropertyValue(org.springframework.beans.AbstractNestablePropertyAccessor.PropertyTokenHolder)方法。
该方法会去获取name属性对应的值,其中调用了getLocalPropertyHandler方法,跟进去到
org.springframework.beans.CachedIntrospectionResults#CachedIntrospectionResults方法中。
这个方法是用来创建javabean信息缓存的,其中调用的getBeanInfo方法就利用了Introspector类来获取javabean的信息。
当类的缓存创建后,再次用到该类时就会直接利用缓存的信息,不会再经过该方法,具体的逻辑在
org.springframework.beans.CachedIntrospectionResults#forClass方法中;回到CachedIntrospectionResults方法中,可以看到beaninfo中的properties存储了class,name两个属性。
回到getPropertyValue方法,在获取了类的相关信息后,会调用getvalue方法去掉用对应的getter方法。
本例中会调用getName,然后接着会进行递归的取值,在解析到最后一个属性后,会进入getPropertyAccessor-ForPropertyPath的else分支,return到向上的调用代码。
回退到
org.springframework.beans.AbstractNestablePropertyAccessor#setPropertyValue(org.springframework.beans.PropertyValue)方法中。
nestedPa即为刚刚递归调用的返回值,是一个接着会获取age属性的tokens,
接着调用nestedPa.setPropertyValue来对属性进行赋值,赋值会调用setter方法,对应着本例中的setAge。
总结一下,对于aaa.bbb.ccc的传入,会依次调用getaaa->getbbb->setccc方法,同时aaa属性需要是对应绑定的javabean中的属性。
再思考一下,在上面的分析中,我们看到在生成javabean缓存时,会多出一个class属性,是由于javabean默认继承Object进而识别getClass方法所获得。
那么其实当我们传入class.xxx.yyy时,就会调用getClass->getxxx->setyyy 这也是本次漏洞的起因。
0x02 漏洞分析
本次漏洞其中一部分是对 CVE-2010-1622 的绕过,详细信息参考:
http://rui0.cn/archives/1158
该漏洞主要是利用上文中的class属性来获取到classloader,进而来修改运行时态的某些属性的值。
为了修复该漏洞,spring在前面分析的CachedIntrospectionResults方法
中添加了一些限制。
这导致我们无法通过下面两种方式来获取到classloader
class.protectionDomain.classLoader
class.classLoader
然而在jdk9以上,jdk中的Class类多了一个module属性。
Module类中存在着getClassLoader方法。
那么我们就可以绕过前面
CachedIntrospectionResults中的限制用class.module.classLoader来调用到
getClassLoader方法拿到classloader对象,接着就是寻找利用方法,可以参考S2-020在Tomcat 8下的利用方法。
参考https://nmap.cc/jsjl/41.html
在Spring MVC+Tomcat的环境下可以通过控制access log文件的文件名和内容,来写入shell。
完整的exp可参考:
https://github.com/Mr-xn/spring-core-rce
0x03 漏洞修复
参考:
https://github.com/spring-projects/spring-framework/commit/afbff391d8299034cd98af968981504b6ca7b38c
修补方式依然是对
CachedIntrospectionResults方法进行限制,当beanClass是Class类时,只能缓存Name为结尾的属性同时不会缓存ClassLoader与ProtectionDomain属性。
0x04 参考文章
-
https://my.oschina.net/u/4547531/blog/4346472
-
https://blog.csdn.net/qq_44973159/article/details/106090450
-
https://nmap.cc/jsjl/41.html
-
http://rui0.cn/archives/1158
-
https://github.com/spring-projects/spring-framework/commit/afbff391d8299034cd98af968981504b6ca7b38c
▇ 扫码关注我们 ▇
长白山攻防实验室
学习最新技术知识
原文始发于微信公众号(长白山攻防实验室):CVE-2022-22965分析复现
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论