New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NSE for OpenWebNet discovery #915
Changes from 15 commits
6aa0984
2a4f8cb
28d70ba
540b75e
c7e24f4
4d0d4a9
5fb09bb
6e7276c
ceb0968
4865a6d
27ab35c
669807d
2ed6404
ff5d5ec
be446a2
337eda5
aedb765
39e9980
5de72e6
796e6dc
f563a23
be53037
79386c6
ee67c24
d6ee4ce
7b123c2
25456ad
348e234
fa69e5a
377da5d
50af9ca
127ea7c
419df0f
46c9aba
0b5cde6
c573a9f
c8fac63
b1a2a9a
c4fa595
75122fe
5362ab4
ee6cb72
00be9dd
63f9bea
aba927e
c11a72e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
local stdnse = require "stdnse" | ||
local shortport = require "shortport" | ||
local comm = require "comm" | ||
local string = require "string" | ||
|
||
description = [[ | ||
OpenWebNet is a communications protocol developed by Bticino since 2000. | ||
Retrieves the Gateway and device type. Retrieves the count and addresses | ||
of lights, multimedia and many other services running on server/servers. | ||
]] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add some References here: the piMyHome link and https://www.myopen-legrandgroup.com/solution-gallery/openwebnet/ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Committed as 337eda5. |
||
|
||
--- | ||
-- @usage | ||
-- nmap --script openwebnet-discovery | ||
-- | ||
-- @output | ||
-- | openwebnet-discover: | ||
-- | MAC address: 0-3-80-1-211-17 | ||
-- | Kernel Version: 2.3.8 | ||
-- | Net mask: 255.255.255.0 | ||
-- | IP address: 192.168.200.35 | ||
-- | Time: 19:58:33:001 | ||
-- | Date: 24.06.2017 | ||
-- | Device Type: F453AV | ||
-- | Distribution Version: 3.0.1 | ||
-- | Firmware version: 3.0.14 | ||
-- | Uptime: 5.3.28.38 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uptime should be reported in the same format that stdnse.format_difftime uses. You don't have to use that function, though, since it's already reported broken into segments like this. Your example output should look like "5d3h28m38s". Note that the piMyHome page does not show seconds, so some devices may not report that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Committed as be53037. |
||
-- | Scenarios: 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still prefer to not see lines with 0 count. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Committed as 0b5cde6 |
||
-- | Lighting: 115 | ||
-- | Automation: 13 | ||
-- | Heating: 0 | ||
-- | Burglar Alarm: 12 | ||
-- |_ Door Entry System: 0 | ||
|
||
author = "Rewanth Cool" | ||
license = "Same as Nmap--See https://nmap.org/book/man-legal.html" | ||
categories = {"discovery", "safe"} | ||
|
||
portrule = shortport.port_or_service(20000, "openwebnet") | ||
|
||
local device = { | ||
[2] = "MHServer", | ||
[4] = "MH200", | ||
[6] = "F452", | ||
[7] = "F452V", | ||
[11] = "MHServer2", | ||
[12] = "F453AV", | ||
[13] = "H4684", | ||
[15] = "F427 (Gateway Open-KNX)", | ||
[16] = "F453", | ||
[23] = "H4684", | ||
[27] = "L4686SDK", | ||
[44] = "MH200N", | ||
[51] = "F454", | ||
[200] = "F454 (new?)" | ||
} | ||
|
||
local who = { | ||
[0] = "Scenarios", | ||
[1] = "Lighting", | ||
[2] = "Automation", | ||
[3] = "Power Management", | ||
[4] = "Heating", | ||
[5] = "Burglar Alarm", | ||
[6] = "Door Entry System" | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It'd be nice to still have the full list of WHO values, even if we only query 0 through 6. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Committed as aedb765. |
||
|
||
local device_dimensions = { | ||
["Time"] = "*#13**0##", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be cleaner to have this table simply be the dimension numbers, and use a function to add the "*#13**" and "##" |
||
["Date"] = "*#13**1##", | ||
["IP address"] = "*#13**10##", | ||
["Net mask"] = "*#13**11##", | ||
["MAC address"] = "*#13**12##", | ||
["Device Type"] = "*#13**15##", | ||
["Firmware version"] = "*#13**16##", | ||
["Uptime"] = "*#13**19##", | ||
["Kernel Version"] = "*#13**23##", | ||
["Distribution Version"] = "*#13**24##" | ||
} | ||
|
||
local ACK = "*#*1##" | ||
local NACK = "*#*0##" | ||
|
||
-- Initiates a socket connection | ||
-- Returns the socket and error message | ||
local function get_socket(host, port, request) | ||
|
||
local sd, response, early_resp = comm.opencon(host, port, request) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we expect to get a banner in response to connection, we need to set the
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Committed as c573a9f |
||
|
||
if sd == nil then | ||
return nil, "Socket connection error." | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return or print (debug) the actual error, which is in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Committed as b1a2a9a |
||
end | ||
|
||
if not response then | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Committed as c573a9f |
||
return nil, "Poor internet connection or no response." | ||
end | ||
|
||
if response == NACK then | ||
return nil, "Received a negative ACK as response." | ||
end | ||
|
||
return sd, nil | ||
end | ||
|
||
local function get_response(sd, request) | ||
|
||
local res = {} | ||
local status, data | ||
|
||
sd:send(request) | ||
|
||
repeat | ||
status, data = sd:receive_buf("##", true) | ||
if status and data ~= ACK then | ||
table.insert(res, data) | ||
end | ||
if data == ACK then | ||
break | ||
end | ||
|
||
-- If response is NACK, it means the request method is not supported | ||
if data == NACK then | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to break the loop in this case. This is a major source of slowness in the script: we try to enumerate something (Scenarios, for example) that it doesn't support listing; we get a NACK; then we end up waiting for a whole read timeout before continuing with the next request. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Commited as 0b5cde6 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ran into another problem. A host closes the connection after sending the banner, but we keep trying to send on the closed socket. The symptom is
In my test case, the server did not respond to any of our queries, but always closed the socket. This means we don't have to try to reopen and reconnect. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And yet another tricky one: timeouts. If the timeout expires, we may end up reading a response to one probe as the answer to the next probe. There are 2 approaches we must take to avoid this:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I handled the false positives by capturing NACK when we get a nil as status response. I think this must work fine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Commit 00be9dd adds request_timeout. |
||
res = {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Returning There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think showing 0 devices will be more helpful to the user during reconnaissance. |
||
end | ||
until not status | ||
|
||
return res | ||
end | ||
|
||
local function format_dimensions(res) | ||
|
||
if res["Time"] then | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For "Time" and "Date", it would be preferable to send the "Date and Time" (22) query, since it saves a query. Then extract the year, month, day, hour, minute, second and pass them in a table to stdnse.format_timestamp. The table format is described in the Lua manual for os.time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Committed as 796e6dc. |
||
res["Time"] = string.gsub(res["Time"], "%.", ":") | ||
end | ||
|
||
if res["Date"] then | ||
res["Date"] = string.match(res["Date"],"%.(%d.+)$") | ||
end | ||
|
||
if res["Device Type"] then | ||
res["Device Type"] = device[ tonumber( res["Device Type"] ) ] | ||
end | ||
|
||
if res["MAC address"] then | ||
res["MAC address"] = string.gsub(res["MAC address"], "%.", "-") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We want the MAC address as colon-separated hex. Here's a list of functions that may help:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Committed as f563a23. |
||
end | ||
|
||
return res | ||
|
||
end | ||
|
||
action = function(host, port) | ||
|
||
local output = stdnse.output_table() | ||
|
||
local sd, err = get_socket(host, port, ACK) | ||
|
||
-- Socket connection creation failed | ||
if sd == nil then | ||
return err | ||
end | ||
|
||
-- Fetching list of dimensions of a device | ||
for _, v in pairs(device_dimensions) do | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Only use for _, label in ipairs({"Device Type", "Date and Time", "Uptime", "Firmware version", ...}) do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Committed as 5de72e6. |
||
|
||
stdnse.debug("Fetching " .. _) | ||
|
||
local res = get_response(sd, v) | ||
|
||
-- Extracts substring from the result | ||
-- Ex: | ||
-- Request - *#13**16## | ||
-- Response - *#13**16*3*0*14## | ||
-- Trimmed Output - 3*0*14## | ||
|
||
output[_] = string.gsub( | ||
string.sub( | ||
string.gsub( | ||
res[1], string.gsub( | ||
string.sub(v,1,-3) .. "*","*","%%*" | ||
), ""), 1, -3 | ||
), "*", ".") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is really convoluted. Try using string.match with captures instead to extract the WHO, DIMENSION, and VALUE (including There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Committed as 5de72e6. |
||
|
||
end | ||
|
||
-- Format the output based on dimension | ||
output = format_dimensions(output) | ||
|
||
-- Fetching list of each device | ||
for _, v in pairs(who) do | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should do this for every possible WHO value. Some of them may have other functions than "get the status of all X" if you call them with a WHAT of 0. For example, Device Communication (13) responds with a NACK. Instead, keep a list of ones that do respond properly like this (1 through 25, maybe, although I don't know what CEN means) and check those this way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Committed as ceb0968. |
||
|
||
stdnse.debug("Fetching the list of " .. v .. " devices.") | ||
|
||
local res = get_response(sd, "*##*#" .. _ .. "*0##") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't send the "*##" empty/invalid command first. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Committed as 39e9980. |
||
output[v] = #res | ||
|
||
end | ||
|
||
return output | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't retrieve the Gateway or "addresses of lights". Generalize this description to only say we retrieve device identifying information and number of connected devices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Committed as 337eda5.