Lucene search
K

telnet-brute NSE Script

🗓️ 29 Jun 2009 23:48:19Reported by nnposterType 
nmap
 nmap
🔗 nmap.org👁 1319 Views

telnet-brute NSE Script for brute-force password auditing against telnet servers with options for thread count, timeout connection, and credential

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for Code Injection in Samba
2 Dec 202509:55
githubexploit
GithubExploit
Exploit for Code Injection in Samba
25 May 201713:20
githubexploit
GithubExploit
Exploit for Code Injection in Samba
15 May 202106:52
githubexploit
GithubExploit
Exploit for Code Injection in Samba
30 May 201715:08
githubexploit
GithubExploit
Exploit for Code Injection in Samba
25 May 201713:20
githubexploit
GithubExploit
Exploit for CVE-2017-0143
16 May 201719:34
githubexploit
GithubExploit
Exploit for Code Injection in Samba
9 May 202102:32
githubexploit
GithubExploit
Exploit for Code Injection in Samba
5 Jun 201716:25
githubexploit
GithubExploit
Exploit for Code Injection in Samba
26 May 201700:58
githubexploit
GithubExploit
Exploit for Code Injection in Samba
1 Nov 202223:17
githubexploit
Rows per page
local comm = require "comm"
local coroutine = require "coroutine"
local creds = require "creds"
local match = require "match"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local strbuf = require "strbuf"
local string = require "string"
local brute = require "brute"

description = [[
Performs brute-force password auditing against telnet servers.
]]

---
-- @usage
--   nmap -p 23 --script telnet-brute --script-args userdb=myusers.lst,passdb=mypwds.lst,telnet-brute.timeout=8s <target>
--
-- @output
-- 23/tcp open  telnet
-- | telnet-brute:
-- |   Accounts
-- |     wkurtz:colonel
-- |   Statistics
-- |_    Performed 15 guesses in 19 seconds, average tps: 0
--
-- @args telnet-brute.timeout   Connection time-out timespec (default: "5s")
-- @args telnet-brute.autosize  Whether to automatically reduce the thread
--                              count based on the behavior of the target
--                              (default: "true")

author = "nnposter"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {'brute', 'intrusive'}

portrule = shortport.port_or_service(23, 'telnet')


-- Miscellaneous script-wide parameters and constants
local arg_timeout = stdnse.get_script_args(SCRIPT_NAME .. ".timeout") or "5s"
local arg_autosize = stdnse.get_script_args(SCRIPT_NAME .. ".autosize") or "true"

local telnet_timeout      -- connection timeout (in ms), from arg_timeout
local telnet_autosize     -- whether to auto-size the execution, from arg_autosize
local telnet_eol = "\r\n" -- termination string for sent lines
local conn_retries = 2    -- # of retries when attempting to connect
local critical_debug = 1  -- debug level for printing critical messages
local login_debug = 2     -- debug level for printing attempted credentials
local detail_debug = 3    -- debug level for printing individual login steps
                          --                          and thread-level info

---
-- Print debug messages, prepending them with the script name
--
-- @param level Verbosity level
-- @param fmt Format string.
-- @param ... Arguments to format.
local debug = stdnse.debug

---
-- Decide whether a given string (presumably received from a telnet server)
-- represents a username prompt
--
-- @param str The string to analyze
-- @return Verdict (true or false)
local is_username_prompt = function (str)
  local lcstr = str:lower()
  return lcstr:find("%f[%w]username%s*:%s*$")
      or lcstr:find("%f[%w]login%s*:%s*$")
end


---
-- Decide whether a given string (presumably received from a telnet server)
-- represents a password prompt
--
-- @param str The string to analyze
-- @return Verdict (true or false)
local is_password_prompt = function (str)
  local lcstr = str:lower()
  return lcstr:find("%f[%w]password%s*:%s*$")
      or lcstr:find("%f[%w]passcode%s*:%s*$")
end


