1
0
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:
dmiller
2017-07-26 19:34:23 +00:00
parent cbf04048d0
commit 743791ce63
4 changed files with 145 additions and 132 deletions

View File

@@ -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.

View File

@@ -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)

View File

@@ -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

View File

@@ -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
}