From 973e3a90e8f0acf87a854e4f8aae104274114b7a Mon Sep 17 00:00:00 2001 From: Sweekar-cmd Date: Fri, 13 Feb 2026 20:09:34 +0545 Subject: [PATCH] pop3-brute.nse added STLS and CAPA-based auth selection --- scripts/pop3-brute.nse | 213 +++++++++++++++++++++++++---------------- 1 file changed, 128 insertions(+), 85 deletions(-) diff --git a/scripts/pop3-brute.nse b/scripts/pop3-brute.nse index 9712d47e5..5d1c606ed 100644 --- a/scripts/pop3-brute.nse +++ b/scripts/pop3-brute.nse @@ -1,136 +1,179 @@ local brute = require "brute" local comm = require "comm" local creds = require "creds" -local nmap = require "nmap" local pop3 = require "pop3" local shortport = require "shortport" -local string = require "string" +local stdnse = require "stdnse" description = [[ Tries to log into a POP3 account by guessing usernames and passwords. +Automatically detects supported authentication mechanisms and upgrades +to TLS using STLS when available. ]] ---- --- @args pop3loginmethod The login method to use: "USER" --- (default), "SASL-PLAIN", "SASL-LOGIN", --- "SASL-CRAM-MD5", or "APOP". Defaults to "USER", --- --- @output --- PORT STATE SERVICE --- 110/tcp open pop3 --- | pop3-brute-ported: --- | Accounts: --- | user:pass => Login correct --- | Statistics: --- |_ Performed 8 scans in 1 seconds, average tps: 8 - -author = {"Philip Pickering", "Piotr Olma"} +author = {"Philip Pickering", "Piotr Olma", "Sweekar-cmd"} license = "Same as Nmap--See https://nmap.org/book/man-legal.html" - categories = {"intrusive", "brute"} -Driver = { - new = function(self, host, port, login_function, is_apop) +portrule = shortport.port_or_service({110, 995}, {"pop3", "pop3s"}) + +--- +-- Choose best supported auth method from CAPA +local function choose_auth(capas) + if capas.AUTH then + local mechs = {} + for _, m in ipairs(capas.AUTH) do + mechs[m:upper()] = true + end + + if mechs["CRAM-MD5"] then + return pop3.login_sasl_crammd5, false + elseif mechs["LOGIN"] then + return pop3.login_sasl_login, false + elseif mechs["PLAIN"] then + return pop3.login_sasl_plain, false + end + end + + if capas.APOP then + return pop3.login_apop, true + end + + return pop3.login_user, false +end + +local Driver = { + new = function(self, host, port, login_function, is_apop, use_stls, implicit_tls) local o = {} setmetatable(o, self) self.__index = self - o.port = port o.host = host + o.port = port o.login_function = login_function o.is_apop = is_apop + o.use_stls = use_stls + o.implicit_tls = implicit_tls return o end, - -- Attempts to connect to the POP server - -- @return true on success - -- @return false, brute.Error object on failure connect = function(self) + local opts = { timeout = 10000, recv_before = true } + local line - self.socket = brute.new_socket() - local opts = {timeout=10000, recv_before=true} - local best_opt, line, _ - self.socket, _, best_opt, line = comm.tryssl(self.host, self.port, "" , opts) + -- Implicit TLS (POP3S, usually port 995) + if self.implicit_tls then + self.socket = brute.new_socket() + local ok = self.socket:connect(self.host, self.port, "ssl") + if not ok then + local err = brute.Error:new("SSL connection failed.") + err:setAbort(true) + return false, err + end + line = self.socket:receive_lines(1) + else + self.socket, _, _, line = comm.tryssl(self.host, self.port, "", opts) + end if not self.socket then local err = brute.Error:new("Failed to connect.") err:setAbort(true) return false, err - end --no connection + end + if not pop3.stat(line) then - local err = brute.Error:new("Failed to make a pop-connection.") + local err = brute.Error:new("Invalid POP3 greeting.") err:setAbort(true) return false, err - end -- no pop-connection + end + -- Extract APOP challenge if self.is_apop then - self.additional = string.match(line, "<[%p%w]+>") --apop challenge + self.additional = line:match("<[^>]+>") end - return true - end, --connect - -- Attempts to login to the POP server - -- - -- @param username string containing the login username - -- @param password string containing the login password - -- @return status, true on success, false on failure - -- @return brute.Error object on failure - -- creds.Account object on success - login = function(self, username, password) - local pstatus - local perror - pstatus, perror = self.login_function(self.socket, username, password, self.additional) - if pstatus then - return true, creds.Account:new(username, password, creds.State.VALID) - else - local err - if (perror == pop3.err.pwError) then - err = brute.Error:new("Wrong password.") - elseif (perror == pop3.err.userError) then - err = brute.Error:new("Wrong username.") - err:setInvalidAccount(username) - else - err = brute.Error:new("Login failed.") + -- Upgrade to TLS using STLS (only for port 110) + if self.use_stls then + self.socket:send("STLS\r\n") + local _, resp = self.socket:receive_lines(1) + if not pop3.stat(resp) then + local err = brute.Error:new("STLS negotiation failed.") + err:setAbort(true) + return false, err end - return false, err + + local ssl = self.socket:sslhandshake() + if not ssl then + local err = brute.Error:new("TLS handshake failed.") + err:setAbort(true) + return false, err + end + self.socket = ssl end - end, --login + + return true + end, + + login = function(self, username, password) + local ok, code = + self.login_function(self.socket, username, password, self.additional) + + if ok then + return true, creds.Account:new(username, password, creds.State.VALID) + end + + local err + if code == pop3.err.pwError then + err = brute.Error:new("Wrong password.") + elseif code == pop3.err.userError then + err = brute.Error:new("Wrong username.") + err:setInvalidAccount(username) + elseif code == pop3.err.OpenSSLMissing then + err = brute.Error:new("OpenSSL required for this authentication method.") + err:setAbort(true) + else + err = brute.Error:new("Login failed.") + end + + return false, err + end, disconnect = function(self) - self.socket:close() - end, --disconnect + if self.socket then + self.socket:close() + end + end, check = function(self) return true - end, --check + end, } -portrule = shortport.port_or_service({110, 995}, {"pop3","pop3s"}) - action = function(host, port) - local pMeth = nmap.registry.args.pop3loginmethod - if (not pMeth) then pMeth = nmap.registry.pop3loginmethod end - if (not pMeth) then pMeth = "USER" end - - --determine function we will use to login to server - local is_apop = false - local login_function - if (pMeth == "USER") then - login_function = pop3.login_user - elseif (pMeth == "SASL-PLAIN") then - login_function = pop3.login_sasl_plain - elseif (pMeth == "SASL-LOGIN") then - login_function = pop3.login_sasl_login - elseif (pMeth == "SASL-CRAM-MD5") then - login_function = pop3.login_sasl_crammd5 - elseif (pMeth == "APOP") then - login_function = pop3.login_apop - is_apop = true - else - login_function = pop3.login_user + local capas = pop3.capabilities(host, port) + if not capas then + return "Could not retrieve capabilities." end - local engine = brute.Engine:new(Driver, host, port, login_function, is_apop) + local login_function, is_apop = choose_auth(capas) + + local use_stls = capas.STLS and port.number == 110 + local implicit_tls = (port.number == 995 or port.service == "pop3s") + + if use_stls then + stdnse.print_debug(1, "POP3: Upgrading to TLS using STLS") + end + + local engine = brute.Engine:new( + Driver, + host, + port, + login_function, + is_apop, + use_stls, + implicit_tls + ) + engine.options.script_name = SCRIPT_NAME - local status, accounts = engine:start() + local _, accounts = engine:start() return accounts end