Nmap Development mailing list archives

Re: [RFC] Changes to HTTPAuth, addition of HTTPbrute


From: Kris Katterjohn <katterjohn () gmail com>
Date: Wed, 25 Jun 2008 00:48:35 -0500

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Thomas Buchanan wrote:
Hello,

I've been inspired by Kris's nifty username/password library to create
an actual HTTP brute forcing script.  In doing so, I decided to remove
the password guessing from HTTPAuth.nse, and create a new script for
these capabilities.  I think HTTPAuth is still useful, as the
Authentication realm can often tell you something about a web server
that you can't get otherwise.  Also, by removing the password guessing,
I think it can be moved into the "safe" category.


I agree: I like the new HTTPAuth script and I think it's fine for "safe".

The HTTP brute force script implements Basic authentication username and
password guessing (for more info, see RFC2617) against servers where the
root URL requires authentication.  It requires the latest version of
Kris's unpwdb library [1], as well as the base64.lua library attached to
this email, which implements a base64 encoding function.

Philip Pickering mentioned [2] that he was working on some Base64
utilities.  Hopefully I haven't duplicated too much of his effort.  My
library doesn't do any decoding to this point, so if somebody wants to
add that, I'm sure it would be appreciated.  Also, my encoding algorithm
probably won't win any beauty contests, but as far as I can tell it
works correctly.

Please review and test the changes to HTTPAuth, and play around with the
new HTTPbrute.nse.  If you have any issues, if it misses any logins, or
especially if you get any false positives, please let me know.  I'll
leave it up to you to provide your own username / password lists.


I've attached a patch against your HTTPAuth that fixes the warning:

SCRIPT ENGINE: ./scripts/HTTPAuth.nse:48: bad argument #1 to 'len' (string
expected, got nil)

If a server didn't send a 401 message, string.len() was called on nil.  I just
made it return if it wasn't a 401, instead of having all of the "real" code
inside a conditional block.

I've also attached a patch to fix some false positives in HTTPbrute.  I ran it
several times, and one time it gave me 7 false positives.  Now that I've fixed
that, I'm having the problem of getting my valid username/password pair to
succeed: all of the requests are getting 401 responses back.  Maybe this is a
problem with the base64 library?  Or maybe I've done something wrong and will
feel stupid after sending this email :)

Also, I've omitted HTTPbrute from the default NSE category.  If there's
strong consensus to include it, feel free.


I agree that it should not be in default.


Thanks,

Thomas


Thanks!,
Kris Katterjohn


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iQIVAwUBSGHcMf9K37xXYl36AQK0NRAAufRUI+oCk0AyxO9eqGWLgUmy5G+s8O1k
cFA5iPi4PuqOhijAKKCKWBGLkZBJZJEo5Ce7gHj2aRojkaiWokBKqu14nnwVdjbr
U7gLXircj6OG0oDka3O/mgq0SgtC3LFmWpqji9hp/QszxLdUdOHlAExpsyW6rF33
H91NNG+X2L3M/TglNdHaTOnZ7DUYK83QBrqNWyCEvYkFx2j1HcDRSJlptl64VvLW
3eD9ATHfE79ipxcKnMKNG0CjgAGoqlazC+JQWFpeR6mIS6C/uvCk2Efcuk8NBhcn
0yfkSq1F8X7zMJnThMcoRNM32SV0CTOLfPBiWtzs6z+3nOcWxkLaEL0ceZ1+oQe+
l9gAPfY+Aq7EhZMxxs5zjZhD8sVD3FlksnteHQPtg50/V2Z3748AsagiUbxnYdsC
65LYTSD1OEOyL9FG7N4msVw3JZVy7tqO+fpqeTZTZIZ51gz6UclO98KkFSPEvMZb
wVhoLXK5xVOOfcASUHALP2Di94bl+QQIZoS+iP0a5ix7eZMQ9w1qCFT2+C1eCYBY
mokU7fAPYY7SkeQOiDNjB69CT2KjMlh48rv442fdIKPzyP7NY3faaZHPi39WfiRi
86raGSODrwG74CuncHIGNJm9I+94dmwfWIMQ3bWFLYSBcokmtGtY3Lh8x5U8zsek
GLK7j7OHelo=
=TfuX
-----END PGP SIGNATURE-----
--- scripts/HTTPAuth.nse        2008-06-24 22:20:10.000000000 -0500
+++ scripts/HTTPAuth.nse        2008-06-24 23:01:36.000000000 -0500
@@ -22,31 +22,31 @@ action = function(host, port)
   local answer = http.get( host, port, "/" )
 
   --- check for 401 response code
