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%
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.
nmap -sV --script=iec-identify <target>
| iec-identify:
| ASDU address: 105
|_ Information objects: 30
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("<I2", recv, 9)
stdnse.debug2("Got asdu=%d, type %d, cause %d, numix %d.", asdu_address, typeid, cause, numix)
if typeid == 100 then
-- C_IC_NA_1
if cause == 7 then
-- ActCon. Skip.
elseif cause == 10 then
-- ActTerm. The end!
break
else
-- TODO: do something!
end
else
if cause >= 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
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%