diff options
Diffstat (limited to 'nselib/socks.lua')
-rw-r--r-- | nselib/socks.lua | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/nselib/socks.lua b/nselib/socks.lua new file mode 100644 index 0000000..fdf588f --- /dev/null +++ b/nselib/socks.lua @@ -0,0 +1,283 @@ +--- +-- A smallish SOCKS version 5 proxy protocol implementation +-- +-- @author Patrik Karlsson <patrik@cqure.net> +-- + +local match = require "match" +local nmap = require "nmap" +local stdnse = require "stdnse" +local string = require "string" +local table = require "table" +_ENV = stdnse.module("socks", stdnse.seeall) + +-- SOCKS Authentication methods +AuthMethod = { + NONE = 0, + GSSAPI = 1, + USERPASS = 2, +} + +Request = { + + -- Class that handles the connection request to the server + Connect = { + + -- Creates a new instance of the class + -- @param auth_method table of requested authentication methods + -- @return o instance on success, nil on failure + new = function(self, auth_method) + local o = { + version = 5, + auth_method = ( "table" ~= type(auth_method) and { auth_method } or auth_method ) + } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Converts the instance to string, so that it can be sent to the + -- server. + -- @return string containing the raw request + __tostring = function(self) + return string.pack("Bs1", self.version, + string.pack(("B"):rep(#self.auth_method), table.unpack(self.auth_method))) + end, + + }, + + -- Class that handles the authentication request to the server + Authenticate = { + + -- Creates a new instance of the class + -- @param auth_method number with the requested authentication method + -- @param creds method specific table of credentials + -- currently only user and pass authentication is supported + -- this method requires two fields to be present + -- <code>username</code> and <code>password</code> + -- @return o instance on success, nil on failure + new = function(self, auth_method, creds) + local o = { + auth_method = auth_method, + creds = creds + } + setmetatable(o, self) + self.__index = self + if ( auth_method == 2 ) then + return o + end + end, + + -- Converts the instance to string, so that it can be sent to the + -- server. + -- @return string containing the raw request + __tostring = function(self) + -- we really don't support anything but 2, but let's pretend that + -- we actually do + if ( 2 == self.auth_method ) then + local version = 1 + local username= self.creds.username or "" + local password= self.creds.password or "" + + username = (username == "") and "\0" or username + password = (password == "") and "\0" or password + + return string.pack("Bs1s1", version, username, password) + end + end, + + } + +} + +Response = { + + -- Class that handles the connection response + Connect = { + + -- Creates a new instance of the class + -- @param data string containing the data as received over the socket + -- @return o instance on success, nil on failure + new = function(self, data) + local o = { data = data } + setmetatable(o, self) + self.__index = self + if ( o:parse() ) then + return o + end + end, + + -- Parses the received data and populates member variables + -- @return true on success, false on failure + parse = function(self) + if ( #self.data ~= 2 ) then + return + end + local pos + self.version, self.method, pos = string.unpack("BB", self.data) + return true + end + + }, + + -- Class that handles the authentication response + Authenticate = { + + Status = { + SUCCESS = 0, + -- could be anything but zero + FAIL = 1, + }, + + -- Creates a new instance of the class + -- @param data string containing the data as received over the socket + -- @return o instance on success, nil on failure + new = function(self, data) + local o = { data = data } + setmetatable(o, self) + self.__index = self + if ( o:parse() ) then + return o + end + end, + + -- Parses the received data and populates member variables + -- @return true on success, false on failure + parse = function(self) + if ( #self.data ~= 2 ) then + return + end + local pos + self.version, self.status, pos = string.unpack("BB", self.data) + return true + end, + + -- checks if the authentication was successful or not + -- @return true on success, false on failure + isSuccess = function(self) + return ( self.status == self.Status.SUCCESS ) + end, + + } + +} + +-- The main script interface +Helper = { + + -- Create a new instance of the class + -- @param host table containing the host table + -- @param port table containing the port table + -- @param options table containing library options, currently: + -- <code>timeout</code> - socket timeout in ms + -- @return o instance of Helper + new = function(self, host, port, options) + options = options or {} + local o = { host = host, port = port, options = options } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Get the authentication method name by number + -- @param method number containing the authentication method + -- @return string containing the method name or Unknown + authNameByNumber = function(self, method) + local methods = { + [0] = "No authentication", + [1] = "GSSAPI", + [2] = "Username and password", + } + return methods[method] or ("Unknown method (%d)"):format(method) + end, + + -- Connects to the SOCKS server + -- @param auth_method table containing the auth. methods to request + -- @return status true on success, false on failure + -- @return response table containing the response or err string on failure + connect = function(self, auth_method, socket) + self.socket = socket or nmap.new_socket() + self.socket:set_timeout(self.options.timeout or 10000) + local status, err = self.socket:connect(self.host, self.port) + if ( not(status) ) then + return status, err + end + + auth_method = auth_method or {AuthMethod.NONE, AuthMethod.GSSAPI, AuthMethod.USERPASS} + status = self.socket:send( tostring(Request.Connect:new(auth_method)) ) + if ( not(status) ) then + self.socket:close() + return false, "Failed to send connection request to server" + end + + local status, data = self.socket:receive_buf(match.numbytes(2), true) + if ( not(status) ) then + self.socket:close() + return false, "Failed to receive connection response from server" + end + + local response = Response.Connect:new(data) + if ( not(response) ) then + return false, "Failed to parse response from server" + end + + if ( response.version ~= 5 ) then + return false, ("Unsupported SOCKS version (%d)"):format(response.version) + end + if ( response.method == 0xFF ) then + return false, "No acceptable authentication methods" + end + + -- store the method so authenticate knows what to use + self.auth_method = response.method + return true, response + end, + + -- Authenticates to the SOCKS server + -- @param creds table containing authentication method specific fields + -- currently only authentication method 2 (username and pass) is + -- implemented. That method requires the following fields: + -- <code>username</code> - containing the username + -- <code>password</code> - containing the password + -- @return status true on success, false on failure + -- @return err string containing the error message + authenticate = function(self, creds) + if ( self.auth_method ~= 2 ) then + return false, "Authentication method not supported" + end + local req = Request.Authenticate:new(self.auth_method, creds) + if ( not(req) ) then + return false, "Failed to create authentication request" + end + + local status = self.socket:send(tostring(req)) + if ( not(status) ) then + return false, "Failed to send authentication request" + end + + if ( 2 == self.auth_method ) then + local status, data = self.socket:receive_buf(match.numbytes(2), true) + local auth = Response.Authenticate:new(data) + + if ( not(auth) ) then + return false, "Failed to parse authentication response" + end + + if ( auth:isSuccess() ) then + return true, "Authentication was successful" + else + return false, "Authentication failed" + end + + end + return false, "Unsupported authentication method" + end, + + -- closes the connection to the server + close = function(self) + return self.socket:close() + end, + +} + +return _ENV; |