diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:42:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:42:04 +0000 |
commit | 0d47952611198ef6b1163f366dc03922d20b1475 (patch) | |
tree | 3d840a3b8c0daef0754707bfb9f5e873b6b1ac13 /nselib/proxy.lua | |
parent | Initial commit. (diff) | |
download | nmap-0d47952611198ef6b1163f366dc03922d20b1475.tar.xz nmap-0d47952611198ef6b1163f366dc03922d20b1475.zip |
Adding upstream version 7.94+git20230807.3be01efb1+dfsg.upstream/7.94+git20230807.3be01efb1+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'nselib/proxy.lua')
-rw-r--r-- | nselib/proxy.lua | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/nselib/proxy.lua b/nselib/proxy.lua new file mode 100644 index 0000000..87a15ee --- /dev/null +++ b/nselib/proxy.lua @@ -0,0 +1,314 @@ +--- +-- Functions for proxy testing. +-- +-- @args proxy.url Url that will be requested to the proxy +-- @args proxy.pattern Pattern that will be searched inside the request results +-- +-- @author Joao Correa <joao@livewire.com.br> +-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html + +local dns = require "dns" +local ipOps = require "ipOps" +local nmap = require "nmap" +local stdnse = require "stdnse" +local string = require "string" +local stringaux = require "stringaux" +_ENV = stdnse.module("proxy", stdnse.seeall) + +-- Start of local functions + +--- check function, checks for all valid returned status +--- If any of the HTTP status below is found, the proxy is potentially open +--- The script tries to split header from body before checking for status +--@param result connection result +--@return true if any of the status is found, otherwise false +local function check_code(result) + if result then + local header + if result:match( "\r?\n\r?\n" ) then + result = result:match( "^(.-)\r?\n\r?\n(.*)$" ) + end + if result:lower():match("^http/%d%.%d%s*200") then return true end + if result:lower():match("^http/%d%.%d%s*30[12]") then return true end + end + return false +end + +--- check pattern, searches a pattern inside a response with multiple lines +--@param result Connection result +--@param pattern The pattern to be searched +--@return true if pattern is found, otherwise false +local function check_pattern(result, pattern) + local lines = stringaux.strsplit("\n", result) + for i, line in ipairs(lines) do + if line:lower():match(pattern:lower()) then return true end + end + return false +end + +--- check, decides what kind of check should be done on the response, +--- depending if a specific pattern is being used +--@param result Connection result +--@param pattern The pattern that should be checked (must be false, in case of +--code check) +--@return true, if the performed check returns true, otherwise false +local function check(result, pattern) + local s_pattern = false + local s_code = check_code(result) + if s_code and pattern then + s_pattern = check_pattern(result, pattern) + end + return s_code, s_pattern +end + +--- Performs a request to the web server and calls check to check if +-- the response is a valid result +-- +--@param socket The socket to send the request through +--@param req The request to be sent +--@param pattern The pattern to check for valid result +--@return check_status True or false. If pattern was used, depends on pattern check result. If not, depends on code check result. +--@return result The result of the request +--@return code_status True or false. If pattern was used, returns the result of code checking for the same result. If pattern was not used, is nil. +local function test(socket, req, pattern) + local status, result = socket:send(req) + if not status then + socket:close() + return false, result + end + status, result = socket:receive() + if not status then + socket:close() + return false, result + end + socket:close() + local s_code, s_pattern = check(result, pattern) + if result and pattern then return s_pattern, result, s_code end + if result then return s_code, result, nil end + return false, nil, nil +end + +--- Builds the GET request and calls test +-- @param host The host table +-- @param port The port table +-- @param proxyType The proxy type to be tested, might be 'socks4', 'socks5' or 'http' +-- @param test_url The url to send the request +-- @param hostname The hostname of the server to send the request +-- @param pattern The pattern to check for valid result +-- @return the result of the function test (status and the request result) +function test_get(host, port, proxyType, test_url, hostname, pattern) + local status, socket = connectProxy(host, port, proxyType, hostname) + if not status then + return false, socket + end + local req = "GET " .. test_url .. " HTTP/1.0\r\nHost: " .. hostname .. "\r\n\r\n" + stdnse.debug1("GET Request: %s", req) + return test(socket, req, pattern) +end + +--- Builds the HEAD request and calls test +-- @param host The host table +-- @param port The port table +-- @param proxyType The proxy type to be tested, might be 'socks4', 'socks5' or 'http' +-- @param test_url The url te send the request +-- @param hostname The hostname of the server to send the request +-- @param pattern The pattern to check for valid result +-- @return the result of the function test (status and the request result) +function test_head(host, port, proxyType, test_url, hostname, pattern) + local status, socket = connectProxy(host, port, proxyType, hostname) + if not status then + return false, socket + end + local req = "HEAD " .. test_url .. " HTTP/1.0\r\nHost: " .. hostname .. "\r\n\r\n" + stdnse.debug1("HEAD Request: %s", req) + return test(socket, req, pattern) +end + +--- Builds the CONNECT request and calls test +-- @param host The host table +-- @param port The port table +-- @param proxyType The proxy type to be tested, might be 'socks4', 'socks5' or 'http' +-- @param hostname The hostname of the server to send the request +-- @return the result of the function test (status and the request result) +function test_connect(host, port, proxyType, hostname) + local status, socket = connectProxy(host, port, proxyType, hostname) + if not status then + return false, socket + end + local req = "CONNECT " .. hostname .. ":80 HTTP/1.0\r\n\r\n" + stdnse.debug1("CONNECT Request: %s", req) + return test(socket, req, false) +end + +--- Checks if any parameter was used in old or new syntax +-- and return the parameters +-- @return url the proxy.url parameter +-- @return pattern the proxy.pattern parameter +function return_args() + local url = false + local pattern = false + if nmap.registry.args['proxy.url'] + then url = nmap.registry.args['proxy.url'] + elseif nmap.registry.args.proxy and nmap.registry.args.proxy.url + then url = nmap.registry.args.proxy.url + end + if nmap.registry.args['proxy.pattern'] + then pattern = nmap.registry.args['proxy.pattern'] + elseif nmap.registry.args.proxy and nmap.registry.args.proxy.url + then pattern = nmap.registry.args.proxy.pattern + end + return url, pattern +end + +--- Creates a socket, performs proxy handshake if necessary +--- and returns it +-- @param host The host table +-- @param port The port table +-- @param proxyType A string with the proxy type. Might be "http","socks4" or "socks5" +-- @param hostname The proxy destination hostname +-- @return status True if handshake succeeded, false otherwise +-- @return socket A socket with the handshake already done, or an error if +function connectProxy(host, port, proxyType, hostname) + local socket = nmap.new_socket() + socket:set_timeout(10000) + local status, err = socket:connect(host, port) + if not status then + socket:close() + return false, err + end + if proxyType == "http" then return true, socket end + if proxyType == "socks4" then return socksHandshake(socket, 4, hostname) end + if proxyType == "socks5" then return socksHandshake(socket, 5, hostname) end + socket:close() + return false, "Invalid proxyType" +end + +--- Performs a socks handshake on a socket and returns it +-- @param socket The socket where the handshake will be performed +-- @param version The socks version (might be 4 or 5) +-- @param hostname The proxy destination hostname +-- @return status True if handshake succeeded, false otherwise +-- @return socket A socket with the handshake already done, or an error if +-- status is false +function socksHandshake(socket, version, hostname) + local status, ip = dns.query(hostname) + if not status then + return false, "Unable to resolve hostname" + end + if version == 4 then + local payload = '\x04\x01\x00\x50' .. ipOps.ip_to_str(ip) .. '\x6e\x6d\x61\x70\x00' + local status, response = socket:send(payload) + if not status then + socket:close() + return false, response + end + status, response = socket:receive() + if not status then + socket:close() + return false, response + end + if #response < 2 then + socket:close() + return false, "Invalid or unknown SOCKS response" + end + local request_status = string.byte(response, 2) + local err = string.format("Unknown response (0x%02x)", request_status) + if(request_status == 0x5a) then + stdnse.debug1('Socks4: Received "Request Granted" from proxy server') + return true, socket + end + if(request_status == 0x5b) then + err = "Request rejected or failed" + elseif (request_status == 0x5c) then + err = "request failed because client is not running identd" + elseif (request_status == 0x5d) then + err = "request failed because client program and identd report different user-ids" + end + stdnse.debug1('Socks4: Received "%s" from proxy server', err) + return false, err + end + if version == 5 then + local payload = '\x05\x01\x00' + local status, err = socket:send(payload) + if not status then + socket:close() + return false, err + end + local auth + status, auth = socket:receive() + local r2 = string.byte(auth,2) + + -- If Auth is required, proxy is closed, skip next test + if(r2 ~= 0x00) then + err = "Authentication Required" + else + -- If no Auth is required, try to establish connection + stdnse.debug1("Socks5: No authentication required") + -- Socks5 second payload: Version, Command, Null, Address type, Ip-Address, Port number + payload = '\x05\x01\x00\x01' .. ipOps.ip_to_str(ip) .. '\x00\x50' + status, err = socket:send(payload) + if not status then + socket:close() + return false, err + end + local z + status, z = socket:receive() + if not status then + socket:close() + return false, z + end + local request_status = string.byte(z, 2) + err = string.format("Unknown response (0x%02x)", request_status) + if (request_status == 0x00) then + stdnse.debug1('Socks5: Received "Request Granted" from proxy server') + return true, socket + elseif(request_status == 0x01) then + err = "General Failure" + elseif (request_status == 0x02) then + err = "Connection not allowed by ruleset" + elseif (request_status == 0x03) then + err = "Network unreachable" + elseif (request_status == 0x04) then + err = "Host unreachable" + elseif (request_status == 0x05) then + err = "Connection refused by destination host" + elseif (request_status == 0x06) then + err = "TTL Expired" + elseif (request_status == 0x07) then + err = "command not supported / protocol error" + elseif (request_status == 0x08) then + err = "Address type not supported" + end + end + stdnse.debug1('Socks5: Received "%s" from proxy server', err) + return false, err + end + return false, "Invalid SOCKS version" +end + +--- Checks if two different responses are equal, +-- if true, the proxy server might be redirecting the requests +-- to a default page +-- +-- Functions splits body from head before comparing, to avoid session +-- variables, cookies... +-- +-- @param resp1 A string with the response for the first request +-- @param resp2 A string with the response for the second request +-- @return bool true if both responses are equal, otherwise false +function redirectCheck(resp1, resp2) + local body1, body2, _ + if resp1:match( "\r?\n\r?\n" ) then + local body1 + _, body1 = resp1:match( "^(.-)\r?\n\r?\n(.*)$" ) + if resp2:match( "\r?\n\r?\n" ) then + _, body2 = resp2:match( "^(.-)\r?\n\r?\n(.*)$" ) + if body1 == body2 then + return true + end + end + end + return false +end + +return _ENV; |