summaryrefslogtreecommitdiffstats
path: root/scripts/ftp-anon.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/ftp-anon.nse')
-rw-r--r--scripts/ftp-anon.nse144
1 files changed, 144 insertions, 0 deletions
diff --git a/scripts/ftp-anon.nse b/scripts/ftp-anon.nse
new file mode 100644
index 0000000..50274b5
--- /dev/null
+++ b/scripts/ftp-anon.nse
@@ -0,0 +1,144 @@
+local ftp = require "ftp"
+local match = require "match"
+local nmap = require "nmap"
+local shortport = require "shortport"
+local stdnse = require "stdnse"
+local string = require "string"
+local table = require "table"
+
+description = [[
+Checks if an FTP server allows anonymous logins.
+
+If anonymous is allowed, gets a directory listing of the root directory
+and highlights writeable files.
+]]
+
+---
+-- @see ftp-brute.nse
+--
+-- @args ftp-anon.maxlist The maximum number of files to return in the
+-- directory listing. By default it is 20, or unlimited if verbosity is
+-- enabled. Use a negative number to disable the limit, or
+-- <code>0</code> to disable the listing entirely.
+--
+-- @output
+-- PORT STATE SERVICE
+-- 21/tcp open ftp
+-- | ftp-anon: Anonymous FTP login allowed (FTP code 230)
+-- | -rw-r--r-- 1 1170 924 31 Mar 28 2001 .banner
+-- | d--x--x--x 2 root root 1024 Jan 14 2002 bin
+-- | d--x--x--x 2 root root 1024 Aug 10 1999 etc
+-- | drwxr-srwt 2 1170 924 2048 Jul 19 18:48 incoming [NSE: writeable]
+-- | d--x--x--x 2 root root 1024 Jan 14 2002 lib
+-- | drwxr-sr-x 2 1170 924 1024 Aug 5 2004 pub
+-- |_Only 6 shown. Use --script-args ftp-anon.maxlist=-1 to see all.
+
+author = {"Eddie Bell", "Rob Nicholls", "Ange Gutek", "David Fifield"}
+license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
+categories = {"default", "auth", "safe"}
+
+
+portrule = shortport.port_or_service({21,990}, {"ftp","ftps"})
+
+-- ---------------------
+-- Directory listing function.
+-- We ask for a PASV connexion, catch the port returned by the server, send a
+-- LIST on the commands socket, connect to the data one and read the directory
+-- list sent.
+-- ---------------------
+local function list(socket, buffer, target, max_lines)
+
+ local list_socket, err = ftp.pasv(socket, buffer)
+ if not list_socket then
+ return nil, err
+ end
+
+ -- Send the LIST command on the commands socket. "Fire and forget"; we
+ -- don't need to take care of the answer on this socket.
+ local status, err = socket:send("LIST\r\n")
+ if not status then
+ return status, err
+ end
+
+ local listing = {}
+ while not max_lines or #listing < max_lines do
+ local status, data = list_socket:receive_buf(match.pattern_limit("\r?\n", 2048), false)
+ if (not status and data == "EOF") or data == "" then
+ break
+ end
+ if not status then
+ return status, data
+ end
+ listing[#listing + 1] = data
+ end
+
+ return true, listing
+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")
+ if not max_list then
+ if nmap.verbosity() == 0 then
+ max_list = 20
+ else
+ max_list = nil
+ end
+ else
+ max_list = tonumber(max_list)
+ if max_list < 0 then
+ max_list = nil
+ end
+ end
+
+
+ local socket, code, message, buffer = ftp.connect(host, port, {request_timeout=8000})
+ if not socket then
+ 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 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 code == 421 or code == 530 then
+ -- Don't log known error codes.
+ -- 421: Service not available, closing control connection.
+ -- 530: Not logged in.
+ else
+ stdnse.debug1("got code %d %q.", code, message)
+ return ("got code %d %q."):format(code, message)
+ end
+ return nil
+ end
+
+ local result = {}
+ result[#result + 1] = "Anonymous FTP login allowed (FTP code " .. code .. ")"
+
+ if not max_list or max_list > 0 then
+ local status, listing = list(socket, buffer, host, max_list)
+ ftp.close(socket)
+
+ if not status then
+ result[#result + 1] = "Can't get directory listing: " .. listing
+ else
+ for _, item in ipairs(listing) do
+ -- Just a quick passive check on user rights.
+ if string.match(item, "^[d-].......w.") then
+ item = item .. " [NSE: writeable]"
+ end
+ result[#result + 1] = item
+ end
+ if max_list and #listing == max_list then
+ result[#result + 1] = string.format("Only %d shown. Use --script-args %s.maxlist=-1 to see all.", #listing, SCRIPT_NAME)
+ end
+ end
+ end
+
+ return table.concat(result, "\n")
+end