-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathfortune_ftp_img.py
executable file
·166 lines (133 loc) · 4.2 KB
/
fortune_ftp_img.py
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
#!/usr/bin/env python3
from ftplib import FTP, FTP_TLS, error_perm, error_proto, error_reply, error_temp
from socket import timeout
from time import sleep
from fire import Fire
from lib.files import LOCAL_DIR
from lib.models import add_result
from lib.scan import check_port, generate_ips, process_each
from lib.utils import str_to_filename
FTP_FILES_PATH = LOCAL_DIR / 'ftp_files' / 'files'
FTP_LOGS_PATH = LOCAL_DIR / 'ftp_files' / 'logs'
FTP_LOG_PATH = LOCAL_DIR / 'ftp.txt'
INTERESTING_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.gif', '.bmp')
PATH_BLACKLIST = ('.', '..', 'bin')
def download_image(ftp, path):
print('* DL', ftp.host, path)
out_filename = '%s_%s' % (ftp.host, path.rpartition('/')[-1])
out_path = str(FTP_FILES_PATH / str_to_filename(out_filename))
r_cmd = ('RETR %s' % path)
with open(out_path, 'wb') as of:
ftp.retrbinary(r_cmd, of.write)
def traverse(ftp: FTP, depth=0, files=None):
if files is None:
files = []
if depth > 10:
print(ftp.host, 'too deep, leave')
return
paths = [p for p in ftp.nlst() if p not in PATH_BLACKLIST]
for path in paths:
files.append(path)
with (FTP_LOGS_PATH / ('%s.txt' % ftp.host)).open('a') as f:
f.write('%s\n' % path)
if len(files) > 100:
print(ftp.host, 'too many files, leave')
return
try:
if path.lower().endswith(INTERESTING_EXTENSIONS):
download_image(ftp, path)
return path
# skip files by extension delimiter
if '.' in path:
print('-', path)
continue
ftp.cwd(path)
print(ftp.host, 'cd', path)
found = traverse(ftp, depth+1, files)
ftp.cwd('..')
if found:
return
except error_perm:
pass
def get_files(ftp: FTP, lock):
ip = ftp.host
lst = [p for p in ftp.nlst() if p not in ('.', '..')]
if not lst:
print('-', ip, 'no files')
return
banner = ''
try:
banner = ftp.getwelcome()
except:
pass
with lock:
comment = '%d file(s) in root' % len(lst)
add_result(ip, 21, comment, ['fortune'], banner)
with FTP_LOG_PATH.open('a') as f:
f.write('%s\n' % ip)
print('Traverse', ip, 'start')
res = traverse(ftp)
print('Traverse', ip, 'end')
return res
def process_ftp(ip, time, lock):
Connector: type[FTP] = FTP
retries = 5
while retries > 0:
try:
with Connector(ip, timeout=30) as ftp:
ftp.login()
print('~', ip, time, 'ms')
try:
ftp.sendcmd('OPTS UTF8 ON')
ftp.encoding = 'utf-8'
except:
pass
return get_files(ftp, lock)
except (error_perm, error_proto) as e:
if Connector is FTP:
Connector = FTP_TLS
else:
break
except (error_reply, error_temp) as e:
try:
code = int(str(e)[:3])
except:
break
if code in {331, 332}:
break # anon login only
if code == 421:
break
if code == 450:
print('-', ip, e)
break
if code == 431:
if Connector is not FTP:
break
Connector = FTP_TLS
continue
print(repr(e))
break
except OSError as e:
pass
except KeyboardInterrupt:
print('Interrupted by user.')
exit(130)
except (EOFError, UnicodeDecodeError):
break
except Exception as e:
print(repr(e))
break
retries -= 1
sleep(1)
def check_host(ip, lock):
res = check_port(ip, 21)
if res:
process_ftp(ip, int(res[1]*1000), lock)
def main(c=10_000_000, w=16):
FTP_FILES_PATH.mkdir(exist_ok=True)
FTP_LOGS_PATH.mkdir(exist_ok=True)
print('Started.')
process_each(check_host, generate_ips(c), w)
print('Finished.')
if __name__ == "__main__":
Fire(main)