Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gteissier
GitHub Repository: gteissier/erl-matter
Path: blob/master/erldp-info.nse
271 views
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
local bin = require "bin"
local match = require "match"

-- @usage
-- nmap -sT -p <port> <ip> --script erldp-info -sV
-- 
-- @output
-- PORT      STATE SERVICE
-- 25672/tcp open  erldp
-- | erldp-info:
-- |   version: 6e00
-- |   node: rabbit@iron
-- |_  flags: 500037f
-- 
-- Erlang daemons - couchdb, ejabberd and rabbitmq are the most common -
-- may expose a sensitive asset called Erlang distribution protocol.
-- This script actively scans for Erlang distribution protocol.
-- The portrule below asks to scan for every open TCP port.
--
-- Note that epmd-info.nse may already provide information regarding
-- named Erlang nodes.
-- 
-- Running the same command as above plus --script epmd-info gives:
-- PORT      STATE SERVICE
-- 4369/tcp  open  epmd
-- | epmd-info:
-- |   epmd_port: 4369
-- |   nodes:
-- |_    rabbit: 25672
-- 5672/tcp  open  amqp
-- 25672/tcp open  erldp
-- | erldp-info:
-- |   version: 6e00
-- |   node: rabbit@iron
-- |_  flags: 500037f
--
-- However, some security guidelines recommend to disallow access to 4369
-- while Erlang nodes might still be accessible.
--



description = [[
Identifies Erlang distribution, which provides remoting for Erlang based servers.
]]

author = "Guillaume Teissier <[email protected]>"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default", "safe", "discovery", "version"}

portrule = function(host, port)
  return port.protocol == "tcp" and port.state == "open"
end

local random = math.random
local function uuid()
    local template ='xxxxxxxx@devnull'
    return string.gsub(template, '[x]', function (c)
        local v = random(0, 0xf)
        return string.format('%x', v)
    end)
end

local function decode(conn)
  local status, data

  status, data = conn:receive_buf(match.numbytes(2), true)
  if not status then
    return
  end

  length = select(2, bin.unpack(">S", data))

  status, data = conn:receive_buf(match.numbytes(length), true)
  if not status then
    return
  end

  return data
end

local function decode_recv_challenge(data)
  local version, flags, challenge, offset
  offset, cmd, version, flags, challenge = bin.unpack(">CSII", data)
  return version, flags, challenge, data:sub(offset)
end

action = function(host, port)
  local client = nmap.new_socket()
  local status
  local data
  local send_name
  local local_name = uuid()
  local output = stdnse.output_table()

  status, data = client:connect(host, port)
  if not status then
    client:close()
    return
  end

  send_name = bin.pack('>SCSIA', 7+string.len(local_name), 110, 5, 0x7499c, local_name)

  if not client:send(send_name) then
    client:close()
    return
  end

  data = decode(client)
  if not data or data:sub(1, 1) ~= "s" or data:sub(2, 3) ~= "ok" then
    client:close()
    return
  end

  data = decode(client)
  if not data or data:sub(1, 1) ~= "n" then
    client:close()
    return
  end

  local version, flags, challenge, peer_name = decode_recv_challenge(data)

  output["version"] = stdnse.tohex(version)
  output["node"] = peer_name
  output["flags"] = stdnse.tohex(flags)

  port.version.product = "Erlang distribution protocol"
  port.version.name = "erldp"

  nmap.set_port_state(host, port, "open")
  nmap.set_port_version(host, port, "hardmatched")

  return output
end