-  if answer.status == 401 then
-    result = "HTTP Service requires authentication\n"
+  if answer.status ~= 401 then
+    return
+  end
+
+  result = "HTTP Service requires authentication\n"
+
+  -- split www-authenticate header
+  local auth_headers = {}
+  local pcre = pcre.new('\\w+( (\\w+=("[^"]+"|\\w+), *)*(\\w+=("[^"]+"|\\w+)))?',0,"C")
+  local match = function( match ) table.insert(auth_headers, match) end
+  pcre:gmatch( answer.header['www-authenticate'], match )
 
-    -- split www-authenticate header
-    local auth_headers = {}
-    local pcre = pcre.new('\\w+( (\\w+=("[^"]+"|\\w+), *)*(\\w+=("[^"]+"|\\w+)))?',0,"C")
-    local match = function( match ) table.insert(auth_headers, match) end
-    pcre:gmatch( answer.header['www-authenticate'], match )
-
-    for _, value in pairs( auth_headers ) do
-      result = result .. "  Auth type: "
-      scheme, realm = string.match(value, "(%a+).-[Rr]ealm=\"(.-)\"")
-      if scheme == "Basic" then
-        basic = true
-      end
-      if realm ~= nil then
-        result = result .. scheme .. ", realm = " .. realm .. "\n"
-      else
-        result = result .. string.match(value, "(%a+)") .. "\n"
-      end
+  for _, value in pairs( auth_headers ) do
+    result = result .. "  Auth type: "
+    scheme, realm = string.match(value, "(%a+).-[Rr]ealm=\"(.-)\"")
+    if scheme == "Basic" then
+      basic = true
+    end
+    if realm ~= nil then
+      result = result .. scheme .. ", realm = " .. realm .. "\n"
+    else
+      result = result .. string.match(value, "(%a+)") .. "\n"
     end
   end
 
-  if string.len(result) > 0 then
-    return result
-  end
+  return result
 end
 
--- scripts/HTTPbrute.nse       2008-06-24 22:20:04.000000000 -0500
+++ scripts/HTTPbrute.nse       2008-06-25 00:37:44.000000000 -0500
@@ -52,14 +52,14 @@ action = function(host, port)
         digest = base64.encode(username .. ":")
         hdr = "Basic " .. digest
         answer = http.get(host, port, '/', {header={Authorization=hdr}})
-        if answer.status ~= 401 and answer.status ~= 403 then
+        if answer.status and (answer.status ~= 401 and answer.status ~= 403) then
       output = output .. " HTTP server may accept user=\"" .. username .. "\" with blank password for Basic 
authentication\n"
      end
         -- try password = username next
         digest = base64.encode(username .. ":" .. username)
         hdr = "Basic " .. digest
         answer = http.get(host, port, '/', {header={Authorization=hdr}})
-        if answer.status ~= 401 and answer.status ~= 403 then
+        if answer.status and (answer.status ~= 401 and answer.status ~= 403) then
       output = output .. " HTTP server may accept user and password = \"" .. username .. "\" for Basic 
authentication\n"
      end
         
@@ -72,7 +72,7 @@ action = function(host, port)
          digest = base64.encode(username .. ":" .. password)
          hdr = "Basic " .. digest
          answer = http.get(host, port, '/', {header={Authorization=hdr}})
-         if answer.status ~= 401 and answer.status ~= 403 then
+         if answer.status and (answer.status ~= 401 and answer.status ~= 403) then
        output = output .. " HTTP server may accept user=\"" .. username .. "\" and password=\"" .. password .. "\" for 
Basic authentication\n"
       end
         end -- password loop
@@ -86,4 +86,4 @@ action = function(host, port)
   return
  end
 end -- end action function
- 
\ No newline at end of file
+ 

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

Current thread: