summaryrefslogtreecommitdiffstats
path: root/scripts/deluge-rpc-brute.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/deluge-rpc-brute.nse')
-rw-r--r--scripts/deluge-rpc-brute.nse167
1 files changed, 167 insertions, 0 deletions
diff --git a/scripts/deluge-rpc-brute.nse b/scripts/deluge-rpc-brute.nse
new file mode 100644
index 0000000..d92b004
--- /dev/null
+++ b/scripts/deluge-rpc-brute.nse
@@ -0,0 +1,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