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
|