mirror of
https://github.com/nmap/nmap.git
synced 2025-12-07 21:21:31 +00:00
socket:connect(host.ip, port.number) socket:connect(host.ip, port.number, port.protocol) to this: socket:connect(host, port) connect can take host and port tables now, and the default protocol is taken from the port table if possible.
214 lines
4.8 KiB
Lua
214 lines
4.8 KiB
Lua
description = [[
|
|
Tries to get Telnet login credentials by guessing usernames and passwords.
|
|
]]
|
|
|
|
author = "Eddie Bell, Ron Bowes"
|
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
|
categories = {'auth', 'intrusive'}
|
|
|
|
---
|
|
-- @output
|
|
-- PORT STATE SERVICE
|
|
-- 23/tcp open telnet
|
|
-- |_telnet-brute: root - 1234
|
|
|
|
-- Update (Ron Bowes, November, 2009): Now uses unpwdb database.
|
|
|
|
require('shortport')
|
|
require('stdnse')
|
|
require('strbuf')
|
|
require('comm')
|
|
require('unpwdb')
|
|
|
|
local soc
|
|
local catch = function() soc:close() end
|
|
local try = nmap.new_try(catch)
|
|
|
|
portrule = shortport.port_or_service(23, 'telnet')
|
|
|
|
local escape_cred = function(cred)
|
|
if cred == '' then
|
|
return '<blank>'
|
|
else
|
|
return cred
|
|
end
|
|
end
|
|
|
|
---
|
|
-- Go through telnet's option palaver so we can get to the login prompt.
|
|
-- We just deny every options the server asks us about.
|
|
local negotiate_options = function(result, soc)
|
|
local index, x, opttype, opt, retbuf
|
|
|
|
index = 0
|
|
retbuf = strbuf.new()
|
|
|
|
while true do
|
|
|
|
-- 255 is IAC (Interpret As Command)
|
|
index, x = string.find(result, '\255', index)
|
|
|
|
if not index then
|
|
break
|
|
end
|
|
|
|
opttype = string.byte(result, index+1)
|
|
opt = string.byte(result, index+2)
|
|
|
|
-- don't want it! won't do it!
|
|
if opttype == 251 or opttype == 252 then
|
|
opttype = 254
|
|
elseif opttype == 253 or opttype == 254 then
|
|
opttype = 252
|
|
end
|
|
|
|
retbuf = retbuf .. string.char(255)
|
|
retbuf = retbuf .. string.char(opttype)
|
|
retbuf = retbuf .. string.char(opt)
|
|
index = index + 1
|
|
end
|
|
soc:send(strbuf.dump(retbuf))
|
|
end
|
|
|
|
---
|
|
-- A semi-state-machine that takes action based on output from the
|
|
-- server. Through pattern matching, it tries to deem if a user/pass
|
|
-- pair is valid. Telnet does not have a way of telling the client
|
|
-- if it was authenticated....so we have to make an educated guess
|
|
local brute_line = function(line, user, pass, usent, soc)
|
|
|
|
if (line:find 'incorrect' or line:find 'failed' or line:find 'denied' or
|
|
line:find 'invalid' or line:find 'bad') and usent then
|
|
usent = false
|
|
return 2, nil, usent
|
|
|
|
elseif (line:find '[/>%%%$#]+' or line:find "last login%s*:" or
|
|
line:find '%u:\\') and not
|
|
(line:find 'username%s*:' and line:find 'login%s*:') and
|
|
usent then
|
|
return 1, escape_cred(user) .. ' - ' .. escape_cred(pass) .. '\n', usent
|
|
|
|
elseif line:find 'username%s*:' or line:find 'login%s*:' then
|
|
try(soc:send(user .. '\r\0'))
|
|
usent = true
|
|
|
|
elseif line:find 'password%s*:' or line:find 'passcode%s*:' then
|
|
-- fix, add 'password only' support
|
|
if not usent then return 1, nil, usent end
|
|
try(soc:send(pass .. '\r\0'))
|
|
end
|
|
|
|
return 0, nil, usent
|
|
end
|
|
|
|
--[[
|
|
Splits the input into lines and passes it to brute_line()
|
|
so it can try to login with <user> and <pass>
|
|
|
|
return value:
|
|
(1, user:pass) - valid pair
|
|
(2, nil) - invalid pair
|
|
(3, nil) - disconnected and invalid pair
|
|
(4, nil) - disconnected and didn't send pair
|
|
--]]
|
|
|
|
local brute_cred = function(user, pass, soc)
|
|
local status, ret, value, usent, results
|
|
|
|
usent = false ; ret = 0
|
|
|
|
while true do
|
|
status, results = soc:receive_lines(1)
|
|
|
|
-- remote host disconnected
|
|
if not status then
|
|
if usent then return 3
|
|
else return 4
|
|
end
|
|
end
|
|
|
|
if (string.byte(results, 1) == 255) then
|
|
negotiate_options(results, soc)
|
|
end
|
|
|
|
results = string.lower(results)
|
|
|
|
for line in results:gmatch '[^\r\n]+' do
|
|
ret, value, usent = brute_line(line, user, pass, usent, soc)
|
|
if (ret > 0) then
|
|
return ret, value
|
|
end
|
|
end
|
|
end
|
|
return 1, "error -> this should never happen"
|
|
end
|
|
|
|
action = function(host, port)
|
|
local pair, status
|
|
local user, pass, count, rbuf
|
|
local usernames, passwords
|
|
|
|
status, usernames = unpwdb.usernames()
|
|
if(not(status)) then
|
|
stdnse.format_output(false, usernames)
|
|
end
|
|
|
|
status, passwords = unpwdb.passwords()
|
|
if(not(status)) then
|
|
return stdnse.format_output(false, passwords)
|
|
end
|
|
|
|
pair = nil
|
|
status = 3
|
|
count = 0
|
|
|
|
local opts = {timeout=4000}
|
|
|
|
local soc, line, best_opt = comm.tryssl(host, port, "\n",opts)
|
|
if not soc then
|
|
return stdnse.format_output(false, "Unable to open connection")
|
|
end
|
|
|
|
-- continually try user/pass pairs (reconnecting, if we have to)
|
|
-- until we find a valid one or we run out of pairs
|
|
pass = passwords()
|
|
while not (status == 1) do
|
|
|
|
if status == 2 or status == 3 then
|
|
user = usernames()
|
|
if(not(user)) then
|
|
usernames('reset')
|
|
user = usernames()
|
|
pass = passwords()
|
|
|
|
if(not(pass)) then
|
|
return stdnse.format_output(true, "No accounts found")
|
|
end
|
|
end
|
|
|
|
stdnse.print_debug(2, "telnet-brute: Trying %s/%s", user, pass)
|
|
end
|
|
|
|
-- make sure we don't get stuck in a loop
|
|
if status == 4 then
|
|
count = count + 1
|
|
if count > 3 then
|
|
return false, nil
|
|
end
|
|
else
|
|
count = 0
|
|
end
|
|
|
|
if status == 3 or status == 4 then
|
|
try(soc:connect(host, port, best_opt))
|
|
end
|
|
|
|
status, pair = brute_cred(user, pass, soc)
|
|
end
|
|
|
|
soc:close()
|
|
|
|
return pair
|
|
end
|
|
|