Patches for http.lua and adding support in httpspider.lua

Vinamra Bhatia <[email protected]>
Newsgroups gmane.comp.security.nmap.devel
Message-ID <CAP+gV2zrYPJ8m66fv+BRmY-cf=ssm04N5gDsaXNW6h2sRN1hFw@mail.gmail.com>
Hello All,
I was trying to add cookie support in httpspider.lua as I wanted to check
my XSS Script. However, I wasn't able to receive cookies on making minor
modifications to the library. I was trying to use DVWA for the purpose
which was running on a VM in my machine.

After quite a few hours of debugging, I found this.
When the spider library goes to {ip}/dvwa/, it gets a cookie in return and
in Location Header, we have login.php.
Instead of going to /dvwa/login.php, it rather goes to login.php. Since
login.php doesnt exist in the VM, it returns of 404 error code.
I realized that url_parse function in the library isnt taking into account
the relative URLs perfectly. Thus, I am submitting a patch which I feel
will solve the problem.

Now, i also noticed that in HTTP library get function, if a link redirects,
it fails to take the cookie generated by the previous link. Hence, We need
to append the cookies accordingly.
I have taken care of the following 3 scenarios:
1. if the response received from the redirect doesnt have any cookie, it
becomes equal to the previous cookie.
2. If response received generates a cookie and the name of the cookie is
different from the previous cookie, it appends it simply in the
option.cookies table.
3. If response received generates a cookie and the name of the cookie is
same as the previous redirect cookie, it updates the value in that
particular cookie name.

I would love to receive feedbacks on this and would request you all to
please test the patches.
I am also attaching the modifications I did in httpspider library to add
cookie support.

Pull Requests for the same patches:
https://github.com/nmap/nmap/pull/912
https://github.com/nmap/nmap/pull/913

Cheers
Vinamra

_______________________________________________
Sent through the dev mailing list
https://nmap.org/mailman/listinfo/dev
Archived at http://seclists.org/nmap-dev/
httpspiderlib.patch (text/x-patch, 3.7 KB)
Index: nselib/httpspider.lua
===================================================================
--- nselib/httpspider.lua	(revision 36817)
+++ nselib/httpspider.lua	(working copy)
@@ -125,6 +125,7 @@
     o.timeout  = options.timeout or 10000
     o.whitelist = o.whitelist or {}
     o.blacklist = o.blacklist or {}
+    o.cookies = o.cookies or {}
     local removewww = function(url) return string.gsub(url, "^www%.", "") end
 
     -- set up the appropriate matching functions
@@ -633,7 +634,7 @@
     o:loadLibraryArguments()
     o:loadDefaultArguments()
 
-    local response = http.get(o.host, o.port, '/', { timeout = o.options.timeout, redirect_ok = o.options.redirect_ok, no_cache = o.options.no_cache } )
+    local response = http.get(o.host, o.port, '/', { timeout = o.options.timeout, redirect_ok = o.options.redirect_ok, no_cache = o.options.no_cache, cookies=o.options.cookies } )
 
     if ( not(response) or 'table' ~= type(response) ) then
       return
@@ -832,7 +833,7 @@
         end
         if is_web_file then
           stdnse.debug2("%s: Using GET: %s", LIBRARY_NAME, file)
-          response = http.get(url:getHost(), url:getPort(), url:getFile(), { timeout = self.options.timeout, redirect_ok = self.options.redirect_ok, no_cache = self.options.no_cache } )
+          response = http.get(url:getHost(), url:getPort(), url:getFile(), { timeout = self.options.timeout, redirect_ok = self.options.redirect_ok, no_cache = self.options.no_cache, cookies = self.options.cookies } )
         else
           stdnse.debug2("%s: Using HEAD: %s", LIBRARY_NAME, file)
           response = http.head(url:getHost(), url:getPort(), url:getFile())
@@ -839,7 +840,29 @@
         end
       else
         -- fetch the url, and then push it to the processed table
-        response = http.get(url:getHost(), url:getPort(), url:getFile(), { timeout = self.options.timeout, redirect_ok = self.options.redirect_ok, no_cache = self.options.no_cache } )
+        response = http.get(url:getHost(), url:getPort(), url:getFile(), { timeout = self.options.timeout, redirect_ok = self.options.redirect_ok, no_cache = self.options.no_cache, cookies = self.options.cookies } )
+        if (self.options and self.options.cookies and #self.options.cookies>0) then
+          --We replace the value of the cookie if same name cookie exists
+          --Else, we append it in the end.
+          local flag = 0
+          for k,v in pairs(response.cookies) do
+            for k1,v1 in pairs(self.options.cookies) do
+              flag = 0   
+              if(v.name == v1.name) then
+                self.options.cookies[k1].value = response.cookies[k].value
+                flag = 1
+                break
+              end 
+            end
+            if (flag == 0) then
+              self.options.cookies[#options.cookies+1] = response.cookies[k]
+            end
+          end
+        else
+          if self.options and self.options.cookies then
+            self.options.cookies = response.cookies
+          end
+        end         
       end
 
       self.processed[tostring(url)] = true
@@ -906,7 +929,9 @@
     if ( nil == self.options.doscraping ) then
       self.options.doscraping = stdnse.get_script_args(sn .. ".doscraping")
     end
-
+    if ( nil == self.options.cookies ) then
+      self.options.cookies = stdnse.get_script_args(sn .. ".cookies")
+    end    
   end,
 
   -- Loads the argument on a library level
@@ -937,6 +962,9 @@
     if ( nil == self.options.doscraping ) then
       self.options.doscraping = stdnse.get_script_args(ln .. ".doscraping")
     end
+    if ( nil == self.options.cookies ) then
+      self.options.cookies = stdnse.get_script_args(ln .. ".cookies")
+    end
   end,
 
   -- Loads any defaults for arguments that were not set
httplib.patch (text/x-patch, 1.9 KB)
Index: nselib/http.lua
===================================================================
--- nselib/http.lua	(revision 36817)
+++ nselib/http.lua	(working copy)
@@ -1571,7 +1571,11 @@
   if ( not(u.host) ) then
     -- we're dealing with a relative url
     u.host = stdnse.get_hostname(host)
-    u.path = ((u.path:sub(1,1) == "/" and "" ) or "/" ) .. u.path -- ensuring leading slash
+    if ( u.path:sub(1,1) == "/") then
+      u.path = ((u.path:sub(1,1) == "/" and "" ) or "/" ) .. u.path -- ensuring leading slash
+    else
+      u.path = ((path:sub(1,1) == "/" and "" ) or "/" ) .. path .. ((path:sub(#path,#path) == "/" and "" ) or "/" ) .. u.path
+    end
   end
   -- do port fixup
   u.port = u.port or get_default_port(u.scheme) or port.number
@@ -1640,6 +1644,30 @@
     response, state = lookup_cache("GET", u.host, u.port, u.path, options);
     if ( response == nil ) then
       response = generic_request(u.host, u.port, "GET", u.path, options)
+      if(response.cookies and #response.cookies>0) then 
+        if (options and options.cookies and #options.cookies>0) then
+          --We replace the value of the cookie if same name cookie exists
+          --Else, we append it in the end.
+          local flag = 0
+          for k,v in pairs(response.cookies) do
+            for k1,v1 in pairs(options.cookies) do
+              flag = 0   
+              if(v.name == v1.name) then
+                options.cookies[k1].value = response.cookies[k].value
+                flag = 1
+                break
+              end 
+            end
+            if (flag == 0) then
+              options.cookies[#options.cookies+1] = response.cookies[k]
+            end
+          end
+        else
+          if options and options.cookies then
+            options.cookies = response.cookies
+          end
+        end
+      end      
       insert_cache(state, response);
     end
     u = parse_redirect(host, port, path, response)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.