Nmap Development mailing list archives

Re: Another SCADA/ICS NMAP NSE script - Hirschmann/Innominate Security Technologies 'mGuard' firewall enumeration script


From: Bob Radvanovsky <rsradvan () unixworks net>
Date: Mon, 06 Dec 2010 22:08:14 -0600

I found some sample output for the Hirschmann/Innominate Security Technologies 'mGuard' firewall NMAP NSE enumeration 
script.  Output is shown below.  NOTE: the "xxx.xxx.xxx.xxx" denotes a sanitized IP address used for testing.

Syntax is: nmap --script=./mguard-10091201.nse xxx.xxx.xxx.xxx -PN -v; it should return the result of this:

===================

[root@server mguard]# nmap --script=./mguard-10091201.nse xxx.xxx.xxx.xxx -PN -v

Starting Nmap 5.35DC1 ( http://nmap.org ) at 2010-09-16 21:49 CDT
NSE: Loaded 1 scripts for scanning.
Initiating Parallel DNS resolution of 1 host. at 21:49
Completed Parallel DNS resolution of 1 host. at 21:49, 0.00s elapsed
Initiating SYN Stealth Scan at 21:49
Scanning xxx.domain.com (xxx.xxx.xxx.xxx) [1000 ports]
Discovered open port 53/tcp on xxx.xxx.xxx.xxx
Discovered open port 22/tcp on xxx.xxx.xxx.xxx
Discovered open port 443/tcp on xxx.xxx.xxx.xxx
Completed SYN Stealth Scan at 21:49, 1.41s elapsed (1000 total ports)
NSE: Script scanning xxx.xxx.xxx.xxx.
NSE: Starting runlevel 1 (of 1) scan.
Initiating NSE at 21:49
Completed NSE at 21:50, 21.76s elapsed
Nmap scan report for xxx.domain.com (xxx.xxx.xxx.xxx)
Host is up (0.010s latency).
Not shown: 996 closed ports
PORT     STATE    SERVICE
22/tcp   open     ssh
53/tcp   open     domain
443/tcp  open     https
| mguard-10091201: CONFIRM DEVICE AS HIRSCHMANN / INNOMINATE
| ** PHASE 1: TLS/SSL certificate verification
| ....Step 1: SSL certificate info   : CONFIRMED
| ....Step 2: SSL certificate MD5 hash information
| ............Flash ID               : 420401db459c83e7
| ............Organization name      : Hirschmann Automation and Control GmbH
| ............SSL certificate MD5    : c93063872150383b879a69f65ab6d7e5
| ............SSL certificate version: 4.2.1 or newer
| ** PHASE 2: File presence verification
| ....Step 1: Existence of "/favicon.ico"
| ............File favicon.ico MD5   : 7449c1f67008cc3bfabbc8f885712207
| ............Server type/version    : 4.2.1 or newer
| ....Step 2: Existence of "/gai.js"
| ............File gai.js MD5        : e7696a86648dcdb6efb2e497e5a8616b
| ............Server type/version    : 4.2.1
| ....Step 3: Existence of "/style.css"
| ............File style.css MD5     : d71581409253d54902bea82107a1abb2
| ............Server type/version    : 4.2.1
| ** PHASE 3: HTML pattern matching verification
| ....Step 1: Confirmation of HTML code per version
| ............HTML code verified     : CONFIRMED
| ............HTML code variant      : Hirschmann
| ....Step 2: Confirmation web server verification
| ............Web server verified    : CONFIRMED
| ............Web server name/type   : fnord
| ............Web server version     : 1.6
| ** PHASE 4: Documentation
| ....Step 1: Documentation exist?   : YES
| ............ninja.infracritical.com/dox/hirschmann/UM_BAT54_SW_Rel754_en.pdf
|_............ninja.infracritical.com/dox/hirschmann/UM_EAGLE_401_EN.pdf
1720/tcp filtered H.323/Q.931

Read data files from: /usr/local/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 23.43 seconds
           Raw packets sent: 1001 (44.044KB) | Rcvd: 999 (39.972KB)

----- Original Message -----
From: Bob Radvanovsky [mailto:rsradvan () unixworks net]
To: nmap-dev () insecure org
Subject: Another SCADA/ICS NMAP NSE script - Hirschmann/Innominate Security Technologies 'mGuard' firewall enumeration 
script


This is one of several enumeration scripts that I have written for the
SCADA/industrial control systems community.  This checks/validates the
web-based traffic for the Hirschmann/Innominate Security
Technologies/Phoenix Contacts 'mGuard' firewall.  NOTE: This has ONLY tested
with the Hirschmann OEM of the Innominate's software, as well as
Innominate's software (direct), and has NOT been tested on the newer Phoenix
Contacts 'mGuard' firewall (even though it continues to be an OEM'd version
of Innominate's software).

As the firewall has been rendered inoperative through our various
enumeration and validation tests, there is currently NO sample output from
the NMAP NSE script...sorry

The same script is shown below; if you wish to download the script, the
script may be accessed here:
http://www.infracritical.com/enum-scripts/mguard-10091201.nse

===============================================

description = "Confirms/verifies that target device is mGuard firewall."
author      = "Bob Radvanovsky <rsradvan at infracritical dot com>"
license     = "Refer to: http://nmap.org/book/man-legal.html for license."
categories  = {"safe", "discovery"}

--
--  Filename:      mguard.nse
--
--  Purpose:       Checks for the following elements confirming said device:
-- 
--  1.  PHASE I - TLS/SSL certification verification.
--      a.  STEP 1:  Performs verification of the self-signed
--                   TLS/SSL certificate against known string patterns
--                   initially discovered of said device.
--      b.  STEP 2:  Performs verification of the self-signed
--                   TLS/SSL certificate against known MD5 and SHA
--                   hashes initially discovered of said device.
--
--          NOTE: Returns "flash_id" string for device (in verbose).
--
--  2.  PHASE II - File existence/confirmation & verification.
--      a.  STEP 1:  Confirms file "/favicon.ico" exists..
--      b.  STEP 2:  Confirms file "/gai.js" exists.
--      c.  STEP 3:  Confirms file "/style.css" exists.
--      d.  STEP 4:  Confirms file "/error404.html" exists.
--                   (version 4.2.1 and higher)
--                
--  3.  PHASE III - HTML pattern matching & confirmation.
--      a.  STEP 1:  Confirms excerpted HTML code based on
--                   each version; each version has slightly variant
--                   differences for each HTML code.
--      b.  STEP 2:  Confirms web server type (from HEAD cmd).
--
--  4.  PHASE IV - Documentation.
--
-- 
==========================================================================
--                      
--  Version(s):    3.1.1, 4.1.1, 4.2.1, 4.2.3
--
--  Usage:         nmap --script=./mguard.nse <IP> -PN
--
--  Author(s):     Bob Radvanovsky - Infracritical
--                 <rsradvan at infracritical dot com>
--
--  Initwritten:   August 2010
--
--  DATE----  INIT
DESCRIPTION------------------------------------------------
--  10.28.08  rsr  Inital development - VERSION 001.
--  10.30.08  rsr  Added script argument feature; argument called "VERBOSE".
--  10.30.08  rsr  Issue with standard argument arrays; making singular arg.
--  10.02.09  rsr  Cleaned up some of the code for efficiency purposes.
--  10.02.09  rsr  Added feature for TERSE vs. VERBOSE modes; if TERSE mode,
--                 only the server name, version and flash ID is returned;
--                 if VERBOSE mode, everything is returned.
--  10.02.09  rsr  Fixed VERBOSE feature; added "if nmap.verbosity() > 0".
--  10.05.09  rsr  Fixed substring issue; found "stdnse.strsplit()" function
--                 for splitting based on any given delimiter.
--  10.12.09  rsr  Removed version detection from basic available ports;
--                 found open/filtered ports unreliable for version
detection.
--
--  NOTE: Script is a derived work from David Fifield's "ssl-cert.nse"
--        NSE script, and has been modified to work specifically on/for the
--        Hirschmann/Innominate/Phoenix mGuard firewell devices.
--
--  NOTE: We try and verify, running tests on as many versions of this
device
--        as possible.  If you encounter an "UNKNOWN", this may mean that
you
--        have scanned a device version/variant that was not tested.
--
--        Since we are providing this script free-of-charge to everyone,
--        would help us - and the community - if you would report the
variance
--        to us.  Submit your findings to "report () infracritical com". 
Thanks!
--

require("nmap")
require("nsedebug")
require("datafiles")
require("stdnse")
require("shortport")
require("http")
require("url")
require("strbuf")

local md5sum, i, answer, s, date_to_string, status, err
local stringify_name, table_find, flashid, orgname
local port1 = ""; local port2 = ""; local port3 = ""; local port4 = ""
local server = ""; local result = ""; local version = ""; local servertype =
""
local HAVE_SSL = false

local STARTTLS_PORTS = { 25, 587 }

--
-- NOTE: Much of what is determined, may be found simply through
interrogating
--       the onboard web server; if the web server is DISABLED, or if the
--       verification is performed through the EXTERNAL port, the results
may
--       vary, depending on how the firewall is configured.
--
portrule = shortport.port_or_service({ 443 }, { "https" })

action = function(host, port)
--
-- NOTE: Default discovered information is shown in NON-VERBOSE MODE; to
--       perform a VERBOSE discovery, follow the usage statement above.
--
--
-- NOTE: Determine if Version 4.2.1 (or newer) or 4.1.1 (or older)
--  

-- local file = assert(io.open("/tmp/temp.out", "a"))
-- file:write("THIS IS A TEST")

--
-- Discovered MD5 hashes for various files
--
  local sslcerts = {
        {md5="089e6c5d1eb61af201b8cbb024d98d00", name="3.1.1"},
        {md5="c93063872150383b879a69f65ab6d7e5", name="4.2.1 or newer"}
        }

  local favicons = {
        {md5="d41d8cd98f00b204e9800998ecf8427e", name="Infracritical TEST"},
        {md5="64e511bd5e7cd3b027e5af2a761e952a", name="4.1.1 or older"},
        {md5="7449c1f67008cc3bfabbc8f885712207", name="4.2.1 or newer"}
        }

  local gaijs = {
        {md5="6c30d7d2034d9840e330110ae118c03e", name="4.1.1 or older"},
        {md5="e7696a86648dcdb6efb2e497e5a8616b", name="4.2.1"},
        {md5="910cf96059109cfb5c2a845ec8456053", name="4.2.3 or newer"}
        }

  local stylecss = {
        {md5="ffc439102a78ea79965c9d76646d7468", name="4.1.1 or older"},
        {md5="d71581409253d54902bea82107a1abb2", name="4.2.1"},
        {md5="f0f828948553d578861a66396c9306b2", name="4.2.3 or newer"}
        }

  if pcall(require,'openssl') then HAVE_SSL = true; end
  if HAVE_SSL == true then

    s = nmap.new_socket()

    if table_find(STARTTLS_PORTS, port.number) then
      local status = starttls_negotiate(host, port)
      if not status then return nil; end
    else
      local status, error = s:connect(host.ip, port.number, "ssl")
      if not status then
        if nmap.verbosity() > 0 then return error else return nil; end
      end
    end

    local checkme
    local cert = s:get_ssl_certificate()
    s:close()

    subject = "Subject: " .. stringify_name(cert.subject)
    issuer = "Issuer: " .. stringify_name(cert.issuer)
    md5cert = stdnse.tohex(cert:digest("md5"))
    result = "CONFIRM DEVICE AS HIRSCHMANN / INNOMINATE"

    if string.match(subject, "Hirschmann Automation and Control GmbH") or
string.match(subject, "Innominate Security Technologies AG") then
      if nmap.verbosity() > 1 then
        result = result .. "\n** PHASE 1: TLS/SSL certificate verification"
        result = result .. "\n....Step 1: SSL certificate info   :
CONFIRMED"
      else
        result = result .. "\n** IF YOU REQUIRE MORE INFO, USE THE \"-v\"
OPTION"
      end
      if nmap.verbosity() > 1 then
        result = result .. "\n....Step 2: SSL certificate MD5 hash
information"
      end
      local split = stdnse.strsplit("/",subject)
      local split2 = stdnse.strsplit(":", split[1])
      local split3 = stdnse.strsplit("=", split2[2])
      flashid = string.sub(split3[2],1,16)
      result = result .. "\n............Flash ID               : " ..
flashid
      if nmap.verbosity() > 1 then
        local split = stdnse.strsplit("/",subject)
        local split2 = stdnse.strsplit("=", split[2])
        orgname = string.sub(split2[2],1,45)
        result = result .. "\n............Organization name      : " ..
orgname
        checkme = "n"
        for i = 1, #sslcerts, 1 do
          if md5cert == sslcerts[i].md5 then
            checkme = "y"
            result = result .. "\n............SSL certificate MD5    : " ..
md5cert
            result = result .. "\n............SSL certificate version: " ..
sslcerts[i].name
          end
        end
        if checkme == "n" then
          result = result .. "\n............SSL certificate version:
UNKNOWN"
          result = result .. "\n............SSL certificate MD5    : " ..
md5cert
        end
      end
    else
      if nmap.verbosity() > 1 then result = "Fingerprint not found."; end
    end
    if nmap.verbosity() > 1 then result = result .. "\n"; end

    if nmap.verbosity() > 1 then
      result = result .. "\n** PHASE 2: File presence verification"
      result = result .. "\n....Step 1: Existence of \"/favicon.ico\""
      answer = http.get(host, port, "/favicon.ico")
      -- check for 200 response code
      if answer.status == 200 then
        md5sum=stdnse.tohex(openssl.md5(answer.body))
        checkme = "n"
        for i = 1, #favicons, 1 do
          if md5sum == favicons[i].md5 then
            checkme = "y"
            result = result .. "\n............File favicon.ico MD5   : " ..
md5sum
            result = result .. "\n............Server type/version    : " ..
favicons[i].name
          end
        end
        if checkme == "n" then
          result = result .. "\n............Unknown favicon.ico MD5: " ..
md5sum
        end
      else
        result = "\n............File favicon.ico       : FAIL"
      end

      result = result .. "\n....Step 2: Existence of \"/gai.js\""
      answer = http.get(host, port, "/gai.js")
      -- check for 200 response code
      if answer.status == 200 then
        md5sum=stdnse.tohex(openssl.md5(answer.body))
        checkme = "n"
        for i = 1, #gaijs, 1 do
          if md5sum == gaijs[i].md5 then
            checkme = "y"
            if string.match(answer.body, "Innominate Security Technologies
AG") then
              result = result .. "\n............File gai.js MD5        : "
.. md5sum
              result = result .. "\n............Server type/version    : "
.. gaijs[i].name
            end
          end
        end
        if checkme == "n" then
          result = result .. "\n............Unknown gai.js MD5     : " ..
md5sum
        end
      else
        result = "\n............File gai.js            : FAIL"
      end
      
      result = result .. "\n....Step 3: Existence of \"/style.css\""
      answer = http.get(host, port, "/style.css")
      -- check for 200 response code
      if answer.status == 200 then
        md5sum=stdnse.tohex(openssl.md5(answer.body))
        checkme = "n"
        for i = 1, #stylecss, 1 do
          if md5sum == stylecss[i].md5 then
            checkme = "y"
            if string.match(answer.body, "Innominate Security Technologies
AG") then
              result = result .. "\n............File style.css MD5     : "
.. md5sum
              result = result .. "\n............Server type/version    : "
.. stylecss[i].name
            end
          end
        end
        if checkme == "n" then
          result = result .. "\n............Unknown style.css MD5  : " ..
md5sum
        end
      else
        result = "\n............File style.css         : FAIL"
      end

      result = result .. "\n** PHASE 3: HTML pattern matching verification"
      result = result .. "\n....Step 1: Confirmation of HTML code per
version"
    end
    answer = http.get(host, port, "/")
    -- check for 200 response code
    if answer.status == 200 then
      if string.match(answer.body, "GAI.SESSIONID") and
string.match(answer.body, "GAI.TAB.STATE") and string.match(answer.body,
"Username:") and string.match(answer.body, "Password:") and
string.match(answer.body, "Access Type:") and string.match(answer.body,
"User Firewall") and string.match(answer.body, "GAI.SUBMIT.LOGIN") then
        if nmap.verbosity() > 1 then
          result = result .. "\n............HTML code verified     :
CONFIRMED"
        end
        if string.match(answer.body, "EAGLE") then
          servertype = "hirschmann"
          if nmap.verbosity() > 1 then
            result = result .. "\n............HTML code variant      :
Hirschmann"
          else
            result = result .. "\n............Manufacturer of device :
Hirschmann"
          end
        end
        if string.match(answer.body, "mGuard") then
          servertype = "innominate"
          if nmap.verbosity() > 1 then
            result = result .. "\n............HTML code variant      :
Innominate"
          else
            result = result .. "\n............Manufacturer of device :
Innominate"
          end
        end
      else
        if nmap.verbosity() > 1 then
          result = result .. "\n............HTML code version      :
UNKNOWN"
          result = result .. "\n"
        end
      end
    end

    if nmap.verbosity() > 1 then
      result = result .. "\n....Step 2: Confirmation web server
verification"
      answer = http.get(host, port, "/")
      -- check for 200 response code
      if answer.status == 200 then
        server = string.match(stdnse.format_output(true, answer.rawheader),
"Server: fnord/1.6")
        if server then
          version = string.sub(server,15)
          result = result .. "\n............Web server verified    :
CONFIRMED"
          result = result .. "\n............Web server name/type   : fnord"
          result = result .. "\n............Web server version     : " ..
version
        else
          result = result .. "\n............Web server name/type   :
UNKNOWN"
        end
        result = result .. "\n"
      end
      result = result .. "\n** PHASE 4: Documentation"
      result = result .. "\n....Step 1: Documentation exist?   : YES"
      result = result .. "\n"
      if servertype == "hirschmann" then
        result = result ..
"\n............ninja.infracritical.com/dox/hirschmann/UM_BAT54_SW_Rel754_en.pdf"
        result = result ..
"\n............ninja.infracritical.com/dox/hirschmann/UM_EAGLE_401_EN.pdf"
      end
      if servertype == "innominate" then
        result = result ..
"\n............ninja.infracritical.com/dox/innominate/handbuch_mguard_600_en.pdf"
      end
    end
    return result
  end -- HAVE_SSL
end

-- Find the index of a value in an array.
function table_find(t, value)
  local i, v
  for i, v in ipairs(t) do
    if v == value then return i; end
  end
  return nil
end

-- These are the subject/issuer name fields that will be shown, in this
order,
-- without a high verbosity.
local NON_VERBOSE_FIELDS = { "commonName", "organizationName",
    "stateOrProvinceName", "countryName" }

function stringify_name(name)
  local fields = {}
  local _, k, v
  if not name then return nil; end
  for _, k in ipairs(NON_VERBOSE_FIELDS) do
    v = name[k]
    if v then
      fields[#fields + 1] = string.format("%s=%s", k, v)
    end
  end
  if nmap.verbosity() > 1 then
    for k, v in pairs(name) do
      -- Don't include a field twice.
      if not table_find(NON_VERBOSE_FIELDS, k) then
        if type(k) == "table" then
          k = stdnse.strjoin(".", k)
        end
        fields[#fields + 1] = string.format("%s=%s", k, v)
      end
    end
  end
  return stdnse.strjoin("/", fields)
end

function date_to_string(date)
  if not date then return "MISSING"; end
  if type(date) == "string" then
    return string.format("Can't parse; string is \"%s\"", date)
  else
    return os.date("%Y-%m-%d %H:%M:%S", os.time(date))
  end
end

function starttls_negotiate(host, port)
  -- Attempt to negotiate TLS over SMTP for services that support it
  -- Works for SMTP (25) and SMTP Submission (587)

  -- Open a standard TCP socket
  local status, error = s:connect(host, port, "tcp")  
  if not status then
    return nil
  else
    -- Loop until the service presents a banner to deal with server
    -- load and timing issues.  There may be a better way to handle this.
    local i = 0
    repeat
      status, resultEHLO = s:receive_lines(1)
      i = i + 1
    until string.match(resultEHLO, "^220") or i == 5

    -- Send EHLO because the the server expects it
    -- We are not going to check for STARTTLS in the capabilities
    -- list, sometimes it is not advertised.
    local query = "EHLO example.org\r\n"
    status = s:send(query)
    status, resultEHLO = s:receive_lines(1)

    if not (string.match(resultEHLO, "^250")) then
      stdnse.print_debug("1","%s",resultEHLO)
      stdnse.print_debug("1","EHLO with errors or timeout.  Enable
--script-trace to see what is happening.")
      return nil
    end

    resultEHLO = ""
      
    -- Send STARTTLS command ask the service to start encryption    
    local query = "STARTTLS\r\n"
    status = s:send(query)
    status, resultEHLO = s:receive_lines(1)
        
    if not (string.match(resultEHLO, "^220")) then
      stdnse.print_debug("1","%s",resultEHLO)
      stdnse.print_debug("1","STARTTLS failed or unavailable.  Enable
--script-trace to see what is happening.")
            
      -- Send QUIT to clean up server side connection
      local query = "QUIT\r\n"
      status = s:send(query)        
      resultEHLO = ""
      return nil
    end
        
    -- Service supports STARTTLS, tell NSE start SSL negotiation
    status, error = s:reconnect_ssl()
    if not status then
      stdnse.print_debug("1","Could not establish SSL session after STARTTLS
command.")
      s:close()
      return nil
    end
  end    
  -- Should have a solid TLS over SMTP session now...
  return "Connected"
end 
_______________________________________________
Sent through the nmap-dev mailing list
http://cgi.insecure.org/mailman/listinfo/nmap-dev
Archived at http://seclists.org/nmap-dev/

_______________________________________________
Sent through the nmap-dev mailing list
http://cgi.insecure.org/mailman/listinfo/nmap-dev
Archived at http://seclists.org/nmap-dev/


Current thread: