diff --git a/CHANGELOG b/CHANGELOG
index bd63d28dd..797fbc54a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,7 +1,7 @@
# Nmap Changelog ($Id$); -*-text-*-
-o [NSE] ftp-syst sends a SYST command to FTP servers to get system version
- information. [Daniel Miller]
+o [NSE] ftp-syst sends SYST and STAT commands to FTP servers to get system
+ version and connection information. [Daniel Miller]
o [NSE] FTP scripts like ftp-anon and ftp-brute now correctly handle
TLS-protected FTP services and use STARTTLS when necessary. [Daniel Miller]
diff --git a/scripts/ftp-syst.nse b/scripts/ftp-syst.nse
index 11db45752..bf667494c 100644
--- a/scripts/ftp-syst.nse
+++ b/scripts/ftp-syst.nse
@@ -5,9 +5,11 @@ local string = require "string"
local table = require "table"
description = [[
-Sends an FTP SYST command and returns the result.
+Sends FTP SYST and STAT commands and returns the result.
-The canonical response of "UNIX Type: L8" is stripped or ignored, since it is meaningless.
+The canonical SYST response of "UNIX Type: L8" is stripped or ignored, since it
+is meaningless. Typical FTP response codes (215 for SYST and 211 for STAT) are
+also hidden.response codes (215 for SYST and 211 for STAT) are also hidden.
References:
* https://cr.yp.to/ftp/syst.html
@@ -19,10 +21,55 @@ categories = {"default", "discovery", "safe"}
---
-- @output
--- |_ftp-syst: UNIX MikroTik 6.39.2
+-- | ftp-syst:
+-- | SYST: UNIX MikroTik 6.34.3
+-- | STAT:
+-- | Enver Curri FTP server (MikroTik 6.34.3) status:
+-- | Logged in as
+-- | TYPE: ASCII; STRUcture: File; transfer MODE: Stream
+-- | No data connection
+-- |_End of status
+--
+-- | ftp-syst:
+-- | STAT:
+-- | FTP server status:
+-- | Connected to 192.0.2.13
+-- | Logged in as ftp
+-- | TYPE: ASCII
+-- | No session bandwidth limit
+-- | Session timeout in seconds is 300
+-- | Control connection is plain text
+-- | Data connections will be plain text
+-- | At session startup, client count was 1
+-- | vsFTPd 2.0.5 - secure, fast, stable
+-- |_End of status
+--
+-- | ftp-syst:
+-- | SYST: Version: Linux 2.6.26.8-rt16
+-- | STAT:
+-- | HES_CPE FTP server status:
+-- | ftpd (GNU inetutils) 1.4.1
+-- | Connected to 72.14.177.105
+-- | Waiting for user name
+-- | TYPE: ASCII, FORM: Nonprint; STRUcture: File; transfer MODE: Stream
+-- | No data connection
+-- |_End of status
+--
+-- @xmloutput
+-- Version: Linux 3.10.73
+--
+-- FRITZ!Box7490 FTP server status:
+-- Connected to 72.14.177.105
+-- Waiting for user name
+-- TYPE: ASCII, FORM: Nonprint; STRUcture: File; transfer MODE: Stream
+-- No data connection
+-- End of status
portrule = shortport.port_or_service({21,990}, {"ftp","ftps"})
+local function do_syst(socket, buffer)
+end
+
action = function(host, port)
local socket, code, message, buffer = ftp.connect(host, port)
if not socket then
@@ -34,38 +81,64 @@ action = function(host, port)
return nil
end
+ -- SYST
local auth_done = false
- ::TRY_AGAIN::
- if not socket:send("SYST\r\n") then
- return nil
- end
- code, message = ftp.read_reply(buffer)
- if not code then
- stdnse.debug1("SYST error: %s", message)
- return nil
- end
- if code == 215 then
- local stripped = message:gsub("^UNIX Type: L8 *", "")
- if stripped ~= "" then
- return stripped
+ local syst
+ repeat
+ if not socket:send("SYST\r\n") then
+ return nil
+ end
+ code, message = ftp.read_reply(buffer)
+ if not code then
+ stdnse.debug1("SYST error: %s", message)
+ break
+ end
+ if code == 215 then
+ local stripped = message:gsub("^UNIX Type: L8 *", "")
+ if stripped ~= "" then
+ syst = stripped
+ end
+ break
+ elseif code < 300 then
+ syst = ("%d %s"):format(code, message)
+ break
+ elseif not auth_done and -- we haven't tried logging in yet
+ ( code == 503 -- Command SYST not accepted during Connected
+ or code == 521 -- Not logged in - Secure authentication required
+ or (code % 100) // 10 == 3 -- x3x codes are auth-related
+ ) then
+ -- Try logging in
+ local status, code, message = ftp.auth(socket, buffer, "anonymous", "IEUser@")
+ if status then
+ auth_done = true
+ end
+ end
+ until not auth_done
+
+ -- STAT
+ if not socket:send("STAT\r\n") then
+ if syst then
+ return {SYST=syst}
else
return nil
end
- elseif code < 300 then
- return ("%d %s"):format(code, message)
- elseif not auth_done and -- we haven't tried logging in yet
- ( code == 503 -- Command SYST not accepted during Connected
- or code == 521 -- Not logged in - Secure authentication required
- or (code % 100) // 10 == 3 -- x3x codes are auth-related
- ) then
- -- Try logging in
- local status, code, message = ftp.auth(socket, buffer, "anonymous", "IEUser@")
- if status then
- auth_done = true
- goto TRY_AGAIN
+ end
+
+ local out = stdnse.output_table()
+ out.SYST = syst
+ local code, stat = ftp.read_reply(buffer)
+
+ if code then
+ if code == 211 then
+ out.STAT = "\n" .. stat
+ elseif code < 300 then
+ out.STAT = ("%d\n%s"):format(code, stat)
end
end
ftp.close(socket)
+ if #out > 0 then
+ return out
+ end
end