---
-- Decide whether a given string (presumably received from a telnet server)
-- indicates a successful login
--
-- @param str The string to analyze
-- @return Verdict (true or false)
local is_login_success = function (str)
  if str:find("^[A-Z]:\\") then                         -- Windows telnet
    return true
  end
  local lcstr = str:lower()
  return lcstr:find("[/>%%%$#]%s*$")                    -- general prompt
      or lcstr:find("^last login%s*:")                  -- linux telnetd
      or lcstr:find("%f[%w]main%smenu%f[%W]")           -- Netgear RM356
      or lcstr:find("^enter terminal emulation:%s*$")   -- Hummingbird telnetd
      or lcstr:find("%f[%w]select an option%f[%W]")     -- Zebra PrintServer
end


---
-- Decide whether a given string (presumably received from a telnet server)
-- indicates a failed login
--
-- @param str The string to analyze
-- @return Verdict (true or false)
local is_login_failure = function (str)
  local lcstr = str:lower()
  return lcstr:find("%f[%w]incorrect%f[%W]")
      or lcstr:find("%f[%w]failed%f[%W]")
      or lcstr:find("%f[%w]denied%f[%W]")
      or lcstr:find("%f[%w]invalid%f[%W]")
      or lcstr:find("%f[%w]bad%f[%W]")
end


