mirror of
https://github.com/nmap/nmap.git
synced 2025-12-10 09:49:05 +00:00
Many scripts were documented as using timespecs (10s, 5000ms, etc) for timeout script-args, but one 1 or 2 actually did. Now all timeout script-args will accept timespecs, except those which took a number of milliseconds, which remain unchanged. Also fixed some documentation issues (missing script name in arg description, missing nsedoc for args, etc)
162 lines
5.3 KiB
Lua
162 lines
5.3 KiB
Lua
local brute = require "brute"
|
|
local creds = require "creds"
|
|
local nmap = require "nmap"
|
|
local shortport = require "shortport"
|
|
local stdnse = require "stdnse"
|
|
local string = require "string"
|
|
local bit = require "bit"
|
|
local bin = require "bin"
|
|
local table = require "table"
|
|
description = [[
|
|
Performs brute force password auditing against the pcAnywhere remote access protocol.
|
|
|
|
Due to certain limitations of the protocol, bruteforcing
|
|
is limited to single thread at a time.
|
|
After a valid login pair is guessed the script waits
|
|
some time until server becomes available again.
|
|
|
|
]]
|
|
|
|
---
|
|
-- @usage
|
|
-- nmap --script=pcanywhere-brute <target>
|
|
--
|
|
-- @output
|
|
-- 5631/tcp open pcanywheredata syn-ack
|
|
-- | pcanywhere-brute:
|
|
-- | Accounts
|
|
-- | administrator:administrator - Valid credentials
|
|
-- | Statistics
|
|
-- |_ Performed 2 guesses in 55 seconds, average tps: 0
|
|
--
|
|
-- @args pcanywhere-brute.timeout socket timeout for connecting to PCAnywhere (default 10s)
|
|
|
|
|
|
author = "Aleksandar Nikolic"
|
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
|
categories = {"intrusive", "brute"}
|
|
|
|
|
|
portrule = shortport.port_or_service(5631, "pcanywheredata")
|
|
|
|
local arg_timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout"))
|
|
arg_timeout = (arg_timeout or 10) * 1000
|
|
|
|
-- implements simple xor based encryption which the server expects
|
|
local function encrypt(data)
|
|
local result = {}
|
|
local xor_key = 0xab
|
|
local k = 0
|
|
if data then
|
|
result[1] = bit.bxor(string.byte(data),xor_key)
|
|
for i = 2,string.len(data) do
|
|
result[i] = bit.bxor(result[i-1],string.byte(data,i),i-2)
|
|
end
|
|
end
|
|
return string.char(table.unpack(result))
|
|
end
|
|
|
|
local retry = false -- true means we found valid login and need to wait
|
|
|
|
Driver = {
|
|
|
|
new = function(self, host, port)
|
|
local o = {}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
o.host = host
|
|
o.port = port
|
|
return o
|
|
end,
|
|
|
|
connect = function( self )
|
|
self.socket = nmap.new_socket()
|
|
local response
|
|
local err
|
|
local status = false
|
|
|
|
stdnse.sleep(2)
|
|
-- when we hit a valid login pair, server enters some kind of locked state
|
|
-- so we need to wait for some time before trying next pair
|
|
-- variable "retry" signifies if we need to wait or this is just not pcAnywhere server
|
|
while not status do
|
|
status, err = self.socket:connect(self.host, self.port)
|
|
self.socket:set_timeout(arg_timeout)
|
|
if(not(status)) then
|
|
return false, brute.Error:new( "Couldn't connect to host: " .. err )
|
|
end
|
|
status, err = self.socket:send(bin.pack("H","00000000")) --initial hello
|
|
status, response = self.socket:receive_bytes(0)
|
|
if not status and not retry then
|
|
break
|
|
end
|
|
stdnse.print_debug("in a loop")
|
|
stdnse.sleep(2) -- needs relatively big timeout between retries
|
|
end
|
|
if not status or string.find(response,"Please press <Enter>") == nil then
|
|
--probably not pcanywhere
|
|
stdnse.print_debug(1, "%s: not pcAnywhere", SCRIPT_NAME)
|
|
return false, brute.Error:new( "Probably not pcAnywhere." )
|
|
end
|
|
retry = false
|
|
status, err = self.socket:send(bin.pack("H","6f06ff")) -- downgrade into legacy mode
|
|
status, response = self.socket:receive_bytes(0)
|
|
|
|
status, err = self.socket:send(bin.pack("H","6f61000900fe0000ffff00000000")) -- auth capabilities I
|
|
status, response = self.socket:receive_bytes(0)
|
|
|
|
status, err = self.socket:send(bin.pack("H","6f620102000000")) -- auth capabilities II
|
|
status, response = self.socket:receive_bytes(0)
|
|
if not status or (string.find(response,"Enter user name") == nil and string.find(response,"Enter login name") == nil) then
|
|
stdnse.print_debug(1, "%s: handshake failed", SCRIPT_NAME)
|
|
return false, brute.Error:new( "Handshake failed." )
|
|
end
|
|
return true
|
|
end,
|
|
|
|
login = function (self, user, pass)
|
|
local response
|
|
local err
|
|
local status
|
|
stdnse.print_debug( "Trying %s/%s ...", user, pass )
|
|
-- send username and password
|
|
-- both are prefixed with 0x06, size and are encrypted
|
|
status, err = self.socket:send(bin.pack("C",0x06) .. bin.pack("C",string.len(user)) .. encrypt(user) ) -- send username
|
|
status, response = self.socket:receive_bytes(0)
|
|
if not status or string.find(response,"Enter password") == nil then
|
|
stdnse.print_debug(1, "%s: Sending username failed", SCRIPT_NAME)
|
|
return false, brute.Error:new( "Sending username failed." )
|
|
end
|
|
-- send password
|
|
status, err = self.socket:send(bin.pack("C",0x06) .. bin.pack("C",string.len(pass)) .. encrypt(pass) ) -- send password
|
|
status, response = self.socket:receive_bytes(0)
|
|
if not status or string.find(response,"Login unsuccessful") or string.find(response,"Invalid login.")then
|
|
stdnse.print_debug(1, "%s: Incorrect username or password", SCRIPT_NAME)
|
|
return false, brute.Error:new( "Incorrect username or password." )
|
|
end
|
|
|
|
if status then
|
|
retry = true -- now the server is in "locked mode", we need to retry next connection a few times
|
|
return true, brute.Account:new( user, pass, creds.State.VALID)
|
|
end
|
|
return false,brute.Error:new( "Incorrect password" )
|
|
end,
|
|
|
|
disconnect = function( self )
|
|
self.socket:close()
|
|
return true
|
|
end
|
|
|
|
}
|
|
|
|
action = function( host, port )
|
|
|
|
local status, result
|
|
local engine = brute.Engine:new(Driver, host, port)
|
|
engine.options.script_name = SCRIPT_NAME
|
|
engine.max_threads = 1 -- pcAnywhere supports only one login at a time
|
|
status, result = engine:start()
|
|
|
|
return result
|
|
end
|