diff --git a/CHANGELOG b/CHANGELOG index d6629b18a..3e36d9968 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] iec-identify probes for the IEC 60870-5-104 SCADA protocol. + [Aleksandr Timorin, Daniel Miller] + o [GH#910] added libssh2 support, ssh-brute, ssh-run, ssh-auth-methods, ssh-publickey-acceptance [Evangelos Deirmentzoglou] diff --git a/scripts/iec-identify.nse b/scripts/iec-identify.nse new file mode 100644 index 000000000..81bcd61a7 --- /dev/null +++ b/scripts/iec-identify.nse @@ -0,0 +1,161 @@ +local shortport = require "shortport" +local comm = require "comm" +local stdnse = require "stdnse" +local string = require "string" +local match = require "match" + +description = [[ +Attempts to identify IEC 60870-5-104 ICS protocol. + +After probing with a TESTFR (test frame) message, a STARTDT (start data +transfer) message is sent and general interrogation is used to gather the list +of information object addresses stored. +]] + +--- +-- @output +-- | iec-identify: +-- | ASDU address: 105 +-- |_ Information objects: 30 +-- + +author = {"Aleksandr Timorin", "Daniel Miller"} +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" +categories = {"discovery", "intrusive"} + +portrule = shortport.port_or_service(2404, "iec-104", "tcp") + +local function get_asdu(socket) + local status, data = socket:receive_buf(match.numbytes(2), true) + if not status then + return nil, data + end + if data:byte(1) ~= 0x68 then + return nil, "Not IEC-104" + end + local len = data:byte(2) + status, data = socket:receive_buf(match.numbytes(len), true) + if not status then + return nil, data + end + local apcitype = data:byte(1) + return apcitype, data +end + +action = function(host, port) + + local output = stdnse.output_table() + local socket, err = comm.opencon(host, port) + if not socket then + stdnse.debug1("Connect error: %s", err) + return nil + end + + -- send TESTFR ACT command + -- Test frame, like "ping" + local TESTFR = "\x68\x04\x43\0\0\0" + local status, err = socket:send( TESTFR ) + if not status then + stdnse.debug1("Failed to send: %s", err) + return nil + end + + -- receive TESTFR answer + local apcitype, recv = get_asdu(socket) + if not apcitype then + stdnse.debug1("protocol error: %s", recv) + return nil + end + if apcitype ~= 0x83 then + stdnse.print_debug(1, "Not IEC-104. TESTFR response: %#x", apcitype) + return nil + end + + -- send STARTDT ACT command + local STARTDT = "\x68\x04\x07\0\0\0" + status, err = socket:send( STARTDT ) + if not status then + stdnse.debug1("Failed to send: %s", err) + return nil + end + + -- receive STARTDT answer + apcitype, recv = get_asdu(socket) + if not apcitype then + stdnse.debug1("protocol error: %s", recv) + return nil + end + if apcitype ~= 0x0b then + stdnse.debug1("STARTDT ACT did not receive STARTDT CON: %#x", apcitype) + return nil + end + + -- May also receive ME_EI_NA_1 (End of initialization), so check for that in the buffer after sending the next part + + -- send C_IC_NA_1 command + -- type: 0x64, C_IC_NA_1, + -- numix: 1 + -- TNCause: 6, Act + -- Originator address; 0 + -- ASDU address: 0xffff + -- Information object address: 0 + -- QOI: 0x14 (20), Station interrogation (global) + local C_IC_NA_1_broadcast = "\x68\x0e\0\0\0\0\x64\x01\x06\0\xff\xff\0\0\0\x14" + status, err = socket:send( C_IC_NA_1_broadcast ) + if not status then + stdnse.debug1("Failed to send: %s", err) + return nil + end + + local asdu_address + local ioas = 0 + -- Have to draw the line somewhere. + local limit = 10 + while limit > 0 do + limit = limit - 1 + apcitype, recv = get_asdu(socket) + if not apcitype then + stdnse.debug1("Error in C_IC_NA_1: %s", recv) + break + end + if apcitype & 0x01 == 0 then -- Type I, numbered information transfer + -- skip 2 bytes Tx, 2 bytes Rx + local typeid = recv:byte(5) + if typeid == 70 then + -- ME_EI_NA_1, End of Initialization. Skip. + else + local numix = recv:byte(6) & 0x7f + local cause = recv:byte(7) & 0x3f + asdu_address = string.unpack("= 20 and cause <= 36 then + -- Inrogen, response to general interrogation + ioas = ioas + numix + end + end + end + end + end + + socket:close() + + if asdu_address then + output["ASDU address"] = asdu_address + output["Information objects"] = ioas + else + output = "IEC-104 endpoint did not respond to C_IC_NA_1 request" + end + + return output +end diff --git a/scripts/script.db b/scripts/script.db index 08ba4dab5..7c35b27ae 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -278,6 +278,7 @@ Entry { filename = "http-xssed.nse", categories = { "discovery", "external", "sa Entry { filename = "iax2-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "iax2-version.nse", categories = { "version", } } Entry { filename = "icap-info.nse", categories = { "discovery", "safe", } } +Entry { filename = "iec-identify.nse", categories = { "discovery", "intrusive", } } Entry { filename = "ike-version.nse", categories = { "default", "discovery", "safe", "version", } } Entry { filename = "imap-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "imap-capabilities.nse", categories = { "default", "safe", } } @@ -394,6 +395,7 @@ Entry { filename = "omp2-enum-targets.nse", categories = { "discovery", "safe", Entry { filename = "omron-info.nse", categories = { "discovery", "version", } } Entry { filename = "openlookup-info.nse", categories = { "default", "discovery", "safe", "version", } } Entry { filename = "openvas-otp-brute.nse", categories = { "brute", "intrusive", } } +Entry { filename = "openwebnet-discovery.nse", categories = { "discovery", "safe", } } Entry { filename = "oracle-brute-stealth.nse", categories = { "brute", "intrusive", } } Entry { filename = "oracle-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "oracle-enum-users.nse", categories = { "auth", "intrusive", } } @@ -499,7 +501,11 @@ Entry { filename = "snmp-win32-users.nse", categories = { "auth", "default", "sa Entry { filename = "socks-auth-info.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "socks-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "socks-open-proxy.nse", categories = { "default", "discovery", "external", "safe", } } +Entry { filename = "ssh-auth-methods.nse", categories = { } } +Entry { filename = "ssh-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "ssh-hostkey.nse", categories = { "default", "discovery", "safe", } } +Entry { filename = "ssh-publickey-acceptance.nse", categories = { } } +Entry { filename = "ssh-run.nse", categories = { "intrusive", } } Entry { filename = "ssh2-enum-algos.nse", categories = { "discovery", "safe", } } Entry { filename = "sshv1.nse", categories = { "default", "safe", } } Entry { filename = "ssl-ccs-injection.nse", categories = { "safe", "vuln", } }