Nmap Development mailing list archives

Re: [NSE] new scripts and libraries: brute library


From: David Fifield <david () bamsoftware com>
Date: Wed, 11 Aug 2010 20:16:46 -0600

On Sun, Aug 08, 2010 at 05:31:36PM +0200, Patrik Karlsson wrote:
I've been working on some stuff during the past few weeks, and as I'm back to work tomorrow, I thought I would 
"offload" it to the list.
Some of it I have already posted but I might have made changes since, so I'm including it all again.
Some of the libraries unfortunately still have chunks of (ugly) undecoded data. The foremost reason for this is the 
lack of documentation.

The scripts and libraries have all undergone limited testing, but as always I welcome further testing, comments and 
suggestions.
Here's a brief description of each library and script and a zip file with everything needed inside.

This is great stuff. It's hard to overstate what a valuable contributor
you've become. In fact, it's starting to overwhelm my powers of review.
I don't have access to all the servers these scripts work with. Because
your code has been of such uniformly high quality, I'm going to spend
less time looking over the code. For this batch, I'm going to take it in
parts, starting with the brute library.

A plea to other readers: When Patrik or anyone asks for help testing
scripts, as at http://twitter.com/nevdull77/status/20637197568, please
take the time to test. The easiest thing to do is just run the scripts
and report the output they produced. If one crashes, the script author
can usually diagnose the problem quickly.

o Brute framework
 - A smallish brute framework that provides the basic iterators and logic
 - For the moment the current scripts make use of it:
    x domcon-brute - see more information below
    x http-brute - performs password guessing against basic authentication
    x http-form-brute - performs form-based password guessing
    x informix-brute - see more information below
    x oracle-brute - see more information below
    x svn-brute - performs password guessing against subversion 
    x vnc-brute - see more information below

I wanted to see how easy it was to write a new brute script using only
the documentation for the brute library. I followed the example and made
this script to talk to a server that returns "YES\n" or "NO\n".

description = ""
categories = {}

require("brute")
require("stdnse")

function portrule(host, port)
  return port.number == 4000
end

local brute_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()
    return self.socket:connect(self.host.ip, self.port.number, "tcp")
  end,
  disconnect = function(self)
    return self.socket:close()
  end,
  check = function(self)
    return true
  end,
  login = function(self, username, password)
    local status, err, reply
    status, err = self.socket:send(username .. ":" .. password .. "\n")
    if not status then
      -- What?
    end
    status, reply = self.socket:receive_bytes(1)
    if not status then
      -- What?
    end
    if reply == "YES\n" then
      return true, brute.Account:new(username, password, "OPEN")
    else
      return false, brute.Error:new("login failed")
    end
  end
}

function action(host, port)
  local status, accounts
  status, accounts = brute.Engine:new(brute_driver, host, port):start()
  if not status then
    return accounts
  else
    return stdnse.format_output(true, accounts)
  end
end

This is a little longer than the naive brute script would be, but I feel
it is worth it for the automatic parallelization. It found a password
for "root" quickly.

$ ./nmap --datadir . --script sample-brute 192.168.0.190 -p 4000 -d2 -Pn -n --script-args brute.firstonly=1
PORT     STATE SERVICE        REASON
4000/tcp open  remoteanything syn-ack
| sample-brute:
|
|   Accounts
|     root:monkey => Login correct
|   Statistics
|_    Perfomed 25 guesses in 1 seconds, average tps: 25

What do you do when there's a socket error in the login method? Is there
a way to report it, or perhaps ask the brute library to retry the
credentials? A nice thing would be the ability to provide feedback to
automatically reduce the number of threads, like the congestion control
in the port scanning engine. I noticed that the script can say
"Re-trying"; what makes this happen and how can it be controlled? It
should be documented.

Please also include documentation for the account states "OPEN",
"LOCKED", and "DISABLED".

I notice that there is a brute.emptypass script argument. I think that
this is better controlled with a password list. Our default list already
includes the empty password.

There are other script arguments that I think could be made unnecessary
with a different interface. Instead of brute.emptypass,
brute.useraspass, brute.unique, and brute.passonly, what do you think of
allowing the user to provide his own username and password iterators? Or
course the defaults would be unpwdb.usernames and unpwdb.passwords. You
could even keep the script arguments as a convenience. I'm picturing an
interface like this:

        engine = brute.Engine:new(brute_driver, host, port)
        engine.usernames = my_username_iterator

Things like brute.useraspass and brute.unique could be nicely
implemented as wrapper iterators, rather than special-purpose code in
the library. I envision that the brute library would have the most
common iterators included in it. Someone wanting a unique list of
usernames, for example, could do

        engine.usernames = brute.unique(my_username_iterator)

(Or the library could do that transparently when the brute.unique script
argument is given.) The reason for all this is that there may be
specialized passwords, for example, that a service wants to add, along
the lines of brute.useraspass. The vnc-brute script might want to do
this:

        function extra_passwords(extras)
                local count = 0
                local status, passwords = unpwdb.passwords()
                if not status then
                        return status, passwords
                end
                return true, function(cmd)
                        if cmd == "reset" then
                                count = 0
                        end
                        if count < #extras then
                                count = count + 1
                                return extras[count]
                        end
                        return passwords(cmd)
                end
        end
        engine = brute.Engine:new(brute_driver, host, port)
        engine.passwords = function() return true, extra_passwords({"vnc", "VNC"}) end

Other iterators could do things like changing the case of letters,
appending "1", anything. Putting the knowledge in external iterators
means the brute library doesn't have to know about everything that's
possible.

I notice that by default, the library checks every password for one
user, then every password for the next, and so on. This can be
controlled through the brute.mode argument. I think that putting
passwords in the outer loop is a better default. What do people think
about this?

Another default is to keep trying credentials after a successful set is
found, in an attempt to find them all. Will this last until the unpwdb
time limit expires? What does everyone prefer for a default for this?

Related to the previous paragraph, if there's going to be a debug
message for every attempted credential, then there should definitely be
one for a successful login.

I don't understand the purpose of the check method. For many protocols
its just going to return true. All it does is stop the engine at the
very beginning if the function returns false; couldn't that just as
easily be done in the action function, and simplify brute's interface?

Since you've made it so easy to control the number of threads with
brute.threads, this is a good opportunity to measure the effect or
parallelism. I tried vnc-brute against the LAN with different script
counts. In this case the difference was slight but maybe other protocols
or network scenarios will do better.

brute.threads=1
|_    Perfomed 2498 guesses in 8 seconds, average tps: 312
brute.threads=10
|_    Perfomed 2498 guesses in 7 seconds, average tps: 356

I want you to commit this library as it is, and make further changes
under revision control. I think this is close enough to what is wanted
that we shouldn't fuss with it too much before committing it.

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


Current thread: