diff --git a/scripts/http-methods.nse b/scripts/http-methods.nse index ed73f1011..9f229ab29 100644 --- a/scripts/http-methods.nse +++ b/scripts/http-methods.nse @@ -15,13 +15,13 @@ individually to see if they are subject to e.g. IP address restrictions. -- possible. -- -- @output --- 80/tcp open http syn-ack Apache httpd 2.2.8 ((Ubuntu)) --- | HTTP allowed methods: according to OPTIONS request: GET,HEAD,POST,OPTIONS,TRACE --- | HTTP Status for GET is 200 OK --- | HTTP Status for HEAD is 200 OK --- | HTTP Status for POST is 200 OK --- | HTTP Status for OPTIONS is 200 OK --- |_ HTTP Status for TRACE is 200 OK +-- 80/tcp open http syn-ack +-- | http-methods: GET,HEAD,POST,OPTIONS,TRACE +-- | GET / -> HTTP/1.1 200 OK +-- | HEAD / -> HTTP/1.1 200 OK +-- | POST / -> HTTP/1.1 200 OK +-- | OPTIONS / -> HTTP/1.1 200 OK +-- |_TRACE / -> HTTP/1.1 200 OK -- -- @usage -- nmap --script=http-methods.nse --script-args http-methods.retest=1 @@ -33,7 +33,8 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"safe"} -require "stdnse" +require("http") +require("stdnse") portrule = function(host, port) if not (port.service == 'http' or port.service == 'https') @@ -48,91 +49,49 @@ portrule = function(host, port) return(true) end ---- cleanup function for HTTP response header --- --- in multi line strings any lines after the first one are removed --- if the first line contains HTTP protocol version and response code --- only the response code itself is kept (removing "HTTP/1.? ") --- @param some_string gives the (probably multi line) string to clean up, --- normally a HTTP response header --- @returns some_string as a clean string, single line - -local cleanup = function(some_string) - if (some_string ~= nil) - then - some_string = string.gsub(some_string , "[\n\r].*", "") - some_string = string.gsub(some_string, "HTTP/[0-9]\.[0-9] ", "") - end - return(some_string) -end - action = function(host, port) - local socket, request, result, methods, protocol, output, httpstatus, methodsarray, i, own_httpstatus, url_path, retest_http_methods, try, catch, location + local url_path, retest_http_methods + local response, methods, options_status_line, output -- default vaules for script-args url_path = nmap.registry.args["http-methods.url-path"] or "/" retest_http_methods = nmap.registry.args["http-methods.retest"] ~= nil - catch = function() - socket:close() + response = http.generic_request(host, port, "OPTIONS", url_path) + if not response.status then + stdnse.print_debug("http-methods: OPTIONS %s failed.", url_path) + return end - try = nmap.new_try(catch) + -- Cache in case retest is requested. + options_status_line = response["status-line"] + stdnse.print_debug("http-methods.nse: HTTP Status for OPTIONS is " .. response.status) - socket = nmap.new_socket() - - if (port.service == 'https' or port.version.service_tunnel == 'ssl') - then - protocol = "ssl" - else - protocol = "tcp" + if not response.header["allow"] then + return string.format("No Allow header in OPTIONS response (status code %d)", response.status) end - try(socket:connect(host.ip, port.number, protocol)) - request = "OPTIONS " .. url_path .. " HTTP/1.0\r\n\r\n" - try(socket:send(request)) - result = try(socket:receive_lines(1)) - socket:close() - - own_httpstatus = cleanup(result) - stdnse.print_debug("http-methods.nse: HTTP Status for OPTIONS is " .. own_httpstatus) - methods = cleanup(string.match(result, "Allow: *(.+)[\n\r]")) - - if (methods ~= nil) - then - -- got methods - output = "OPTIONS " .. url_path .. " request returned: " .. methods - else - -- got no methods - output = "OPTIONS " .. url_path .. " request returned no methods but response code " .. own_httpstatus - end + output = { response.header["allow"] } -- retest http methods if requested - if (retest_http_methods and methods ~= nil) - then - methodsarray = stdnse.strsplit(",", methods) - for i=1, #methodsarray, 1 - do - stdnse.print_debug("http-methods.nse: found method " .. i .. " " .. methodsarray[i]) - if (methodsarray[i] == 'OPTIONS') - then - stdnse.print_debug("http-methods.nse: no need to try method OPTIONS, using status of previous request"); - output = output .. "\n HTTP Status for OPTIONS " .. url_path .. " is " .. own_httpstatus + if retest_http_methods then + local methods = stdnse.strsplit(",%s*", response.header["allow"]) + local _ + for _, method in ipairs(methods) do + local str + if method == "OPTIONS" then + -- Use the saved value. + str = options_status_line else - stdnse.print_debug("http-methods.nse: trying method " .. methodsarray[i] .. " on " .. protocol); - - socket = nmap.new_socket() - try(socket:connect(host.ip, port.number, protocol)) - request = methodsarray[i] .. " " .. url_path .. " HTTP/1.0\r\n\r\n" - try(socket:send(request)) - httpstatus = cleanup(try(socket:receive_lines(1))) - socket:close() - - stdnse.print_debug("http-methods.nse: HTTP Status for " .. methodsarray[i] .. " " .. url_path .. " is " .. httpstatus) - output = output .. "\n HTTP Status for " .. methodsarray[i] .. " " .. url_path .. " is " .. httpstatus + response = http.generic_request(host, port, method, url_path) + if not response.status then + str = "Error getting response" + else + str = response["status-line"] + end end + output[#output + 1] = string.format("%s %s -> %s", method, url_path, str) end end - return(output) + return stdnse.strjoin("\n", output) end -