---
-- Strip off ANSI escape sequences (terminal codes) that start with <esc>[
-- and replace them with white space, namely the VT character (0x0B).
-- This way their new representation can be naturally matched with pattern %s.
--
-- @param str The string that needs to be strained
-- @return The same string without the escape sequences
local remove_termcodes = function (str)
  local mark = '\x0B'
  return str:gsub('\x1B%[%??%d*%a', mark)
            :gsub('\x1B%[%??%d*;%d*%a', mark)
end


---
-- Simple class to encapsulate connection operations
local Connection = { methods = {} }


---
-- Initialize a connection object
--
-- @param host Telnet host
-- @param port Telnet port
-- @return Connection object or nil (if the operation failed)
Connection.new = function (host, port, proto)
  local soc = brute.new_socket(proto)
  if not soc then return nil end
  return setmetatable({
                        socket = soc,
                        isopen = false,
                        buffer = nil,
                        error = nil,
                        host = host,
                        port = port,
                        proto = proto
                      },
                      {
                        __index = Connection.methods,
                        __gc = Connection.methods.close
                      })
end


---
-- Open the connection
--
-- @param self Connection object
-- @return Status (true or false)
-- @return nil if the operation was successful; error code otherwise
Connection.methods.connect = function (self)
  local status
  local wait = 1

  self.buffer = ""

  for tries = 0, conn_retries do
    self.socket:set_timeout(telnet_timeout)
    status, self.error = self.socket:connect(self.host, self.port, self.proto)
    if status then break end
    stdnse.sleep(wait)
    wait = 2 * wait
  end

  self.isopen = status
  return status, self.error
end


---
-- Close the connection
--
-- @param self Connection object
-- @return Status (true or false)
-- @return nil if the operation was successful; error code otherwise
Connection.methods.close = function (self)
  if not self.isopen then return true, nil end
  local status
  self.isopen = false
  self.buffer = nil
  status, self.error = self.socket:close()
  return status, self.error
end


---
-- Send one line through the connection to the server
--
-- @param self Connection object
-- @param line Characters to send, will be automatically terminated
-- @return Status (true or false)
-- @return nil if the operation was successful; error code otherwise
Connection.methods.send_line = function (self, line)
  local status
  status, self.error = self.socket:send(line .. telnet_eol)
  return status, self.error
end


---
-- Add received data to the connection buffer while taking care
-- of telnet option signalling
--
-- @param self Connection object
-- @param data Data string to add to the buffer
-- @return Number of characters in the connection buffer
Connection.methods.fill_buffer = function (self, data)
  local outbuf = strbuf.new(self.buffer)
  local optbuf = strbuf.new()
  local oldpos = 0

  while true do
    -- look for IAC (Interpret As Command)
    local newpos = data:find('\255', oldpos, true)
    if not newpos then break end

    outbuf = outbuf .. data:sub(oldpos, newpos - 1)
    local opttype, opt = data:byte(newpos + 1, newpos + 2)

    if opttype == 251 or opttype == 252 then
      -- Telnet Will / Will Not
      -- regarding ECHO or GO-AHEAD, agree with whatever the
      -- server wants (or not) to do; otherwise respond with
      -- "don't"
      opttype = (opt == 1 or opt == 3) and opttype + 2 or 254
    elseif opttype == 253 or opttype == 254 then
      -- Telnet Do / Do not
      -- I will not do whatever the server wants me to
      opttype = 252
    end

    optbuf = optbuf .. string.char(255, opttype, opt)
    oldpos = newpos + 3
  end

  self.buffer = strbuf.dump(outbuf) .. data:sub(oldpos)
  self.socket:send(strbuf.dump(optbuf))
  return self.buffer:len()
end


---
-- Return leading part of the connection buffer, up to a line termination,
-- and refill the buffer as needed
--
-- @param self Connection object
-- @param normalize whether the returned line is normalized (default: false)
-- @return String representing the first line in the buffer
Connection.methods.get_line = function (self)
  if self.buffer:len() == 0 then
    -- refill the buffer
    local status, data = self.socket:receive_buf(match.pattern_limit("[\r\n:>%%%$#\255].*", 2048), true)
    if not status then
      -- connection error
      self.error = data
      return nil
    end

    self:fill_buffer(data)
  end
  return remove_termcodes(self.buffer:match('^[^\r\n]*'))
end


---
-- Discard leading part of the connection buffer, up to and including
-- one or more line terminations
--
-- @param self Connection object
-- @return Number of characters remaining in the connection buffer
Connection.methods.discard_line = function (self)
  self.buffer = self.buffer:gsub('^[^\r\n]*[\r\n]*', '', 1)
  return self.buffer:len()
end


---
-- Ghost connection object
Connection.GHOST = {}


---
-- Simple class to encapsulate target properties, including thread-specific data
-- persisted across Driver instances
local Target = { methods = {} }


---
-- Initialize a target object
--
-- @param host Telnet host
-- @param port Telnet port
-- @return Target object or nil (if the operation failed)
Target.new = function (host, port)
  local soc, _, proto = comm.tryssl(host, port, "\n", {timeout=telnet_timeout})
  if not soc then return nil end
  soc:close()
  return setmetatable({
                        host = host,
                        port = port,
                        proto = proto,
                        workers = setmetatable({}, { __mode = "k" })
                      },
                      { __index = Target.methods })
end


---
-- Set up the calling thread as one of the worker threads
--
-- @param self Target object
Target.methods.worker = function (self)
  local thread = coroutine.running()
  self.workers[thread] = self.workers[thread] or {}
end


---
-- Provide the calling worker thread with an open connection to the target.
-- The state of the connection is at the beginning of the login flow.
--
-- @param self Target object
-- @return Status (true or false)
-- @return Connection if the operation was successful; error code otherwise
Target.methods.attach = function (self)
  local worker = self.workers[coroutine.running()]
  local conn = worker.conn
               or Connection.new(self.host, self.port, self.proto)
  if not conn then return false, "Unable to allocate connection" end
  worker.conn = conn

  if conn.error then conn:close() end
  if not conn.isopen then
    local status, err = conn:connect()
    if not status then return false, err end
  end

  return true, conn
end


---
-- Recover a connection used by the calling worker thread
--
-- @param self Target object
-- @return Status (true or false)
-- @return nil if the operation was successful; error code otherwise
Target.methods.detach = function (self)
  local conn = self.workers[coroutine.running()].conn
  local status, response = true, nil
  if conn and conn.error then status, response = conn:close() end
  return status, response
end


---
-- Set the state of the calling worker thread
--
-- @param self Target object
-- @param inuse Whether the worker is in use (true or false)
-- @return inuse
Target.methods.inuse = function (self, inuse)
  self.workers[coroutine.running()].inuse = inuse
  return inuse
end


---
-- Decide whether the target is still being worked on
--
-- @param self Target object
-- @return Verdict (true or false)
Target.methods.idle = function (self)
  local idle = true
  for t, w in pairs(self.workers) do
    idle = idle and (not w.inuse or coroutine.status(t) == "dead")
  end
  return idle
end


---
-- Class that can be used as a "driver" by brute.lua
local Driver = { methods = {} }


---
-- Initialize a driver object
--
-- @param host Telnet host
-- @param port Telnet port
-- @param target instance of a Target class
-- @return Driver object or nil (if the operation failed)
Driver.new = function (self, host, port, target)
  assert(host == target.host and port == target.port, "Target mismatch")
  target:worker()
  return setmetatable({
                        target = target,
                        connect = telnet_autosize
                                  and Driver.methods.connect_autosize
                                  or Driver.methods.connect_simple,
                        thread_exit = nmap.condvar(target)
                      },
                      { __index = Driver.methods })
end


---
-- Connect the driver to the target (when auto-sizing is off)
--
-- @param self Driver object
-- @return Status (true or false)
-- @return nil if the operation was successful; error code otherwise
Driver.methods.connect_simple = function (self)
  assert(not self.conn, "Multiple connections attempted")
  local status, response = self.target:attach()
  if status then
    self.conn = response
    response = nil
  end
  return status, response
end


---
-- Connect the driver to the target (when auto-sizing is on)
--
-- @param self Driver object
-- @return Status (true or false)
-- @return nil if the operation was successful; error code otherwise
Driver.methods.connect_autosize = function (self)
  assert(not self.conn, "Multiple connections attempted")
  self.target:inuse(true)
  local status, response = self.target:attach()
  if status then
    -- connected to the target
    self.conn = response
    if self:prompt() then
      -- successfully reached login prompt
      return true, nil
    end
    -- connected but turned away
    self.target:detach()
  end
  -- let's park the thread here till all the functioning threads finish
  self.target:inuse(false)
  debug(detail_debug, "Retiring %s", tostring(coroutine.running()))
  while not self.target:idle() do self.thread_exit("wait") end
  -- pretend that it connected
  self.conn = Connection.GHOST
  return true, nil
end


---
-- Disconnect the driver from the target
--
-- @param self Driver object
-- @return Status (true or false)
-- @return nil if the operation was successful; error code otherwise
Driver.methods.disconnect = function (self)
  assert(self.conn, "Attempt to disconnect non-existing connection")
  if self.conn.isopen and not self.conn.error then
    -- try to reach new login prompt
    self:prompt()
  end
  self.conn = nil
  return self.target:detach()
end


---
-- Attempt to reach telnet login prompt on the target
--
-- @param self Driver object
-- @return line Reached prompt or nil
Driver.methods.prompt = function (self)
  assert(self.conn, "Attempt to use disconnected driver")
  local conn = self.conn
  local line
  repeat
    line = conn:get_line()
  until not line
        or is_username_prompt(line)
        or is_password_prompt(line)
        or not conn:discard_line()
  return line
end


---
-- Attempt to establish authenticated telnet session on the target
--
-- @param self Driver object
-- @return Status (true or false)
-- @return instance of creds.Account if the operation was successful;
--         instance of brute.Error otherwise
Driver.methods.login = function (self, username, password)
  assert(self.conn, "Attempt to use disconnected driver")
  local sent_username = self.target.passonly
  local sent_password = false
  local conn = self.conn

  local loc = " in " .. tostring(coroutine.running())

  local connection_error = function (msg)
    debug(detail_debug, msg .. loc)
    local err = brute.Error:new(msg)
    err:setRetry(true)
    return false, err
  end

  local passonly_error = function ()
    local msg = "Password prompt encountered"
    debug(critical_debug, msg .. loc)
    local err = brute.Error:new(msg)
    err:setAbort(true)
    return false, err
  end

  local username_error = function ()
    local msg = "Invalid username encountered"
    debug(detail_debug, msg .. loc)
    local err = brute.Error:new(msg)
    err:setInvalidAccount(username)
    return false, err
  end

  local login_error = function ()
    local msg = "Login failed"
    debug(detail_debug, msg .. loc)
    return false, brute.Error:new(msg)
  end

  local login_success = function ()
    local msg = "Login succeeded"
    debug(detail_debug, msg .. loc)
    return true, creds.Account:new(username, password, creds.State.VALID)
  end

  local login_no_password = function ()
    local msg = "Login succeeded without password"
    debug(detail_debug, msg .. loc)
    return true, creds.Account:new(username, "", creds.State.VALID)
  end

  debug(detail_debug, "Login attempt %s:%s%s", username, password, loc)

  if conn == Connection.GHOST then
    -- reached when auto-sizing is enabled and all worker threads
    -- failed
    return connection_error("Service unreachable")
  end

  -- username has not yet been sent
  while not sent_username do
    local line = conn:get_line()
    if not line then
      -- stopped receiving data
      return connection_error("Login prompt not reached")
    end

    if is_username_prompt(line) then
      -- being prompted for a username
      conn:discard_line()
      debug(detail_debug, "Sending username" .. loc)
      if not conn:send_line(username) then
        return connection_error(conn.error)
      end
      sent_username = true
      if conn:get_line() == username then
        -- ignore; remote echo of the username in effect
        conn:discard_line()
      end

    elseif is_password_prompt(line) then
      -- looks like 'password only' support
      return passonly_error()

    else
      -- ignore; insignificant response line
      conn:discard_line()
    end
  end

  -- username has been already sent
  while not sent_password do
    local line = conn:get_line()
    if not line then
      -- remote host disconnected
      return connection_error("Password prompt not reached")
    end

    if is_login_success(line) then
      -- successful login without a password
      conn:close()
      return login_no_password()

    elseif is_password_prompt(line) then
      -- being prompted for a password
      conn:discard_line()
      debug(detail_debug, "Sending password" .. loc)
      if not conn:send_line(password) then
        return connection_error(conn.error)
      end
      sent_password = true

    elseif is_login_failure(line) then
      -- failed login without a password; explicitly told so
      conn:discard_line()
      return username_error()

    elseif is_username_prompt(line) then
      -- failed login without a password; prompted again for a username
      return username_error()

    else
      -- ignore; insignificant response line
      conn:discard_line()
    end

  end

  -- password has been already sent
  while true do
    local line = conn:get_line()
    if not line then
      -- remote host disconnected
      return connection_error("Login not completed")
    end

    if is_login_success(line) then
      -- successful login
      conn:close()
      return login_success()

    elseif is_login_failure(line) then
      -- failed login; explicitly told so
      conn:discard_line()
      return login_error()

    elseif is_password_prompt(line) or is_username_prompt(line) then
      -- failed login; prompted again for credentials
      return login_error()

    else
      -- ignore; insignificant response line
      conn:discard_line()
    end

  end

  -- unreachable code
  assert(false, "Reached unreachable code")
end


action = function (host, port)
  local ts, tserror = stdnse.parse_timespec(arg_timeout)
  if not ts then
    return stdnse.format_output(false, "Invalid timeout value: " .. tserror)
  end
  telnet_timeout = 1000 * ts
  telnet_autosize = arg_autosize:lower() == "true"

  local target = Target.new(host, port)
  if not target then
    return stdnse.format_output(false, "Unable to connect to the target")
  end

  local engine = brute.Engine:new(Driver, host, port, target)
  engine.options.script_name = SCRIPT_NAME
  target.passonly = engine.options.passonly
  local _, result = engine:start()
  return result
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

16 Jul 2018 03:17Current
9.4High risk
Vulners AI Score9.4
EPSS0.94176
1319