SpringBeansRCE的延申学习(CVE-2010-1622漏洞分析)

[toc]

前言

由于spring的参数绑定通过内省机制递归获得对应参数的discriptor,最终通过其writeMethod也就是set方法对其赋值,那么我们可以看通过class类可以到达什么对象,通过对其中的属性进行修改,可以达到什么样的利用效果。由于springRCE的漏洞是对CVE-2010-1622的绕过,我们要先了解下CVE-2010-1622。

前置知识

在写上篇文章时和开始写这篇文章前通过多次调试,理解了在spring中属性注入的内省的流程,总结如下:

在BeanWrapper第一次通过aaa.bbb.ccc进行属性注入时,先通过递归,创建好a,b,c对应的BeanWrapper,依次嵌套,储存在最外侧的BeanWrapper.nestedPropertyAccessors中。

在每次递归中:比如先通过对aaa内省(在CachedIntrospectionResults中通过Introspector.getBeanInfo内省。然后将获得的propertyDescriptor都放在其propertyDescriptors中,readMethod放在其readMethods中。在这一步,中进行了判断,如果当前类的名称为”Class”且propertyDescriptor的name为classLoader,则跳过,即跳过了class.classLoader。(为了修复CVE-2010-1622))

如果aaa的propertyAccessors缓存中bbb的propertyAccessor是null的话,就通过setDeafultValue–>BeanUtils.instantiateClass(bbb.Class)创建一个实例,然后重复。这其中内省得到的结果都被缓存。

在之后进行对aaa.bbb.ccc赋值时,直接从其中取到bbb对应的BeanWrapper即AbstractNestablePropertyAccessor。

然后通过getLocalPropertyHandler(“ccc”)得到其PropertyHandler(PropertyHandler是对PropertyDiscriptor的封装)getLoaclPropertyHander会从CachedIntrospectionResults中去取。然后通过writeMethod.invoke去赋值。

CVE-2010-1622

影响版本

1
2
3
4
5
6
3.0.0 to 3.0.2
2.5.0 to 2.5.6.SEC01 (community releases)
2.5.0 to 2.5.7 (subscription customers)

tomcat < 6.0.28
spring 使用了表单标签功能

漏洞成因

参数绑定的过程中,由于spring对内省的实现,对于Bean中的List,Map,Set,不需要具有其set方法,也可以对其进行赋值。刚好可以赋值到class.classLoader.URLs[]。

  • 在tomcat中class.classLoader为org.apache.catalina.loader.WebappClassLoader。
  • 而WebappClassLoader继承URLClassLoader,其中包含getURLs方法,返回一个数组。

而Jasper’s TldLocationsCache 会从WebappClassLoader里面读取url参数并用来解析TLD文件(自定义标签使用)。所以是输入可以控制程序自定义一个恶意的JSP标签从而RCE。

JSP自定义标签

在JSP中有apache官方实现的标签库JSTL。

为了方便开发,我们也可以自定义标签。为了理解漏洞利用方式,所以实操一下自定义一个标签。

首先创建一个JSP项目

写一个Tag类,要继承SimpleTagSupport

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.pyrrhax;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;

public class HelloTag extends SimpleTagSupport {
public void doTag() throws JspException, IOException{
JspWriter out = getJspContext().getOut();
out.println("Hello Custom Tag");
}
}

然后在WEB-INF里面写一个.tld文件(tag library discriptor),内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="ISO-8859-1"?>

<taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">

<tlib-version>1.0</tlib-version>
<short-name>myshortname</short-name>

<tag>
<name>Hello</name>
<tag-class>com.pyrrhax.HelloTag</tag-class>
<body-content>empty</body-content>
</tag>

<!-- Invoke 'Generate' action to add tags or functions -->

</taglib>

其中可以<tag>标签中的<tag-class>指定一个类。也可以通过<tag-file>标签的<path>指定一个.tag文件。.tag文件中直接写jsp代码就可以。

这里面就是定义了一个tab叫Hello,并指定其对应的class,在class中实现doTag()方法,只要在jsp页面中引用这个tag,就可以执行其中的doTag()。

JSP页面中加上如下两行即可

1
2
<%@ taglib prefix="my" uri="WEB-INF/MyTag.tld"%>
<my:Hello></my:Hello>

效果如下,在页面中什么都没写的情况下,用tag输出了”Hello Custom Tag”。当然,把tag类中的代码改成弹出计算器也行。

image-20221215104056117

