mirror of
https://github.com/nmap/nmap.git
synced 2025-12-08 13:41:29 +00:00
120 lines
3.0 KiB
Lua
120 lines
3.0 KiB
Lua
description = [[
|
|
Checks if an FTP server allows anonymous logins.
|
|
]]
|
|
|
|
---
|
|
-- @output
|
|
--- Default behavior
|
|
-- PORT STATE SERVICE
|
|
-- 21/tcp open ftp
|
|
-- |_ftp-anon: Anonymous FTP login allowed (FTP code 230)
|
|
|
|
author = "Eddie Bell, Rob Nicholls, Ange Gutek, David Fifield"
|
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
|
categories = {"default", "auth", "safe"}
|
|
|
|
require "shortport"
|
|
|
|
portrule = shortport.port_or_service(21, "ftp")
|
|
|
|
-- Read an FTP reply and return the numeric code and the message. See RFC 959,
|
|
-- section 4.2. The buffer argument should have been created with
|
|
-- stdnse.make_buffer(socket, "\r?\n"). On error, returns nil and an error
|
|
-- message.
|
|
local function read_reply(buffer)
|
|
local readline
|
|
local line, err
|
|
local code, message
|
|
local _, p, tmp
|
|
|
|
line, err = buffer()
|
|
if not line then
|
|
return line, err
|
|
end
|
|
|
|
-- Single-line response?
|
|
code, message = string.match(line, "^(%d%d%d) (.*)$")
|
|
if code then
|
|
return tonumber(code), message
|
|
end
|
|
|
|
-- Multi-line response?
|
|
_, p, code, message = string.find(line, "^(%d%d%d)-(.*)$")
|
|
if p then
|
|
while true do
|
|
line, err = buffer()
|
|
if not line then
|
|
return line, err
|
|
end
|
|
tmp = string.match(line, "^%d%d%d (.*)$")
|
|
if tmp then
|
|
message = message .. "\n" .. tmp
|
|
break
|
|
end
|
|
message = message .. "\n" .. line
|
|
end
|
|
|
|
return tonumber(code), message
|
|
end
|
|
|
|
return nil, string.format("Unparseable response: %q", line)
|
|
end
|
|
|
|
|
|
--- Connects to the FTP server and checks if the server allows anonymous logins.
|
|
action = function(host, port)
|
|
local socket = nmap.new_socket()
|
|
local code, message
|
|
local err_catch = function()
|
|
socket:close()
|
|
end
|
|
|
|
local try = nmap.new_try(err_catch)
|
|
|
|
try(socket:connect(host.ip, port.number, port.protocol))
|
|
buffer = stdnse.make_buffer(socket, "\r?\n")
|
|
|
|
-- Read banner.
|
|
code, message = read_reply(buffer)
|
|
if code and code == 220 then
|
|
try(socket:send("USER anonymous\r\n"))
|
|
code, message = read_reply(buffer)
|
|
if code == 331 then
|
|
-- 331: User name okay, need password.
|
|
try(socket:send("PASS IEUser@\r\n"))
|
|
code, message = read_reply(buffer)
|
|
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 = read_reply(buffer)
|
|
if code == 331 then
|
|
-- 331: User name okay, need password.
|
|
try(socket:send("PASS IEUser@\r\n"))
|
|
code, message = read_reply(buffer)
|
|
end
|
|
end
|
|
end
|
|
|
|
socket:close()
|
|
|
|
if code and code >= 200 and code < 300 then
|
|
-- We are primarily looking for 230: User logged in, proceed.
|
|
return "Anonymous FTP login allowed (FTP code " .. code .. ")"
|
|
elseif code == 421 then
|
|
-- 421: Service not available, closing control connection.
|
|
elseif code == 530 then
|
|
-- 530: Not logged in.
|
|
else
|
|
if not code then
|
|
stdnse.print_debug(1, "ftp-anon: got socket error %q.", message)
|
|
else
|
|
stdnse.print_debug(1, "ftp-anon: got code %d %q.", code, message)
|
|
end
|
|
end
|
|
end
|