diff --git a/scripts/ftp-brute.nse b/scripts/ftp-brute.nse index 15c4f5213..7835f498e 100644 --- a/scripts/ftp-brute.nse +++ b/scripts/ftp-brute.nse @@ -1,206 +1,121 @@ description = [[ Performs brute force password auditing against FTP servers. -This uses the standard unpwdb username/password list. However, in tests FTP servers are -significantly slower than other servers when responding, so the number of usernames/passwords -can be artificially limited using script arguments. +Based on old ftp-brute.nse script by Diman Todorov, Vlatko Kosturjak and Ron Bowes. ]] --- +-- @usage +-- nmap --script ftp-brute -p 21 +-- +-- This script uses brute library to perform password +-- guessing. +-- -- @output --- PORT STATE SERVICE REASON --- 21/tcp open ftp syn-ack --- | ftp-brute: --- | | anonymous: IEUser@ --- |_ |_ test: password +-- PORT STATE SERVICE +-- 21/tcp open ftp +-- | my-ftp-brute: +-- | Accounts +-- | root:root - Valid credentials +-- | Statistics +-- |_ Performed 510 guesses in 610 seconds, average tps: 0 -- --- @args userlimit The number of user accounts to try (default: unlimited). --- @args passlimit The number of passwords to try (default: unlimited). --- @args limit Set userlimlt and passlimit at the same time. - --- 2008-11-06 Vlatko Kosturjak --- Modified xampp-default-auth script to generic ftp-brute script --- --- 2009-09-18 Ron Bowes --- Made into an actual bruteforce script (previously, it only tried one username/password). - -author = "Diman Todorov, Vlatko Kosturjak, Ron Bowes" +-- @args timeout the amount of secounds to wait for a response on the socket. +-- Lowering this value may result in a higher throughput for servers +-- having a delayed response on incorrect login attempts. (default: 5) +author = "Aleksandar Nikolic" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"intrusive", "brute"} -categories = {"brute", "intrusive"} - -require "shortport" -require "stdnse" -require "unpwdb" +require 'shortport' +require 'brute' +require 'creds' portrule = shortport.port_or_service(21, "ftp") -local function get_limits() - local userlimit = -1 - local passlimit = -1 +local arg_timeout = stdnse.get_script_args(SCRIPT_NAME .. ".timeout") or 5 - if(nmap.registry.args.userlimit) then - userlimit = tonumber(nmap.registry.args.userlimit) - end +Driver = { - if(nmap.registry.args.passlimit) then - passlimit = tonumber(nmap.registry.args.passlimit) - end + new = function(self, host, port) + local o = {} + setmetatable(o, self) + self.__index = self + o.host = host + o.port = port + return o + end, - if(nmap.registry.args.limit) then - userlimit = tonumber(nmap.registry.args.limit) - passlimit = tonumber(nmap.registry.args.limit) - end + connect = function( self ) + self.socket = nmap.new_socket() + status, err = self.socket:connect(self.host, self.port) + self.socket:set_timeout(tonumber(arg_timeout) * 1000) + if(not(status)) then + return false, brute.Error:new( "Couldn't connect to host: " .. err ) + end + return true + end, + + login = function (self, user, pass) + local status, err + local res = "" - return userlimit, passlimit -end -local function login(host, port, user, pass) - local status, err - local res = "" - - -- Create a new socket - local socket = nmap.new_socket() - status, err = socket:connect(host, port) - if(not(status)) then - socket:close() - return false, "Couldn't connect to host: " .. err - end - - status, err = socket:send("USER " .. user .. "\r\n") - if(not(status)) then - socket:close() - return false, "Couldn't send login: " .. err - end - - status, err = socket:send("PASS " .. pass .. "\n\n") - if(not(status)) then - socket:close() - return false, "Couldn't send login: " .. err - end - - -- Create a buffer and receive the first line - local buffer = stdnse.make_buffer(socket, "\r?\n") - local line = buffer() - - -- Loop over the lines - while(line)do - stdnse.print_debug("Received: %s", line) - if(string.match(line, "^230")) then - stdnse.print_debug(1, "ftp-brute: Successful login: %s/%s", user, pass) - socket:close() - return true, true - elseif(string.match(line, "^530")) then - socket:close() - return true, false - elseif(string.match(line, "^220")) then - elseif(string.match(line, "^331")) then - else - stdnse.print_debug(1, "ftp-brute: WARNING: Unhandled response: %s", line) + status, err = self.socket:send("USER " .. user .. "\r\n") + if(not(status)) then + return false, brute.Error:new("Couldn't send login: " .. err) end - line = buffer() - end - - socket:close() - return false, "Login didn't return a proper response" -end - -local function go(host, port) - local status, err - local result - local userlimit, passlimit = get_limits() - local authcombinations = { - {user="anonymous", password="IEUser@"}, -- Anonymous user - {user="nobody", password="xampp"} -- XAMPP default ftp - } - - -- Load accounts from unpwdb - local usernames, username, passwords, password - - -- Load the usernames - status, usernames = unpwdb.usernames() - if(not(status)) then - return false, "Couldn't load username list: " .. usernames - end - - -- Load the passwords - status, passwords = unpwdb.passwords() - if(not(status)) then - return false, "Couldn't load password list: " .. usernames - end - - -- Figure out how many - local i = 0 - local j = 0 - - -- Add the passwords to the authcombinations table - password = passwords() - while (password) do - -- Limit the passwords - i = i + 1 - if(passlimit > 0 and i > passlimit) then - break + status, err = self.socket:send("PASS " .. pass .. "\n\n") + if(not(status)) then + return false, brute.Error:new("Couldn't send login: " .. err) end - j = 0 - username = usernames() - while(username) do - -- Limit the usernames - j = j + 1 - if(userlimit > 0 and j > userlimit) then - break + -- Create a buffer and receive the first line + local buffer = stdnse.make_buffer(self.socket, "\r?\n") + local line = buffer() + + -- Loop over the lines + while(line)do + stdnse.print_debug("Received: %s", line) + if(string.match(line, "^230")) then + stdnse.print_debug(1, "ftp-brute: Successful login: %s/%s", user, pass) + return true, brute.Account:new( user, pass, creds.State.VALID) + elseif(string.match(line, "^530")) then + return false, brute.Error:new( "Incorrect password" ) + elseif(string.match(line, "^220")) then + elseif(string.match(line, "^331")) then + else + stdnse.print_debug(1, "ftp-brute: WARNING: Unhandled response: %s", line) + local err = brute.Error:new("Unhandled response") + err:setRetry(true) + return false, err end - table.insert(authcombinations, {user=username, password=password}) - username = usernames() + line = buffer() end - usernames('reset') - password = passwords() + + return false, brute.Error:new("Login didn't return a proper response") + end, + + disconnect = function( self ) + self.socket:close() + return true end + - stdnse.print_debug(1, "ftp-brute: Loaded %d username/password pairs", #authcombinations) +} - local results = {} - for _, combination in ipairs(authcombinations) do +action = function( host, port ) + + local status, result + local engine = brute.Engine:new(Driver, host, port) + engine.options.script_name = SCRIPT_NAME - -- Attempt a login - status, result = login(host, port, combination.user, combination.password) - - -- Check for an error - if(not(status)) then - return false, result - end - - -- Check for a success - if(status and result) then - table.insert(results, combination) - end - end - - - return true, results + status, result = engine:start() + + return result end - -action = function(host, port) - local response = {} - local status, results = go(host, port) - - if(not(status)) then - return stdnse.format_output(false, results) - end - - if(#results == 0) then - return stdnse.format_output(false, "No accounts found") - end - - for i, v in ipairs(results) do - table.insert(response, string.format("%s: %s\n", v.user, v.password)) - end - - return stdnse.format_output(true, response) -end -