mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
Move starttls and auth functions into ftp.lua
This commit is contained in:
115
nselib/ftp.lua
115
nselib/ftp.lua
@@ -119,6 +119,121 @@ function close(socket)
|
||||
socket:close()
|
||||
end
|
||||
|
||||
--- Issue a STARTTLS command.
|
||||
--
|
||||
-- @param socket The connected command socket
|
||||
-- @param buffer The socket read buffer
|
||||
-- @return Boolean true if AUTH TLS succeeded, false otherwise
|
||||
-- @return error string on failure
|
||||
function starttls(socket, buffer)
|
||||
-- Send AUTH TLS command, ask the service to start encryption
|
||||
local status, err = socket:send("AUTH TLS\r\n")
|
||||
if not status then
|
||||
return nil, err
|
||||
end
|
||||
local code, result = read_reply(buffer)
|
||||
return code == 234, result
|
||||
end
|
||||
|
||||
local function is_ssl(socket)
|
||||
return pcall(socket.get_ssl_certificate, socket)
|
||||
end
|
||||
|
||||
-- Should we try STARTTLS based on this error?
|
||||
local function should_try_ssl(code, message)
|
||||
return code and code >= 400 and (
|
||||
message:match('[Ss][Ss][Ll]') or
|
||||
message:match('[Tt][Ll][Ss]') or
|
||||
message:match('[Ss][Ee][Cc][Uu][Rr]')
|
||||
)
|
||||
end
|
||||
|
||||
-- Try to reconnect over STARTTLS.
|
||||
local function reconnect_ssl(socket, buffer)
|
||||
local status, err = starttls(socket, buffer)
|
||||
if status then
|
||||
status, err = socket:reconnect_ssl()
|
||||
if status then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return nil, err
|
||||
end
|
||||
|
||||
--- Authenticate with username and password
|
||||
--
|
||||
-- May negotiate AUTH TLS if required
|
||||
-- @param socket The connected command socket
|
||||
-- @param buffer The socket read buffer
|
||||
-- @param username The username to send
|
||||
-- @param password The password to send
|
||||
-- @param acct (optional) If the server requires it, send this account name. Default: username
|
||||
-- @return Boolean true if auth succeeded, false otherwise
|
||||
-- @return FTP response code
|
||||
-- @return FTP response message
|
||||
function auth(socket, buffer, username, password, acct)
|
||||
local already_ssl = is_ssl(socket)
|
||||
::TRY_AGAIN::
|
||||
local status, err = socket:send(("USER %s\r\n"):format(username))
|
||||
if not status then
|
||||
return nil, err
|
||||
end
|
||||
local code, message = read_reply(buffer)
|
||||
if code == 331 then
|
||||
-- 331: User name okay, need password.
|
||||
status, err =socket:send(("PASS %s\r\n"):format(password))
|
||||
if not status then
|
||||
return nil, err
|
||||
end
|
||||
code, message = read_reply(buffer)
|
||||
elseif not already_ssl and should_try_ssl(code, message) then
|
||||
if not reconnect_ssl(socket, buffer) then
|
||||
return nil
|
||||
end
|
||||
already_ssl = true
|
||||
goto TRY_AGAIN
|
||||
end
|
||||
|
||||
if code == 332 then
|
||||
-- 332: Need account for login.
|
||||
-- This is rarely seen but may come in response to a
|
||||
-- USER or PASS command.
|
||||
status, err = socket:send("ACCT %s\r\n"):format(acct or username)
|
||||
if not status then
|
||||
return nil, err
|
||||
end
|
||||
code, message = read_reply(buffer)
|
||||
if code == 331 then
|
||||
-- 331: User name okay, need password.
|
||||
status, err = socket:send("PASS %s\r\n"):format(password)
|
||||
if not status then
|
||||
return nil, err
|
||||
end
|
||||
code, message = read_reply(buffer)
|
||||
elseif not already_ssl and should_try_ssl(code, message) then
|
||||
if not reconnect_ssl(socket, buffer) then
|
||||
return nil
|
||||
end
|
||||
already_ssl = true
|
||||
goto TRY_AGAIN
|
||||
end
|
||||
end
|
||||
|
||||
if code and code >= 200 and code < 300 then
|
||||
-- We are primarily looking for 230: User logged in, proceed.
|
||||
return true, code, message
|
||||
else
|
||||
if code and not already_ssl and should_try_ssl(code, message) then
|
||||
if not reconnect_ssl(socket, buffer) then
|
||||
return nil
|
||||
end
|
||||
already_ssl = true
|
||||
goto TRY_AGAIN
|
||||
end
|
||||
return nil, code, message
|
||||
end
|
||||
end
|
||||
|
||||
--- Start PASV mode
|
||||
--
|
||||
-- For IPv6 connections, attempts to use EPSV (RFC 2428). If the server sends an address that is not the target address, then this is an error.
|
||||
|
||||
@@ -204,28 +204,20 @@ StartTLS = {
|
||||
-- Works for FTP (21)
|
||||
|
||||
-- Open a standard TCP socket
|
||||
local s, err = comm.opencon(host, port)
|
||||
local s, code, result, buf = ftp.connect(host, port)
|
||||
if not s then
|
||||
return false, string.format("Failed to connect to FTP server: %s", err)
|
||||
return false, string.format("Failed to connect to FTP server: %s", code)
|
||||
end
|
||||
local buf = stdnse.make_buffer(s, "\r?\n")
|
||||
|
||||
local code, result = ftp.read_reply(buf)
|
||||
if code ~= 220 then
|
||||
return false, string.format("FTP protocol error: %s", code or result)
|
||||
end
|
||||
|
||||
-- Send AUTH TLS command, ask the service to start encryption
|
||||
s:send("AUTH TLS\r\n")
|
||||
code, result = ftp.read_reply(buf)
|
||||
if code ~= 234 then
|
||||
local status, err = ftp.starttls(s, buf)
|
||||
if not status then
|
||||
starttls_supported(host, port, false)
|
||||
stdnse.debug1("AUTH TLS failed or unavailable. Enable --script-trace to see what is happening.")
|
||||
|
||||
-- Send QUIT to clean up server side connection
|
||||
s:send("QUIT\r\n")
|
||||
|
||||
return false, string.format("FTP AUTH TLS error: %s", code or result)
|
||||
ftp.close(s)
|
||||
return false, string.format("FTP AUTH TLS error: %s", err)
|
||||
end
|
||||
-- Should have a solid TLS over FTP session now...
|
||||
starttls_supported(host, port, true)
|
||||
|
||||
@@ -76,30 +76,6 @@ local function list(socket, buffer, target, max_lines)
|
||||
return true, listing
|
||||
end
|
||||
|
||||
local function is_ssl(socket)
|
||||
return pcall(socket.get_ssl_certificate, socket)
|
||||
end
|
||||
|
||||
-- Try to reconnect over STARTTLS.
|
||||
-- Returns a new connected socket and buffer
|
||||
local function reconnect_ssl(socket, host, port)
|
||||
socket:close()
|
||||
local status, socket = sslcert.isPortSupported({service="ftp"})(host, port)
|
||||
if status then
|
||||
socket:set_timeout(8000)
|
||||
return socket, stdnse.make_buffer(socket, "\r?\n")
|
||||
end
|
||||
end
|
||||
|
||||
-- Should we try STARTTLS based on this error?
|
||||
local function should_try_ssl(code, message)
|
||||
return code and code >= 400 and (
|
||||
message:match('[Ss][Ss][Ll]') or
|
||||
message:match('[Tt][Ll][Ss]') or
|
||||
message:match('[Ss][Ee][Cc][Uu][Rr]')
|
||||
)
|
||||
end
|
||||
|
||||
--- Connects to the FTP server and checks if the server allows anonymous logins.
|
||||
action = function(host, port)
|
||||
local max_list = stdnse.get_script_args("ftp-anon.maxlist")
|
||||
@@ -119,67 +95,18 @@ action = function(host, port)
|
||||
|
||||
local socket, code, message, buffer = ftp.connect(host, port, {request_timeout=8000})
|
||||
if not socket then
|
||||
stdnse.debug1("Couldn't connect: %s", code)
|
||||
stdnse.debug1("Couldn't connect: %s", code or message)
|
||||
return nil
|
||||
end
|
||||
if code and code ~= 220 then
|
||||
stdnse.debug1("banner code %d %q.", code, message)
|
||||
return nil
|
||||
end
|
||||
|
||||
local try = nmap.new_try( function()
|
||||
socket:close()
|
||||
end)
|
||||
|
||||
-- Read banner.
|
||||
::TRY_AGAIN::
|
||||
local already_ssl = is_ssl(socket)
|
||||
if code and code == 220 then
|
||||
try(socket:send("USER anonymous\r\n"))
|
||||
code, message = ftp.read_reply(buffer)
|
||||
if code == 331 then
|
||||
-- 331: User name okay, need password.
|
||||
try(socket:send("PASS IEUser@\r\n"))
|
||||
code, message = ftp.read_reply(buffer)
|
||||
elseif not already_ssl and should_try_ssl(code, message) then
|
||||
socket, buffer = reconnect_ssl(socket, host, port)
|
||||
if not socket then
|
||||
return nil
|
||||
end
|
||||
code = 220
|
||||
goto TRY_AGAIN
|
||||
end
|
||||
|
||||
if code == 332 then
|
||||
-- 332: Need account for login.
|
||||
-- This is rarely seen but may come in response to a
|
||||
-- USER or PASS command. As we're doing this
|
||||
-- anonymously, send back a blank ACCT.
|
||||
try(socket:send("ACCT\r\n"))
|
||||
code, message = ftp.read_reply(buffer)
|
||||
if code == 331 then
|
||||
-- 331: User name okay, need password.
|
||||
try(socket:send("PASS IEUser@\r\n"))
|
||||
code, message = ftp.read_reply(buffer)
|
||||
elseif not already_ssl and should_try_ssl(code, message) then
|
||||
socket, buffer = reconnect_ssl(socket, host, port)
|
||||
if not socket then
|
||||
return nil
|
||||
end
|
||||
code = 220
|
||||
goto TRY_AGAIN
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if code and code >= 200 and code < 300 then
|
||||
-- We are primarily looking for 230: User logged in, proceed.
|
||||
else
|
||||
local status, code, message = ftp.auth(socket, buffer, "anonymous", "IEUser@")
|
||||
if not status then
|
||||
if not code then
|
||||
stdnse.debug1("got socket error %q.", message)
|
||||
elseif not already_ssl and should_try_ssl(code, message) then
|
||||
socket, buffer = reconnect_ssl(socket, host, port)
|
||||
if not socket then
|
||||
return nil
|
||||
end
|
||||
code = 220
|
||||
goto TRY_AGAIN
|
||||
elseif code == 421 or code == 530 then
|
||||
-- Don't log known error codes.
|
||||
-- 421: Service not available, closing control connection.
|
||||
@@ -196,7 +123,7 @@ action = function(host, port)
|
||||
|
||||
if not max_list or max_list > 0 then
|
||||
local status, listing = list(socket, buffer, host, max_list)
|
||||
socket:close()
|
||||
ftp.close(socket)
|
||||
|
||||
if not status then
|
||||
result[#result + 1] = "Can't get directory listing: " .. listing
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
local brute = require "brute"
|
||||
local creds = require "creds"
|
||||
local nmap = require "nmap"
|
||||
local shortport = require "shortport"
|
||||
local stdnse = require "stdnse"
|
||||
local string = require "string"
|
||||
local ftp = require "ftp"
|
||||
|
||||
description = [[
|
||||
Performs brute force password auditing against FTP servers.
|
||||
@@ -56,62 +55,42 @@ Driver = {
|
||||
|
||||
connect = function( self )
|
||||
self.socket = brute.new_socket()
|
||||
local status, err = self.socket:connect(self.host, self.port)
|
||||
self.socket:set_timeout(arg_timeout)
|
||||
if(not(status)) then
|
||||
return false, brute.Error:new( "Couldn't connect to host: " .. err )
|
||||
-- discard buffer, we'll create a new one over the BruteSocket later
|
||||
local realsocket, code, message, buffer = ftp.connect(self.host, self.port, {request_timeout=arg_timeout})
|
||||
if not realsocket then
|
||||
return false, brute.Error:new( "Couldn't connect to host: " .. (code or message) )
|
||||
end
|
||||
self.socket.socket = realsocket
|
||||
return true
|
||||
end,
|
||||
|
||||
login = function (self, user, pass)
|
||||
local status, err
|
||||
local res = ""
|
||||
|
||||
status, err = self.socket:send("USER " .. user .. "\r\n")
|
||||
if(not(status)) then
|
||||
return false, brute.Error:new("Couldn't send login: " .. err)
|
||||
end
|
||||
|
||||
status, err = self.socket:send("PASS " .. pass .. "\r\n")
|
||||
if(not(status)) then
|
||||
return false, brute.Error:new("Couldn't send login: " .. err)
|
||||
end
|
||||
|
||||
-- Create a buffer and receive the first line
|
||||
local buffer = stdnse.make_buffer(self.socket, "\r?\n")
|
||||
local line = buffer()
|
||||
local status, code, message = ftp.auth(self.socket, buffer, user, pass)
|
||||
|
||||
-- Loop over the lines
|
||||
while(line)do
|
||||
stdnse.debug1("Received: %s", line)
|
||||
if(string.match(line, "^230")) then
|
||||
stdnse.debug1("Successful login: %s/%s", user, pass)
|
||||
return true, creds.Account:new( user, pass, creds.State.VALID)
|
||||
elseif(string.match(line, "^530")) then
|
||||
if not status then
|
||||
if not code then
|
||||
return false, brute.Error:new("socket error during login: " .. message)
|
||||
elseif code == 530 then
|
||||
return false, brute.Error:new( "Incorrect password" )
|
||||
elseif(string.match(line, "^421")) then
|
||||
elseif code == 421 then
|
||||
local err = brute.Error:new("Too many connections")
|
||||
err:setReduce(true)
|
||||
return false, err
|
||||
elseif(string.match(line, "^220")) then
|
||||
elseif(string.match(line, "^331")) then
|
||||
else
|
||||
stdnse.debug1("WARNING: Unhandled response: %s", line)
|
||||
stdnse.debug1("WARNING: Unhandled response: %d %s", code, message)
|
||||
local err = brute.Error:new("Unhandled response")
|
||||
err:setRetry(true)
|
||||
return false, err
|
||||
end
|
||||
|
||||
line = buffer()
|
||||
end
|
||||
|
||||
|
||||
return false, brute.Error:new("Login didn't return a proper response")
|
||||
stdnse.debug1("Successful login: %s/%s", user, pass)
|
||||
return true, creds.Account:new( user, pass, creds.State.VALID)
|
||||
end,
|
||||
|
||||
disconnect = function( self )
|
||||
self.socket:close()
|
||||
ftp.close(self.socket)
|
||||
return true
|
||||
end
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user