├── README.md ├── Siemens-CommunicationsProcessor.nse ├── Siemens-HMI-miniweb.nse ├── Siemens-SIMATIC-PLC-S7.nse ├── Siemens-Scalance-module.nse └── Siemens-WINCC.nse /README.md: -------------------------------------------------------------------------------- 1 | nmap-scada 2 | ========== 3 | 4 | nse scripts for scada identification 5 | 6 | nmap --script ./Siemens-CommunicationsProcessors.nse -p 80 7 | 8 | nmap -sU --script ./Siemens-SCALANCE-module.nse -p 161 9 | 10 | nmap -sU --script ./Siemens-WINCC.nse -p 137 -------------------------------------------------------------------------------- /Siemens-CommunicationsProcessor.nse: -------------------------------------------------------------------------------- 1 | local http = require "http" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local strbuf = require "strbuf" 5 | 6 | 7 | description = [[ 8 | Checks for SCADA Siemens S7 Communications Processor devices. 9 | 10 | The higher the verbosity or debug level, the more disallowed entries are shown. 11 | ]] 12 | 13 | --- 14 | --@output 15 | -- 80/tcp open http syn-ack 16 | -- |_Siemens-CommunicationsProcessor: CP 343-1 CX10 17 | 18 | 19 | 20 | author = "Jose Ramon Palanco, drainware" 21 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 22 | categories = {"default", "discovery", "safe"} 23 | 24 | portrule = shortport.http 25 | local last_len = 0 26 | 27 | 28 | local function verify_version(body, output) 29 | local version = nil 30 | 31 | if string.find (body, "/S7Web.css") then 32 | version = body:match("(.-)") 33 | if version == nil then 34 | version = "Unknown version" 35 | end 36 | output = output .. version 37 | return true 38 | elseif string.find (body, "examples/visual_key.htm") then 39 | version = body:match("(.-)") 40 | if version == nil then 41 | version = "Unknown version" 42 | end 43 | output = output .. version 44 | return true 45 | elseif string.find (body, "__FSys_Root") then 46 | version = body:match("(.-)") 47 | version = version:gsub(" ", " ") 48 | if version == nil then 49 | version = "Unknown version" 50 | end 51 | output = output .. version 52 | return true 53 | else 54 | return nil 55 | end 56 | end 57 | 58 | action = function(host, port) 59 | local verified, noun 60 | 61 | local answer1 = http.get(host, port, "/Portal0000.htm" ) 62 | local answer2 = http.get(host, port, "/__Additional" ) 63 | local answer3 = http.get(host, port, "/" ) 64 | 65 | if answer1.status ~= 200 and answer2.status ~= 200 and answer3.status ~= 200 then 66 | return nil 67 | end 68 | 69 | if answer1.status == 200 then 70 | answer = answer1 71 | elseif answer2.status == 200 then 72 | answer = answer2 73 | elseif answer3.status == 200 then 74 | answer = answer3 75 | end 76 | 77 | local v_level = nmap.verbosity() + (nmap.debugging()*2) 78 | local detail = 15 79 | local output = strbuf.new() 80 | 81 | 82 | verified = verify_version(answer.body, output) 83 | 84 | 85 | if verified == nil then 86 | return 87 | end 88 | 89 | -- verbose/debug mode, print 50 entries 90 | if v_level > 1 and v_level < 5 then 91 | detail = 40 92 | -- double debug mode, print everything 93 | elseif v_level >= 5 then 94 | detail = verified 95 | end 96 | 97 | 98 | return output 99 | end -------------------------------------------------------------------------------- /Siemens-HMI-miniweb.nse: -------------------------------------------------------------------------------- 1 | local http = require "http" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local strbuf = require "strbuf" 5 | 6 | 7 | description = [[ 8 | Checks for SCADA Siemens SIMATIC S7- devices. 9 | 10 | The higher the verbosity or debug level, the more disallowed entries are shown. 11 | ]] 12 | 13 | --- 14 | --@output 15 | -- 80/tcp open http syn-ack 16 | -- |_SIEMENS-HMI-miniweb: Not implemented verify_version 17 | 18 | 19 | 20 | author = "Jose Ramon Palanco, drainware" 21 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 22 | categories = {"default", "discovery", "safe"} 23 | 24 | portrule = shortport.http 25 | local last_len = 0 26 | 27 | 28 | local function verify_version(body, output) 29 | local version = nil 30 | if string.find (body, "ad_header_form_sprachauswahl") then 31 | version = body:match("") 32 | if version == nil then 33 | version = "Not implemented verify_version" 34 | end 35 | output = output .. version 36 | return true 37 | else 38 | return nil 39 | end 40 | end 41 | 42 | action = function(host, port) 43 | local verified, noun 44 | local answer = http.get(host, port, "/CSS/Miniweb.css" ) 45 | 46 | if answer.status ~= 200 then 47 | return nil 48 | end 49 | 50 | local v_level = nmap.verbosity() + (nmap.debugging()*2) 51 | local detail = 15 52 | local output = strbuf.new() 53 | 54 | 55 | verified = verify_version(answer.body, output) 56 | 57 | 58 | if verified == nil then 59 | return 60 | end 61 | 62 | -- verbose/debug mode, print 50 entries 63 | if v_level > 1 and v_level < 5 then 64 | detail = 40 65 | -- double debug mode, print everything 66 | elseif v_level >= 5 then 67 | detail = verified 68 | end 69 | 70 | 71 | return output 72 | end -------------------------------------------------------------------------------- /Siemens-SIMATIC-PLC-S7.nse: -------------------------------------------------------------------------------- 1 | local http = require "http" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local strbuf = require "strbuf" 5 | 6 | 7 | description = [[ 8 | Checks for SCADA Siemens Simatic S7 devices. 9 | 10 | The higher the verbosity or debug level, the more disallowed entries are shown. 11 | ]] 12 | 13 | --- 14 | --@output 15 | -- 80/tcp open http syn-ack 16 | -- |_Siemens-Simatic-S7: SIMATIC 300 (MPI2)/CPU 315-2 PN/DP 17 | 18 | 19 | 20 | author = "Jose Ramon Palanco, drainware" 21 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 22 | categories = {"default", "discovery", "safe"} 23 | 24 | portrule = shortport.http 25 | local last_len = 0 26 | 27 | 28 | local function verify_version(body, output) 29 | local version = nil 30 | if string.find (body, "/S7Web.css") then 31 | version = body:match("(.-)") 32 | version = version:gsub(" ", " ") 33 | if version == nil then 34 | version = "Unknown version" 35 | end 36 | output = output .. version 37 | return true 38 | elseif string.find (body, "snCplugPresent") then 39 | version = body:match("Siemens, (.-) !!!") 40 | version = version:gsub(" ", " ") 41 | if version == nil then 42 | version = "Unknown version" 43 | end 44 | output = output .. version 45 | return true 46 | 47 | else 48 | return nil 49 | end 50 | end 51 | 52 | action = function(host, port) 53 | local verified, noun 54 | local answer1 = http.get(host, port, "/Portal/Portal.mwsl" ) 55 | local answer2 = http.get(host, port, "/docs/cplugError.html/" ) 56 | 57 | if answer1.status ~= 200 and answer2.status ~= 200 then 58 | return nil 59 | end 60 | 61 | if answer1.status == 200 then 62 | answer = answer1 63 | elseif answer2.status == 200 then 64 | answer = answer2 65 | end 66 | 67 | 68 | local v_level = nmap.verbosity() + (nmap.debugging()*2) 69 | local detail = 15 70 | local output = strbuf.new() 71 | 72 | 73 | verified = verify_version(answer.body, output) 74 | 75 | 76 | if verified == nil then 77 | return 78 | end 79 | 80 | -- verbose/debug mode, print 50 entries 81 | if v_level > 1 and v_level < 5 then 82 | detail = 40 83 | -- double debug mode, print everything 84 | elseif v_level >= 5 then 85 | detail = verified 86 | end 87 | 88 | 89 | return output 90 | end -------------------------------------------------------------------------------- /Siemens-Scalance-module.nse: -------------------------------------------------------------------------------- 1 | local nmap = require "nmap" 2 | local shortport = require "shortport" 3 | local snmp = require "snmp" 4 | local stdnse = require "stdnse" 5 | local table = require "table" 6 | 7 | description = [[ 8 | Checks for SCADA Siemens SCALANCE modules. 9 | 10 | The higher the verbosity or debug level, the more disallowed entries are shown. 11 | ]] 12 | 13 | --- 14 | -- @output 15 | -- | Siemens-Scalance-module: 16 | -- |_ SCALANCE W788-1PRO 17 | 18 | 19 | author = "Jose Ramon Palanco, drainware" 20 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 21 | categories = {"default", "discovery", "safe"} 22 | dependencies = {"snmp-brute"} 23 | 24 | 25 | portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) 26 | 27 | 28 | function process_answer( tbl ) 29 | 30 | local new_tab = {} 31 | 32 | for _, v in ipairs( tbl ) do 33 | if string.find (v.value, "SCALANCE") then 34 | version = v.value:gsub("SCALANCE", "VERSION:") 35 | model = version:match("%W+ %s*(.-)%d%d%d") 36 | if model == "W" then 37 | version = version .. " (wireless device)" 38 | elseif model == "X" then 39 | version = version .. " (network switch)" 40 | elseif model == "S" then 41 | version = version .. " (firewall)" 42 | end 43 | else 44 | return nil 45 | end 46 | table.insert( new_tab, version) 47 | end 48 | 49 | table.sort( new_tab ) 50 | 51 | return new_tab 52 | 53 | end 54 | 55 | action = function(host, port) 56 | 57 | local socket = nmap.new_socket() 58 | local catch = function() socket:close() end 59 | local try = nmap.new_try(catch) 60 | local snmpoid = "1.3.6.1.2.1.1.1" 61 | local services = {} 62 | local status 63 | 64 | socket:set_timeout(5000) 65 | try(socket:connect(host, port)) 66 | 67 | status, services = snmp.snmpWalk( socket, snmpoid ) 68 | socket:close() 69 | 70 | 71 | if ( not(status) ) or ( services == nil ) or ( #services == 0 ) then 72 | return 73 | end 74 | 75 | services = process_answer(services) 76 | 77 | if services == nil then 78 | return 79 | end 80 | 81 | nmap.set_port_state(host, port, "open") 82 | 83 | return stdnse.format_output( true, services ) 84 | end 85 | -------------------------------------------------------------------------------- /Siemens-WINCC.nse: -------------------------------------------------------------------------------- 1 | local datafiles = require "datafiles" 2 | local netbios = require "netbios" 3 | local nmap = require "nmap" 4 | local stdnse = require "stdnse" 5 | local string = require "string" 6 | local table = require "table" 7 | 8 | description = [[ 9 | Checks for SCADA Siemens WINCC server. 10 | 11 | The higher the verbosity or debug level, the more disallowed entries are shown. 12 | ]] 13 | 14 | --- 15 | -- @usage 16 | -- sudo nmap -sU --script Siemens-WINCC.nse -p137 17 | -- 18 | -- @output 19 | -- Host script results: 20 | -- | Siemens-WINCC: 21 | -- |_ Detected Siemens WINCC_SRV 22 | 23 | 24 | author = "Jose Ramon Palanco, drainware" 25 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 26 | 27 | -- Current version of this script was based entirly on Implementing CIFS, by 28 | -- Christopher R. Hertel. 29 | categories = {"default", "discovery", "safe"} 30 | 31 | 32 | hostrule = function(host) 33 | 34 | -- The following is an attempt to only run this script against hosts 35 | -- that will probably respond to a UDP 137 probe. One might argue 36 | -- that sending a single UDP packet and waiting for a response is no 37 | -- big deal and that it should be done for every host. In that case 38 | -- simply change this rule to always return true. 39 | 40 | local port_t135 = nmap.get_port_state(host, 41 | {number=135, protocol="tcp"}) 42 | local port_t139 = nmap.get_port_state(host, 43 | {number=139, protocol="tcp"}) 44 | local port_t445 = nmap.get_port_state(host, 45 | {number=445, protocol="tcp"}) 46 | local port_u137 = nmap.get_port_state(host, 47 | {number=137, protocol="udp"}) 48 | 49 | return (port_t135 ~= nil and port_t135.state == "open") or 50 | (port_t139 ~= nil and port_t139.state == "open") or 51 | (port_t445 ~= nil and port_t445.state == "open") or 52 | (port_u137 ~= nil and 53 | (port_u137.state == "open" or 54 | port_u137.state == "open|filtered")) 55 | end 56 | 57 | 58 | action = function(host) 59 | 60 | local i 61 | local status 62 | local names, statistics 63 | local server_name 64 | local mac, prefix, manuf 65 | local response = {} 66 | local catch = function() return end 67 | local try = nmap.new_try(catch) 68 | 69 | 70 | -- Get the list of NetBIOS names 71 | status, names, statistics = netbios.do_nbstat(host) 72 | status, names, statistics = netbios.do_nbstat(host) 73 | status, names, statistics = netbios.do_nbstat(host) 74 | status, names, statistics = netbios.do_nbstat(host) 75 | if(status == false) then 76 | return stdnse.format_output(false, names) 77 | end 78 | 79 | -- Get the server name 80 | status, server_name = netbios.get_server_name(host, names) 81 | if(status == false) then 82 | return stdnse.format_output(false, server_name) 83 | end 84 | 85 | local step1, step2, step3, step4, step5 = nil 86 | 87 | for i = 1, #names, 1 do 88 | local padding = string.rep(" ", 17 - #names[i]['name']) 89 | local flags_str = netbios.flags_to_string(names[i]['flags']) 90 | 91 | 92 | 93 | if string.find(names[i]['name'], "WINCC_SRV") then 94 | if names[i]['suffix'] == 0x0 then 95 | step1 = true 96 | elseif names[i]['suffix'] == 0x20 then 97 | step2 = true 98 | end 99 | end 100 | 101 | if names[i]['name'] == "SIEMENS" then 102 | if names[i]['suffix'] == 0x0 then 103 | step3 = true 104 | elseif names[i]['suffix'] == 0x1e then 105 | step4 = true 106 | elseif names[i]['suffix'] == 0x1d then 107 | step5 = true 108 | end 109 | end 110 | 111 | end 112 | 113 | if step1 and step2 and step3 and step4 and step5 then 114 | info = string.format("Detected Siemens %s", server_name) 115 | table.insert(response, info) 116 | end 117 | 118 | 119 | return stdnse.format_output(true, response) 120 | 121 | 122 | end --------------------------------------------------------------------------------