1local brute = require "brute" 2local creds = require "creds" 3local nmap = require "nmap" 4local shortport = require "shortport" 5local stdnse = require "stdnse" 6local table = require "table" 7 8description=[[ 9Performs brute force password auditing against a Nessus vulnerability scanning daemon using the XMLRPC protocol. 10]] 11 12--- 13-- @output 14-- PORT STATE SERVICE REASON 15-- 8834/tcp open unknown syn-ack 16-- | nessus-xmlrpc-brute: 17-- | Accounts 18-- | nessus:nessus - Valid credentials 19-- | Statistics 20-- |_ Performed 1933 guesses in 26 seconds, average tps: 73 21-- 22-- @args nessus-xmlrpc-brute.threads sets the number of threads. 23-- @args nessus-xmlrpc-brute.timeout socket timeout for connecting to Nessus (default 5s) 24 25author = "Patrik Karlsson" 26 27license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 28 29categories = {"intrusive", "brute"} 30 31 32portrule = shortport.port_or_service(8834, "ssl/http", "tcp") 33 34local arg_timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME..'.timeout')) 35arg_timeout = (arg_timeout or 5) * 1000 36local arg_threads = tonumber(stdnse.get_script_args("nessus-xmlrpc-brute.threads")) 37 38local function authenticate(host, port, username, password) 39 local post_data = ("login=%s&password=%s"):format(username, password) 40 41 local headers = { 42 "POST /login HTTP/1.1", 43 "User-Agent: Nmap", 44 ("Host: %s:%d"):format(host.ip, port.number), 45 "Accept: */*", 46 ("Content-Length: %d"):format(#post_data), 47 "Content-Type: application/x-www-form-urlencoded", 48 } 49 50 local data = table.concat(headers, "\r\n") .. "\r\n\r\n" .. post_data 51 local socket = brute.new_socket() 52 socket:set_timeout(arg_timeout) 53 54 local status, err = socket:connect(host, port) 55 if ( not(status) ) then 56 return false, "Failed to connect to server" 57 end 58 local status, err = socket:send(data) 59 if ( not(status) ) then 60 return false, "Failed to send request to server" 61 end 62 local status, response = socket:receive() 63 socket:close() 64 if ( not(status) ) then 65 return false, "Failed to receive response from server" 66 end 67 return status, response 68end 69 70Driver = 71{ 72 new = function (self, host, port ) 73 local o = { host = host, port = port } 74 setmetatable (o,self) 75 self.__index = self 76 return o 77 end, 78 79 connect = function ( self ) return true end, 80 81 login = function( self, username, password ) 82 83 local status, response = authenticate(self.host, self.port, username, password) 84 if ( status and response ) then 85 if ( response:match("^HTTP/1.1 200 OK.*<status>OK</status>") ) then 86 return true, creds.Account:new(username, password, creds.State.VALID) 87 elseif ( response:match("^HTTP/1.1 200 OK.*<status>ERROR</status>") ) then 88 return false, brute.Error:new("incorrect login") 89 end 90 end 91 local err = brute.Error:new( "incorrect response from server" ) 92 err:setRetry(true) 93 return false, err 94 end, 95 96 disconnect = function( self ) return true end, 97} 98 99local function fail(err) return stdnse.format_output(false, err) end 100 101action = function(host, port) 102 103 local status, response = authenticate(host, port, "nmap-ssl-test-probe", "nmap-ssl-test-probe") 104 if ( not(status) ) then 105 return fail(response) 106 end 107 -- patch the protocol due to the ugly way the Nessus web server works. 108 -- The server answers non-ssl connections as legitimate http stating that 109 -- the server should be connected to using https on the same port. ugly. 110 if ( status and response:match("^HTTP/1.1 400 Bad request\r\n") ) then 111 port.protocol = "ssl" 112 status, response = authenticate(host, port, "nmap-ssl-test-probe", "nmap-ssl-test-probe") 113 if ( not(status) ) then 114 return fail(response) 115 end 116 end 117 118 if ( not(response:match("^HTTP/1.1 200 OK.*Server: NessusWWW.*<status>ERROR</status>")) ) then 119 return fail("Failed to detect Nessus Web server") 120 end 121 122 local engine = brute.Engine:new(Driver, host, port) 123 if ( arg_threads ) then 124 engine:setMaxThreads(arg_threads) 125 end 126 engine.options.script_name = SCRIPT_NAME 127 local result 128 status, result = engine:start() 129 return result 130end 131