该漏洞通过参数绑定漏洞热替换了<input>``<form>等标签的实现,导致包含这些标签的页面在解析jsp时被插入恶意代码。至于TldLocationsCache相关Spring对JSP解析的具体流程等,需要跟读Spring源码才可以详细理解,暂时没有兴趣。

修复方式

spring的修复方法:

spring在CachedIntrospectionResults中获取beanInfo后对其进行了判断,将classloader添加进了黑名单。picgo2022-04-21-13-14-04

注意:这里对漏洞利用的判断写的是或||,所以当java9中出现class.module后,class.module可以通过第二个判断成为真,而module.classLoader可以通过第一个判断成为真,所以恰好可以绕过这里的验证。

tomcat的修复方法:

tomcat6.0.28版本后把getURLs方法返回的值改成了clone的,使得我们获得的拷贝版本无法修改classloader中的URLs[]。

picgo2022-04-21-13-14-52

class.module是什么

前面说在java9中出现了class.module从而绕过了对CVE-2010-1622的验证,因为之前没有接触过所以这里了解一下为什么java9中增加了class.module。

推荐阅读:Java9的模块(Module)系统

那在Class类中增加的module应该就是获取模块相关信息的。btw:之前用IDEA,发现可以新建module还以为是IDEA的功能,这么看来是Java的新特性了。

利用链发现方式

参考文章:https://xz.aliyun.com/t/11281

用来遍历属性的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@RequestMapping("/testclass")
public void classTest(){
HashSet<Object> set = new HashSet<Object>();
String poc = "class.moduls.classLoader";
User action = new User();
processClass(action.getClass().getClassLoader(),set,poc);
}

public void processClass(Object instance, java.util.HashSet set, String poc){
try {
Class<?> c = instance.getClass();
set.add(instance);
Method[] allMethods = c.getMethods();
for (Method m : allMethods) {
if (!m.getName().startsWith("set")) {
continue;
}
if (!m.toGenericString().startsWith("public")) {
continue;
}
Class<?>[] pType = m.getParameterTypes();
if(pType.length!=1) continue;

if(pType[0].getName().equals("java.lang.String")||
pType[0].getName().equals("boolean")||
pType[0].getName().equals("int")){
String fieldName = m.getName().substring(3,4).toLowerCase()+m.getName().substring(4);
System.out.println(poc+"."+fieldName);
//System.out.println(m.getName());
}
}
for (Method m : allMethods) {
if (!m.getName().startsWith("get")) {
continue;
}
if (!m.toGenericString().startsWith("public")) {
continue;
}
Class<?>[] pType = m.getParameterTypes();
if(pType.length!=0) continue;
if(m.getReturnType() == Void.TYPE) continue;
m.setAccessible(true);
Object o = m.invoke(instance);
if(o!=null)
{
if(set.contains(o)) continue;

processClass(o, set, poc+"."+m.getName().substring(3,4).toLowerCase()+m.getName().substring(4));
}
}
} catch (IllegalAccessException | InvocationTargetException x) {
x.printStackTrace();
}
}

就是遍历ClassLoader中的具有set方法的属性,如果有get就get后递归获取具有set方法的属性。

因为Spring-Beans RCE的利用方式还参考了Struts2 S2-020,看了S2-202的分析文章,也是通过去遍历可修改的属性来找利用方式。S2-020是对CVE-2014-0094的绕过,而CVE-2014-0094是Struts提供的ParametersInterceptor拦截器的漏洞,而该拦截器用于参数绑定。

总结

​ 参数绑定漏洞的利用方式发现主要是依靠枚举可被修改的参数,看修改每个参数可以造成什么影响。参数绑定漏洞一般存在于开发框架之中,利用框架提供的功能对程序员预期之外的属性进行修改。要发现该漏洞及其绕过方式需要对开发框架的源码有清晰的理解(比如spring参数绑定对于list、set、map等之中的属性,就算没有set方法也可以对其进行修改)。并且对于可修改的参数,要对其参与的功能的全流程有一定了解(比如Spring中的jsp解析tag的机制,tomcat中的流水线机制和日志配置机制),当然后面这点可以在发现漏洞之后去,找出可以修改的属性再逐渐学习研究。

​ 要想发现相关漏洞还是得依靠对各种组件和框架的源码进行学习和审计。要想发现之前某个历史漏洞的绕过方式,需要对历史漏洞及当前修复方式有清晰的了解。并对最新的组件或语言特性有清晰的了解。

​ 细节上,了解了jsp自定义标签和java9的moudule。