mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 20:51:30 +00:00
Totally overhaulted the ftp-brute.nse script. I opened this script to see about certain changes, and discovered that it was only actually checking a single account (xampp/default). So I hooked it into unpwdb, but discovered that, at least against my test server, it was _extremely_ slow. For that reason, I limited it to the first 10 usernames/passwords for now.
This commit is contained in:
@@ -1,61 +1,210 @@
|
|||||||
description = [[
|
description = [[
|
||||||
Tries to get FTP login credentials by guessing usernames and passwords.
|
Tries to get FTP login credentials by guessing usernames and passwords.
|
||||||
|
|
||||||
|
This uses the standard unpwdb username/password list. However, in tests FTP servers are
|
||||||
|
significantly slower than other servers when responding, so the accounts were artificially
|
||||||
|
limited (can be changed with script-args).
|
||||||
|
|
||||||
|
2008-11-06 Vlatko Kosturjak <kost@linux.hr>
|
||||||
|
Modified xampp-default-auth script to generic ftp-brute script
|
||||||
|
|
||||||
|
2009-09-18 Ron Bowes <ron@skullsecurity.net>
|
||||||
|
Made into an actual bruteforce script (previously, it only tried one username/password).
|
||||||
]]
|
]]
|
||||||
|
|
||||||
---
|
---
|
||||||
-- @output
|
-- @output
|
||||||
-- 21/tcp open ftp
|
-- PORT STATE SERVICE REASON
|
||||||
-- |_ ftp-auth: Login success with u/p: nobody/xampp
|
-- 21/tcp open ftp syn-ack
|
||||||
|
-- | ftp-brute:
|
||||||
|
-- | anonymous: IEUser@
|
||||||
|
-- |_ test: password
|
||||||
--
|
--
|
||||||
-- 2008-11-06 Vlatko Kosturjak <kost@linux.hr>
|
-- @args userlimit The number of user accounts to try (default: 10). Set to 0 for no limit.
|
||||||
-- Modified xampp-default-auth script to generic ftp-brute script
|
-- @args passlimit The number of passwords to try (default: 10). Set to 0 for no limit.
|
||||||
|
-- @args limit Set userlimlt + passlimit at the same time. Set to 0 for no limit.
|
||||||
|
|
||||||
author = "Diman Todorov <diman.todorov@gmail.com>"
|
author = "Diman Todorov <diman.todorov@gmail.com>, Vlatko Kosturjak <kost@linux.hr>, Ron Bowes <ron@skullsecurity.net>"
|
||||||
|
|
||||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
|
||||||
categories = {"auth", "intrusive"}
|
categories = {"auth", "intrusive"}
|
||||||
|
|
||||||
require "shortport"
|
require "shortport"
|
||||||
|
require "stdnse"
|
||||||
|
require "unpwdb"
|
||||||
|
|
||||||
portrule = shortport.port_or_service(21, "ftp")
|
portrule = shortport.port_or_service(21, "ftp")
|
||||||
|
|
||||||
login = function(socket, user, pass)
|
local function get_limits()
|
||||||
|
local userlimit = 10
|
||||||
|
local passlimit = 10
|
||||||
|
|
||||||
|
if(nmap.registry.args.userlimit) then
|
||||||
|
userlimit = tonumber(nmap.registry.args.userlimit)
|
||||||
|
end
|
||||||
|
|
||||||
|
if(nmap.registry.args.passlimit) then
|
||||||
|
passlimit = tonumber(nmap.registry.args.passlimit)
|
||||||
|
end
|
||||||
|
|
||||||
|
if(nmap.registry.args.limit) then
|
||||||
|
userlimit = tonumber(nmap.registry.args.limit)
|
||||||
|
passlimit = tonumber(nmap.registry.args.limit)
|
||||||
|
end
|
||||||
|
|
||||||
|
return userlimit, passlimit
|
||||||
|
end
|
||||||
|
|
||||||
|
local function login(host, port, user, pass)
|
||||||
local status, err
|
local status, err
|
||||||
local res = ""
|
local res = ""
|
||||||
status, err = socket:send("USER " .. user .. "\n")
|
|
||||||
status, err = socket:send("PASS " .. pass .. "\n")
|
|
||||||
|
|
||||||
-- consume the banner and stuff
|
-- Create a new socket
|
||||||
while true do
|
local socket = nmap.new_socket()
|
||||||
status, res = socket:receive_lines(1)
|
status, err = socket:connect(host.ip, port.number)
|
||||||
if
|
if(not(status)) then
|
||||||
not string.match(res, "^220")
|
socket:close()
|
||||||
and not string.match(res, "^331 ")
|
return false, "Couldn't connect to host: " .. err
|
||||||
then
|
end
|
||||||
|
|
||||||
|
status, err = socket:send("USER " .. user .. "\r\n")
|
||||||
|
if(not(status)) then
|
||||||
|
socket:close()
|
||||||
|
return false, "Couldn't send login: " .. err
|
||||||
|
end
|
||||||
|
|
||||||
|
status, err = socket:send("PASS " .. pass .. "\n\n")
|
||||||
|
if(not(status)) then
|
||||||
|
socket:close()
|
||||||
|
return false, "Couldn't send login: " .. err
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create a buffer and receive the first line
|
||||||
|
local buffer = stdnse.make_buffer(socket, "\r?\n")
|
||||||
|
local line = buffer()
|
||||||
|
|
||||||
|
-- Loop over the lines
|
||||||
|
while(line)do
|
||||||
|
stdnse.print_debug("Received: %s", line)
|
||||||
|
if(string.match(line, "^230")) then
|
||||||
|
stdnse.print_debug(1, "ftp-brute: Successful login: %s/%s", user, pass)
|
||||||
|
socket:close()
|
||||||
|
return true, true
|
||||||
|
elseif(string.match(line, "^530")) then
|
||||||
|
socket:close()
|
||||||
|
return true, false
|
||||||
|
elseif(string.match(line, "^220")) then
|
||||||
|
elseif(string.match(line, "^331")) then
|
||||||
|
else
|
||||||
|
stdnse.print_debug(1, "ftp-brute: WARNING: Unhandled response: %s", line)
|
||||||
|
end
|
||||||
|
|
||||||
|
line = buffer()
|
||||||
|
end
|
||||||
|
|
||||||
|
socket:close()
|
||||||
|
return false, "Login didn't return a proper response"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function go(host, port)
|
||||||
|
local status, err
|
||||||
|
local result
|
||||||
|
local userlimit, passlimit = get_limits()
|
||||||
|
local authcombinations = {
|
||||||
|
{user="anonymous", password="IEUser@"}, -- Anonymous user
|
||||||
|
{user="nobody", password="xampp"} -- XAMPP default ftp
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Load accounts from unpwdb
|
||||||
|
local usernames, username, passwords, password
|
||||||
|
|
||||||
|
-- Load the usernames
|
||||||
|
status, usernames = unpwdb.usernames()
|
||||||
|
if(not(status)) then
|
||||||
|
return false, "Couldn't load username list: " .. usernames
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Load the passwords
|
||||||
|
status, passwords = unpwdb.passwords()
|
||||||
|
if(not(status)) then
|
||||||
|
return false, "Couldn't load password list: " .. usernames
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Figure out how many
|
||||||
|
local i = 0
|
||||||
|
local j = 0
|
||||||
|
|
||||||
|
-- Add the passwords to the authcombinations table
|
||||||
|
password = passwords()
|
||||||
|
while (password) do
|
||||||
|
-- Limit the passwords
|
||||||
|
i = i + 1
|
||||||
|
if(passlimit > 0 and i > passlimit) then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
|
j = 0
|
||||||
|
username = usernames()
|
||||||
|
while(username) do
|
||||||
|
-- Limit the usernames
|
||||||
|
j = j + 1
|
||||||
|
if(userlimit > 0 and j > userlimit) then
|
||||||
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
-- are we logged in?
|
table.insert(authcombinations, {user=username, password=password})
|
||||||
if string.match(res, "^230") then
|
username = usernames()
|
||||||
return "Login success with u/p: " .. user .. "/" .. pass
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
usernames('reset')
|
||||||
|
password = passwords()
|
||||||
|
end
|
||||||
|
|
||||||
|
stdnse.print_debug(1, "ftp-brute: Loaded %d username/password pairs", #authcombinations)
|
||||||
|
|
||||||
|
local results = {}
|
||||||
|
for _, combination in ipairs(authcombinations) do
|
||||||
|
|
||||||
|
|
||||||
|
-- Attempt a login
|
||||||
|
status, result = login(host, port, combination.user, combination.password)
|
||||||
|
|
||||||
|
-- Check for an error
|
||||||
|
if(not(status)) then
|
||||||
|
return false, result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check for a success
|
||||||
|
if(status and result) then
|
||||||
|
table.insert(results, combination)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return true, results
|
||||||
end
|
end
|
||||||
|
|
||||||
action = function(host, port)
|
action = function(host, port)
|
||||||
local res
|
local status, results = go(host, port)
|
||||||
local socket = nmap.new_socket()
|
|
||||||
local authcombinations = {
|
|
||||||
{user="nobody", password="xampp"}, --- XAMPP default ftp
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, combination in pairs (authcombinations) do
|
if(not(status)) then
|
||||||
socket:connect(host.ip, port.number)
|
if(nmap.debugging() > 0) then
|
||||||
res = login(socket, combination.user, combination.password)
|
return "ERROR: " .. results
|
||||||
socket:close()
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return res
|
if(#results == 0) then
|
||||||
|
return "No accounts found"
|
||||||
|
end
|
||||||
|
|
||||||
|
local response = " \n"
|
||||||
|
for i, v in ipairs(results) do
|
||||||
|
response = response .. string.format("%s: %s\n", v.user, v.password)
|
||||||
|
end
|
||||||
|
|
||||||
|
return response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user