Nmap Development mailing list archives

Re: Suspect that --host-timeout is not working in 4.50?


From: jah <jah () zadkiel plus com>
Date: Fri, 21 Dec 2007 03:00:00 +0000

On 16/12/2007 04:08, Randolph Reitz wrote:

On Dec 15, 2007, at 3:03 PM, jah wrote:

On 15/12/2007 00:34, Randolph Reitz wrote:


[scanner@clouseau ~]$ nmap -d -sS -F --host_timeout 1m -A 131.225.136.140
I've got a feeling that one of the scripts running during the above scan you gave as an example is causing a problem for you (either bruteTelnet or anonFTP), would you mind running the following similar scan:

nmap -sS -p21,23 --script=bruteTelnet,anonFTP --script-trace -d3 --log-errors 131.225.136.140

I'm not sure whether nse scripts are meant to obey host-timeout rules or not, but neither of these scripts should take very long, certainly not the times you're seeing and using script-trace should help identify what is causing the massive delay.

Regards,

jah

Hi,

I ran the nmap command for ~11 minutes and the output file is ...

[scanner@clouseau ~]$ ls -lt | head
total 2231076
-rw-rw-r--   1 scanner scanner 2207219285 Dec 15 21:52 namp_test.out

Wow, that's big. Looks like a loop that never ends. Here is the first minute ...



--
This email has been verified as Virus free
Virus Protection and more available at http://www.plus.net



There is no firewall at the Fermilab border, so you can run the command if you wish.

So there are two issues here. One, the script doesn't obey --host-timeout and Two, there's a bug, I think, in bruteTelnet. Can anyone shed any light on the first? Are NSE scripts supposed to obey --host-timeout? Is there a way for the script to get that information from nmap api?

On the second, some observations from playing with the script.
The failure occurs when the 10th user and/or pass has been tried at which point the output from NSOCK goes crazy outputting nearly 160000 lines every second:

   NSOCK (212.6570s) nsock_loop() started (timeout=50ms). 0 events pending

It doesn't take many seconds to have a several gigabyte file when piping nmaps output!

Using Wireshark, I was able to determine that the script opens a new socket for each login attempt (rather than using the available number of login attempts (in this case 2) and reconnecting once the telnet server breaks the connection). The script never closes any (well, maybe just the one) connections that it creates (until script conclusion or a forced abort whereupon RST, ACKs are sent) and I assume that it runs out of the 10 allowed sockets available to the script.

I've attached a patch (and the modified script too for Randys benefit) that hopefully closes each socket after an unsuccessful login attempt, but try as I might, I cannot sort out the business of utilising remaining login attempts in a given connection... perhaps someone with more nouse will have a go.

Just for extra info, in a given connection, Randys server seems to ask for a username and then appears to validate it and doesn't prompt for a password:

   login:

then responds with the following after an unsuccessful login:

   Login incorrect\r\n
   login:

and then:

   Login incorrect\r\n

This script could be much faster if it would utilise that remaining login attempt because it would (in this case) halve the number of times needed to 'negotiate options' - about 14 secs each time. Finally, if these are indeed usernames that are requested, the script ought to be able to move to the next username in the userpass table rather than repeating the username for each user:pass pair. In this case, it tries 'guest' 3 times, 'root' 4 times, 'admin' ...

jah
id='bruteforce'
author = 'Eddie Bell <ejlbell () gmail com>'
description='brute force telnet login credientials'
license = 'See nmaps COPYING for licence'
categories = {'intrusive'}

require('shortport')
require('stdnse')
require('strbuf')

local soc
local catch = function() soc:close() end
local try = nmap.new_try(catch)

portrule = shortport.port_or_service(23, 'telnet')

local escape_cred = function(cred) 
        if cred == '' then
                return '<blank>'
        else
                return cred 
        end
end

--[[
Returns a function which returns the next user/pass pair each time
it is called. When no more pairs are available nil is returned. 

There are plenty more possible pairs but we need to find
a compromise between speed and coverage
--]]

local new_auth_iter = function()
        local userpass = { 
                -- guest
                {'guest', ''}, {'guest', 'guest'}, {'guest', 'visitor'},

                -- root
                {'root', ''}, {'root', 'root'}, 
                {'root', 'pass'}, {'root', 'password'},

                -- admin
                {'admin', ''}, {'admin', 'admin'},
                {'admin', 'pass'}, {'admin', 'password'},

                -- adminstrator
                {'adminstrator', ''}, {'adminstrator', 'adminstrator'},
                {'adminstrator', 'password'}, {'adminstrator', 'pass'},
                
                -- others
                {'visitor', ''}, {'netman', 'netman'}, {'Admin', 'Admin'},
                {'manager', 'manager'}, {'security', 'security'},
                {'username', 'password'}, {'user', 'pass'}, 

                -- sentinel 
                {nil, nil}
        }

        local i = 1 
        return function(direction)
                if not userpass[i][1] then 
                        return
                 end

                i = i + 1
                stdnse.print_debug(3, id .. " " .. 
                                  userpass[i-1][1] .. ":" .. escape_cred(userpass[i-1][2]))
                return userpass[i-1][1], userpass[i-1][2]
        end
