forked from rituallyunclean/NSE_2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ftp-anon.nse
190 lines (165 loc) · 5.62 KB
/
ftp-anon.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
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.
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"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
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 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)
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)
end
end
end
if code and code >= 200 and code < 300 then
-- We are primarily looking for 230: User logged in, proceed.
else
if not code then
stdnse.print_debug(1, "ftp-anon: 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.print_debug(1, "ftp-anon: got code %d %q.", code, message)
end
return nil
end
local result = {}
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