1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
|
local brute = require "brute"
local creds = require "creds"
local shortport = require "shortport"
local string = require "string"
local have_zlib, zlib = pcall(require, "zlib")
description = [[
Performs brute force password auditing against the DelugeRPC daemon.
]]
---
-- @usage
-- nmap --script deluge-rpc-brute -p 58846 <host>
--
-- @output
-- PORT STATE SERVICE REASON TTL
-- 58846/tcp open unknown syn-ack 0
-- | deluge-rpc-brute:
-- | Accounts
-- | admin:default - Valid credentials
-- | Statistics
-- |_ Performed 8 guesses in 1 seconds, average tps: 8
author = "Claudiu Perta <claudiu.perta@gmail.com>"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"intrusive", "brute"}
portrule = shortport.port_or_service(58846, "deluge-rpc")
-- Returns an rencoded login request with the given username and password.
-- The format of the login command is the following:
--
-- ((0, 'daemon.login', ('username', 'password'), {}),)
--
-- This is inspired from deluge source code, in particular, see
-- http://git.deluge-torrent.org/deluge/tree/deluge/rencode.py
local rencoded_login_request = function(username, password)
local INT_POS_FIXED_START = 0
local INT_POS_FIXED_COUNT = 44
-- Dictionaries with length embedded in typecode.
local DICT_FIXED_START = 102
local DICT_FIXED_COUNT = 25
-- Strings with length embedded in typecode.
local STR_FIXED_START = 128
local STR_FIXED_COUNT = 64
-- Lists with length embedded in typecode.
local LIST_FIXED_START = 192
local LIST_FIXED_COUNT = 64
if #username > 0xff - STR_FIXED_START then
return nil, "Username too long"
elseif #password > 0xff - STR_FIXED_START then
return nil, "Password too long"
end
-- Encode the login request:
-- ((0, 'daemon.login', ('username', 'password'), {}),)
local request = string.pack("BBBB",
LIST_FIXED_START + 1,
LIST_FIXED_START + 4,
INT_POS_FIXED_START,
STR_FIXED_START + string.len("daemon.login")
)
.. "daemon.login"
.. string.pack("BB",
LIST_FIXED_START + 2,
STR_FIXED_START + string.len(username)
)
.. username
.. string.pack("B",
STR_FIXED_START + string.len(password)
)
.. password
.. string.pack("B", DICT_FIXED_START)
return request
end
Driver = {
new = function(self, host, port, invalid_users)
local o = {}
setmetatable(o, self)
self.__index = self
o.host = host
o.port = port
o.invalid_users = invalid_users
return o
end,
connect = function(self)
local status, err
self.socket = brute.new_socket()
self.socket:set_timeout(
((self.host.times and self.host.times.timeout) or 8) * 1000)
local status, err = self.socket:connect(self.host, self.port, "ssl")
if not status then
return false, brute.Error:new("Failed to connect to server")
end
return true
end,
disconnect = function(self)
self.socket:close()
end,
login = function(self, username, password)
if (self.invalid_users[username]) then
return false, brute.Error:new("Invalid user")
end
local request, err = rencoded_login_request(username, password)
if not request then
return false, brute.Error:new(err)
end
local status, err = self.socket:send(zlib.compress(request))
if not status then
return false, brute.Error:new("Login error")
end
local status, response = self.socket:receive()
if not status then
return false, brute.Error:new("Login error")
end
response = zlib.decompress(response)
if response:match("BadLoginError") then
local error_message = "Login error"
if response:match("Username does not exist") then
self.invalid_users[username] = true
error_message = "Username not found"
elseif response:match("Password does not match") then
error_message = "Username not found"
end
return false, brute.Error:new(error_message)
end
return true, creds.Account:new(username, password, creds.State.VALID)
end,
check = function(self)
return true
end
}
action = function(host, port)
if not have_zlib then
return "Error: zlib required!"
end
local invalid_users = {}
local engine = brute.Engine:new(Driver, host, port, invalid_users)
engine.options.script_name = SCRIPT_NAME
local status, results = engine:start()
return results
end
|