local nmap = require "nmap" local http = require "http" local stdnse = require "stdnse" local shortport = require "shortport" local table = require "table" description = [[ Attempts to obtain information from Trane Tracer SC devices. Trane Tracer SC is an intelligent field panel for communicating with HVAC equipment controllers deployed across several sectors including commercial facilities and others. The information is obtained from the web server that exposes sensitive content to unauthenticated users. Tested on Trane Tracer SC version 4.40.1211 and below. References: * http://websec.mx/publicacion/blog/Scripts-de-Nmap-para-Trane-Tracer-SC-HVAC ]] --- -- @usage nmap -p80 --script trane-info.nse -- -- @output -- | http-trane-info: -- | serverName: XXXXX -- | serverTime: 2017-09-24T01:03:08-05:00 -- | serverBootTime: 2017-08-03T02:06:39-05:00 -- | vendorName: Trane -- | productName: Tracer SC -- | productVersion: v4.20.1128 (release) -- | kernelVersion: 2.6.30_HwVer12AB-hydra -- | hardwareType: HwVer12AB -- | hardwareSerialNumber: XXXXX -- | devices: -- | -- | isOffline: false -- | equipmentUri: /equipment/dac/generic/1 -- | displayName: RTU-01 -- | equipmentFamily: AirHandler -- | roleDocument: BCI-I_9a8c9b8116cd392fc0b4a233405f3f5964fa6b885809c810a8d0ed5478XXXXXX__RTU_Ipak_VAV -- | deviceName: RTU-01 -- -- @xmloutput -- XXXXX -- 2017-09-24T01:05:28-05:00 -- 2017-08-03T02:06:39-05:00 -- Trane -- Tracer SC -- v4.20.1128 (release) -- 2.6.30_HwVer12AB-hydra -- HwVer12AB -- XXXXX -- --
-- /equipment/dac/generic/1 -- AirHandler -- RTU-01 -- false -- BCI-I_9a8c9b8116cd392fc0b4a233405f3f5964fa6b885809c810a8d0ed5478XXXXX__RTU_Ipak_VAV -- RTU-01 --
--- author = "Pedro Joaquin " license = "Same as Nmap--See https://nmap.org/book/man-legal.html" categories = {"discovery", "version", "safe"} portrule = function(host, port) return (shortport.http(host,port) and nmap.version_intensity() >= 7) end local function GetInformation(host, port) local output = stdnse.output_table() --Get information from /evox/about local uri = '/evox/about' local response = http.get(host, port, uri) if response['status-line'] and response['status-line']:match("200") then --Verify parsing of XML from /evox/about local deviceType = response['body']:match('serverName" val=([^<]*)/>') if not deviceType then stdnse.debug1("Problem with XML parsing of /evox/about") return nil,"Problem with XML parsing of /evox/about" end --Parse information from /evox/about local keylist = {"serverName","serverTime","serverBootTime","vendorName","productName","productVersion","kernelVersion","hardwareType","hardwareSerialNumber"} for _,key in ipairs(keylist) do stdnse.debug2("Looking for : "..key) output[key] = response['body']:match(key..'" val=([^<]*)/>') stdnse.debug2("Found : "..output[key]) output[key] = output[key]:gsub('"', "") end --Get information from /evox/equipment/installedSummary local uri = '/evox/equipment/installedSummary' local response = http.get(host, port, uri) if response['status-line'] and response['status-line']:match("200") then --Verify parsing of XML from /evox/equipment/installedSummary local error = response['body']:match('Error code: 00017') if error then stdnse.debug1("/evox/equipment/installedSummary is not available") end local equipmentUri = response['body']:match('equipmentUri" val=([^<]*)/>') if not equipmentUri then stdnse.debug1("Problem with XML parsing") end if not error then --Parse information from /evox/equipment/installedSummary local keylist = {"equipmentUri","displayName","deviceName","equipmentFamily","roleDocument","isOffline"} local _,lastequipmentUri = response['body']:find(".*equipmentUri") stdnse.debug2("lastequipmentUri : "..lastequipmentUri) local count = 1 local nextequipmentUri = 1 local devices = {} while nextequipmentUri < lastequipmentUri do local device = {} for _,key in ipairs(keylist) do stdnse.debug2("Looking for : "..key) device[key] = response['body']:match(key..'" val=([^<]*)/>',nextequipmentUri) if not device[key] then device[key] = "Not available" else device[key] = device[key]:gsub('"', "") stdnse.debug2("Found : ".. device[key]) end end _,nextequipmentUri = response['body']:find("equipmentUri",nextequipmentUri) table.insert(devices, device) count = count + 1 end output["devices"] = devices end end stdnse.debug2("status-line: "..response['status-line']) local error = response['status-line']:match('Error') if error then stdnse.debug2("Request returned a network error.") return nil, "Request returned a network error." end -- Set the port version port.version.name = "http" port.version.name_confidence = 10 port.version.product = output["productName"] port.version.version = output["productVersion"] port.version.devicetype = output["hardwareType"] table.insert(port.version.cpe, "cpe:/h:".. output["vendorName"] .. ":" .. output["productName"]) nmap.set_port_version(host, port, "hardmatched") return output end end action = function(host,port) -- Identify servers that answer 200 to invalid HTTP requests and exit as these would invalidate the tests local status_404, result_404, _ = http.identify_404(host,port) if ( status_404 and result_404 == 200 ) then stdnse.debug1("Exiting due to ambiguous response from web server on %s:%s. All URIs return status 200.", host.ip, port.number) return nil end return GetInformation(host, port) end