end

--[[
Go through telnet's option palaver so we can get to the login prompt.
We just deny every options the server asks us about.
--]]

local negotiate_options = function(result)
        local index, x, opttype, opt, retbuf

        index = 0
        retbuf = strbuf.new()

        while true do

                -- 255 is IAC (Interpret As Command)
                index, x = string.find(result, '\255', index)

                if not index then 
                        break 
                end

                opttype = string.byte(result, index+1)
                opt = string.byte(result, index+2)

                -- don't want it! won't do it! 
                if opttype == 251 or opttype == 252 then
                        opttype = 254
                elseif opttype == 253 or opttype == 254 then
                        opttype = 252
                end

                retbuf = retbuf .. string.char(255)
                retbuf = retbuf .. string.char(opttype)
                retbuf = retbuf .. string.char(opt)
                index = index + 1
        end     
        soc:send(strbuf.dump(retbuf))
end

--[[
A semi-state-machine that takes action based on output from the
server. Through pattern matching, it tries to deem if a user/pass
pair is valid. Telnet does not have a way of telling the client
if it was authenticated....so we have to make an educated guess
--]]

local brute_line = function(line, user, pass, usent)

        if (line:find 'incorrect' or line:find 'failed' or line:find 'denied' or 
            line:find 'invalid' or line:find 'bad') and usent then
                usent = false
                return 2, nil, usent 

        elseif (line:find '[/>%%%$#]+' or line:find "last login%s*:" or
                line:find '%u:\\') and not
               (line:find 'username%s*:' and line:find 'login%s*:') and
               usent then
                return 1, escape_cred(user) .. ' - ' .. escape_cred(pass)  .. '\n', usent
                
        elseif line:find 'username%s*:' or line:find 'login%s*:' then
                try(soc:send(user .. '\r\0'))
                usent = true

        elseif line:find 'password%s*:' or line:find 'passcode%s*:' then
                -- fix, add 'password only' support
                if not usent then return 1, nil, usent end
                try(soc:send(pass .. '\r\0'))
        end

        return 0, nil, usent
end

--[[
Splits the input into lines and passes it to brute_line()
so it can try to login with <user> and <pass>

return value: 
        (1, user:pass)   - valid pair
        (2, nil)         - invalid pair
        (3, nil)         - disconnected and invalid pair
        (4, nil)         - disconnected and didn't send pair
--]]

local brute_cred = function(user, pass)
        local status, ret, value, usent, results

        usent = false ; ret = 0

        while true do
                status, results = soc:receive_lines(1)

                -- remote host disconnected
                if not status then 
                        if usent then return 3 
                        else return 4 
                        end
                end

                if (string.byte(results, 1) == 255) then
                        negotiate_options(results)
                end

                results = string.lower(results)

                for line in results:gmatch '[^\r\n]+' do 
                        ret, value, usent = brute_line(line, user, pass, usent)
                        if (ret > 0) then
                                return ret, value
                        end
                end
        end
        return 1, "error -> this should never happen"
end

action = function(host, port)
        local pair, status, auth_iter 
        local user, pass, count, rbuf
        
        pair = nil ; status = 3 ; count = 0
        auth_iter = new_auth_iter(); 

        soc = nmap.new_socket()
        soc:set_timeout(4000)

        -- continually try user/pass pairs (reconnecting, if we have to)
    -- until we find a valid one or we run out of pairs
        while not (status == 1) do

                if status == 2 or status == 3 then
                        user, pass = auth_iter() 
                end

                -- make sure we don't get stuck in a loop
                if status == 4 then
                        count = count + 1
                        if count > 3 then return nil end
                else count = 0 end

                -- no more users left
                if not user then break end

                if status == 3 or status == 4 then
                        soc:close()
                        try(soc:connect(host.ip, port.number, port.protocol))
                end

                status, pair = brute_cred(user, pass)
        end
        soc:close()
        return pair
end
Index: bruteTelnet.nse
===================================================================
--- bruteTelnet.nse     (revision 6616)
+++ bruteTelnet.nse     (working copy)
@@ -9,7 +9,7 @@
 require('strbuf')
 
 local soc
-local catch = function() soc.close() end
+local catch = function() soc:close() end
 local try = nmap.new_try(catch)
 
 portrule = shortport.port_or_service(23, 'telnet')
@@ -210,6 +210,7 @@
                if not user then break end
 
                if status == 3 or status == 4 then
+                       soc:close()
                        try(soc:connect(host.ip, port.number, port.protocol))
                end
 

_______________________________________________
Sent through the nmap-dev mailing list
http://cgi.insecure.org/mailman/listinfo/nmap-dev
Archived at http://SecLists.Org

Current thread: