-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathftp-anon-better.nse
248 lines (217 loc) · 7.62 KB
/
ftp-anon-better.nse
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
local ftp = require "ftp"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
description = [[
Checks if an FTP server allows anonymous logins.
Will check for anonymous login and
USR guest PWD blank
USR guest PWD guest
If anonymous is allowed, gets a directory listing of the root directory
and highlights writeable files.
]]
---
-- @args ftp-anon.maxlist The maximum number of files to return in the
-- directory listing. By default it is 20, or unlimited if verbosity is
-- enabled. Use a negative number to disable the limit, or
-- <code>0</code> to disable the listing entirely.
--
-- @output
-- PORT STATE SERVICE
-- 21/tcp open ftp
-- | ftp-anon: Anonymous FTP login allowed (FTP code 230)
-- | -rw-r--r-- 1 1170 924 31 Mar 28 2001 .banner
-- | d--x--x--x 2 root root 1024 Jan 14 2002 bin
-- | d--x--x--x 2 root root 1024 Aug 10 1999 etc
-- | drwxr-srwt 2 1170 924 2048 Jul 19 18:48 incoming [NSE: writeable]
-- | d--x--x--x 2 root root 1024 Jan 14 2002 lib
-- | drwxr-sr-x 2 1170 924 1024 Aug 5 2004 pub
-- |_Only 6 shown. Use --script-args ftp-anon.maxlist=-1 to see all.
author = {"Eddie Bell", "Rob Nicholls", "Ange Gutek", "David Fifield", "InfoSkirmish.com"}
license = "Same as Nmap--See https://nmap.org/book/man-legal.html (Modified by InfoSkirmish.com based on org. ftp-anon script)"
categories = {"default", "auth", "safe"}
portrule = shortport.port_or_service(21, "ftp")
-- ---------------------
-- Directory listing function.
-- We ask for a PASV connexion, catch the port returned by the server, send a
-- LIST on the commands socket, connect to the data one and read the directory
-- list sent.
-- ---------------------
local function list(socket, target, max_lines)
local status, err
-- ask the server for a Passive Mode: it should give us a port to
-- listen to, where it will dump the directory listing
local buffer = stdnse.make_buffer(socket, "\r?\n")
status, err = socket:send("PASV\r\n")
if not status then
return status, err
end
local code, message = ftp.read_reply(buffer)
-- Compute the PASV port as given by the server
-- The server should answer with something like
-- 2xx Entering Passive Mode (a,b,c,d,hp,lp)
-- (-- IP--,PORT)
-- PORT is (hp x 256) + lp
local high, low = string.match(message, "%(%d+,%d+,%d+,%d+,(%d+),(%d+)%)")
if not high then
return nil, string.format("Can't parse PASV response: %q", message)
end
local pasv_port = high * 256 + low
-- Send the LIST command on the commands socket. "Fire and forget"; we
-- don't need to take care of the answer on this socket.
status, err = socket:send("LIST\r\n")
if not status then
return status, err
end
local list_socket = nmap.new_socket()
status, err = list_socket:connect(target, pasv_port, "tcp")
if not status then
return status, err
end
local listing = {}
while not max_lines or #listing < max_lines do
local status, data = list_socket:receive_buf("\r?\n", false)
if (not status and data == "EOF") or data == "" then
break
end
if not status then
return status, data
end
listing[#listing + 1] = data
end
return true, listing
end
--- Connects to the FTP server and checks if the server allows anonymous logins.
action = function(host, port)
local socket = nmap.new_socket()
local code, message
local err_catch = function()
socket:close()
end
local wearedone = 0
local result = {}
local max_list = stdnse.get_script_args("ftp-anon.maxlist")
if not max_list then
if nmap.verbosity() == 0 then
max_list = 20
else
max_list = nil
end
else
max_list = tonumber(max_list)
if max_list < 0 then
max_list = nil
end
end
local try = nmap.new_try(err_catch)
try(socket:connect(host, port))
local buffer = stdnse.make_buffer(socket, "\r?\n")
-- Read banner.
code, message = ftp.read_reply(buffer)
if code and code == 220 then
try(socket:send("USER anonymous\r\n"))
code, message = ftp.read_reply(buffer)
if code == 331 then
-- 331: User name okay, need password.
try(socket:send("PASS IEUser@\r\n"))
code, message = ftp.read_reply(buffer)
result[#result + 1] = "Trying Anonymous Login (FTP PASS Code " .. code .. ")"
if code == 230 then
wearedone = 1
end
end
if code == 332 then
-- 332: Need account for login.
-- This is rarely seen but may come in response to a
-- USER or PASS command. As we're doing this
-- anonymously, send back a blank ACCT.
try(socket:send("ACCT\r\n"))
code, message = ftp.read_reply(buffer)
if code == 331 then
-- 331: User name okay, need password.
try(socket:send("PASS IEUser@\r\n"))
code, message = ftp.read_reply(buffer)
result[#result + 1] = "Trying ACCT Login (FTP PASS Code " .. code .. ")"
if code == 230 then
wearedone = 1
end
end
end
if wearedone == 0 then
try(socket:send("USER guest\r\n"))
code, message = ftp.read_reply(buffer)
if code == 331 then
-- 331: User name okay, need password.
try(socket:send("PASS IEUser@\r\n"))
code, message = ftp.read_reply(buffer)
result[#result + 1] = "Trying Guest/IEUSER Login (FTP PASS Code " .. code .. ")"
if code == 230 then
wearedone = 1
end
end
end
if wearedone == 0 then
try(socket:send("USER guest\r\n"))
code, message = ftp.read_reply(buffer)
if code == 331 then
-- 331: User name okay, need password.
try(socket:send("PASS guest\r\n"))
code, message = ftp.read_reply(buffer)
result[#result + 1] = "Trying Guest/Guest Login (FTP PASS Code " .. code .. ")"
if code == 230 then
wearedone = 1
end
end
end
if wearedone == 0 then
try(socket:send("USER guest\r\n"))
code, message = ftp.read_reply(buffer)
if code == 331 then
-- 331: User name okay, need password.
try(socket:send("PASS\r\n"))
code, message = ftp.read_reply(buffer)
result[#result + 1] = "Trying Guest/Blank Login (FTP PASS Code " .. code .. ")"
if code == 230 then
wearedone = 1
end
end
end
end
if code and code >= 200 and code < 300 then
-- Ignore.
else
if not code then
stdnse.debug1("got socket error %q.", message)
elseif code == 421 or code == 530 then
-- Don't log known error codes.
-- 421: Service not available, closing control connection.
-- 530: Not logged in.
else
stdnse.debug1("got code %d %q.", code, message)
end
result[#result + 1] = "No Anonymous Login Found"
return table.concat(result, "\n")
end
result[#result + 1] = "Anonymous FTP login allowed (FTP code " .. code .. ")"
if not max_list or max_list > 0 then
local status, listing = list(socket, host, max_list)
socket:close()
if not status then
result[#result + 1] = "Can't get directory listing: " .. listing
else
for _, item in ipairs(listing) do
-- Just a quick passive check on user rights.
if string.match(item, "^[d-].......w.") then
item = item .. " [NSE: writeable]"
end
result[#result + 1] = item
end
if max_list and #listing == max_list then
result[#result + 1] = string.format("Only %d shown. Use --script-args %s.maxlist=-1 to see all.", #listing, SCRIPT_NAME)
end
end
end
return table.concat(result, "\n")
end