-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathemail-validator.py
383 lines (309 loc) · 12.6 KB
/
email-validator.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
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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" SMTP Enumeration Script """
"""Uses time-based delays in responses and RCPT to identify valid emails on an SMTP server. Particularly created for a certain email appliance."""
__author__ = "Sam Bertram"
__version__ = "0.1"
hello_msg = '''
____ _ _ ____ _ _ _ _ ____ _ _ ___ ____ ___ ____ ____
|___ |\/| |__| | | | | |__| | | | \ |__| | | | |__/
|___ | | | | | |___ \/ | | |___ | |__/ | | | |__| | \
version: %s
''' % __version__
data = {}
import io
import csv
import re
import platform
import argparse
import csv
import os
import datetime
import urllib2
import io
from random import randint
from time import sleep
from tempfile import NamedTemporaryFile
import urllib
import pprint
import csv
import time
import socket
# pretty print
pp = pprint.PrettyPrinter(indent=4)
parser = argparse.ArgumentParser(description="Work in progress...email-validator.py --host TARGETHOST -p 25 -f [email protected] -r recipients.txt -D target.com -vv --delay 2000")
parser.add_argument('-H','--host', help='The host to target for enumeration (hostname or IP)',required=True)
parser.add_argument('-p','--port', help='The default SMTP service port (default 25)', default=25)
parser.add_argument('-f','--from', help='MAIL FROM address (include the @domain.com)', default="[email protected]")
parser.add_argument('-r','--recipient', help='RCPT TO address to validate (include the @domain.com), or the filename of addresses',required=True)
parser.add_argument('-i','--index',help="Start from this count (index). Good for fast-forwarding through a file",default=0)
# TODO parse banner from response if not set??
parser.add_argument('-b','--banner', help='The HELO command to send (typically banner)', default="x")
parser.add_argument('-D','--domain', help='Domain to append on recipients (ex: gmail.com)')
parser.add_argument('-t','--threshold',help="The timeout threshold for a valid user account (default 500ms)", default=500)
parser.add_argument('-d','--delay',help="The delay between attempts (default 5000ms)", default=6000)
parser.add_argument('-j','--jitter',help="The delay jitter between attempts. Multiplied by sleep delay (default 0.3)", default=0.5)
parser.add_argument('-c','--csv',help="The CSV file to save the found accounts at", default="output_smtp.csv" )
parser.add_argument('-v','--verbose', help='Verbose output', action="store_true")
parser.add_argument('-vv','--debug', help='Less tool output, only shows good/bad/info error messages.', action="store_true")
args = vars(parser.parse_args())
#2018-11-19 16:12:47 ubuntu@ip-172-31-17-190:~$ nc -vn 159.50.101.86 25
#Connection to 159.50.101.86 25 port [tcp/*] succeeded!
#220-target.TARGET.com ESMTP
#220 TARGET.com ESMTP Ready
#HELO x
#250 target.TARGET.com
#MAIL FROM: [email protected]
#501 #5.5.2 syntax error 'MAIL FROM: [email protected]'
#MAIL FROM: [email protected]
#501 #5.5.2 syntax error 'MAIL FROM: [email protected]'
#VRFY [email protected]
#252 ok
#VRFY [email protected]
#252 ok
#VRFY [email protected]
#252 ok
#VRFY [email protected]
#252 ok
#VRFY [email protected]
#252 ok
#MAIL FROM: foo
#501 #5.5.2 syntax error 'MAIL FROM: foo'
#MAIL FROM: <[email protected]>
#250 sender <[email protected]> ok
#RCPT TO: [email protected]
#501 #5.5.2 syntax error 'RCPT TO: [email protected]'
#RCPT TO: <[email protected]>
#550 #5.1.0 Address rejected.
#RCPT TO: <[email protected]>
#250 recipient <[email protected]> ok
#RCPT TO: <[email protected]>
#550 #5.1.0 Address rejected.
#QUIT
#221 target.TARGET.com
def main():
# if debug is enabled, enable verbose as well
if args['debug']: args['verbose'] = True
# target options
host = args['host']
port = int(args['port'])
# enumeration information
mail_from = args['from']
domain = args['domain']
rcpts = args['recipient']
# timing options
delay = int(args['delay'])
jitter = float(args['jitter'])
threshold = int(args['threshold'])
# csv file
csv = args['csv']
banner = args['banner']
# start option
index = int(args['index'])
if args['debug'] or args['verbose']:
ALERT("host - %s:%s" % (host,port),ALERT.INFO)
ALERT("enum - from: %s, domain: %s, recipient: %s" % (mail_from,domain,rcpts),ALERT.INFO)
ALERT("time - delay: %s, jitter: %s, threshold: %s" % (delay,jitter,threshold),ALERT.INFO)
ALERT("csv: %s" % csv, ALERT.INFO)
ALERT("index: %s" % index, ALERT.INFO)
# try and validate the recipient, could be a single or could be a file
if os.path.isfile(rcpts):
ALERT("Recipient input is a file... reading")
rcpts = open(rcpts).read().splitlines()
ALERT("Testing multiple accounts (%s)" % len(rcpts))
else:
ALERT("Testing single account (%s)" % rcpts)
rcpts = [rcpts]
keep_running = True
connected = False
count = 0
while keep_running:
# attempt to connect. if too many invalid recipients, then retry
# after three minutes
if not connected:
#220-target.TARGET.com ESMTP
#220 TARGET.com ESMTP Ready
# first connect to server
print_time()
# TODO what happens if i can't connect to the port?
# keep trying to connected
while not connected:
ALERT("Connecting to %s:%s..." % (host,port))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((host, port))
except socket.error, e:
ALERT("Cannot connect (%s). Sleeping for 3 minutes" % e, ALERT.SEVERE)
s.close()
time.sleep(180)
continue
#socket.error: [Errno 110] Connection timed out
s.settimeout(1)
fn = s.makefile('rwb')
resp = recv_data(s)
if "Too many invalid recipients" in resp:
ALERT("Cannot connect. Too many invalid recipients. Sleeping for 3 minutes", ALERT.SEVERE)
s.close()
time.sleep(180)
connected = False
continue
else: connected = True
ALERT("Connected.")
# if we get this far, that means we have a valid socket.
#HELO x
#250 target.TARGET.com
snooze(delay,jitter)
helo = "HELO %s" % banner
if args['debug']: ALERT(helo,ALERT.INFO)
fn.write('%s\r\n' % helo)
fn.flush()
resp = recv_data(s)
#resp = fn.readline().rstrip('\n')
#ALERT(resp)
#MAIL FROM: <[email protected]>
#250 sender <[email protected]> ok
snooze(delay,jitter)
mf = "MAIL FROM: <%s>" % mail_from
if args['debug']: ALERT(mf,ALERT.INFO)
fn.write('%s\r\n' % mf)
fn.flush()
resp = recv_data(s)
#RCPT TO: <[email protected]>
#250 recipient <[email protected]> ok
# loop through users
for rcpt_to in rcpts:
count = count + 1
# if we are starting from an index, try and go to top of loop
if index and count < index: continue
snooze(delay,jitter)
if domain: rcpt_to = "%s@%s" % (rcpt_to, domain)
if args['verbose']: ALERT("Enumerating %s account (%s/%s)..." % (rcpt_to, count, len(rcpts)))
# start timekeeping
start = datetime.datetime.now()
rcpt = "RCPT TO: <%s>" % rcpt_to
if args['debug']: ALERT(rcpt,ALERT.INFO)
fn.write('%s\r\n' % rcpt)
fn.flush()
resp = recv_data(s)
end = datetime.datetime.now()
# delay in ms
response_time = int((end - start).total_seconds() * 1000)
# if ms < threshold:
# ALERT("%s, delay of %sms, valid" % (rcpt_to, ms), ALERT.GOOD)
# response = "valid"
# else:
# ALERT("%s, delay of %sms, invalid" % (rcpt_to,ms), ALERT.BAD)
# response = "invalid"
# if user is valid, output and log to csv
# start,end,host,port,domain,mail_from,response_time,resp
csvrow = "%s,%s,%s,%s,%s,%s,%s,%s,%s" % (start.strftime('%Y-%m-%d %H:%M:%S:%f'),end.strftime('%Y-%m-%d %H:%M:%S:%f'),host,port,domain,mail_from,rcpt_to,response_time,resp.rstrip('\r\n'))
fd = open(csv,'a')
fd.write(csvrow)
fd.write("\r\n")
fd.close()
if "Too many recipients" in resp or "550 Too many invalid recipients" in resp:
ALERT("Received too many recipients on %s. Disconnected." % count, ALERT.SEVERE)
index = count
connected = False
break
if "451 Internal resource temporarily unavailable" in resp:
ALERT("Mimecast 451 received on %s. Disconnected!" % count, ALERT.SEVERE)
connected = False
break
# after looping through all recipients, we don't want to keep running
if count == len(rcpts): keep_running = False
# only disconnected if connected
if connected:
# disconnect from the server
snooze(delay,jitter)
quit = "QUIT"
ALERT(quit,ALERT.INFO)
fn.write('%s\r\n' % quit)
fn.flush()
s.close()
print_time()
# nc -v mail.DOMAIN.com 25
#Connection to mail.DOMAIN.com 25 port [tcp/smtp] succeeded!
#220 DOMAIN.com.local APPLIANCE
#HELO DOMAIN.com.local
#250 DOMAIN.com.local
#MAIL FROM: [email protected]
#250 2.1.0 Ok
#RCPT TO: [email protected]
#250 2.1.5 Ok
#RCPT TO: [email protected]
#250 2.1.5 Ok
#RCPT TO: [email protected]
#250 2.1.5 Ok
#RCPT TO: [email protected]
#250 2.1.5 Ok
#421 4.4.2 DOMAIN.com.local Error: timeout exceeded
# append to CSV file as soon as response comes
# date,host,port,domain,mail_from,response_time,response
# save to CSV
ALERT("done")
def recv_data(s):
full_msg = ""
while True:
try:
msg = s.recv(4096)
full_msg = full_msg + msg
except socket.timeout, e:
#print "[!] socket timed out!"
#sys.exit(1)
return full_msg
except socket.error, e:
print "[!] unknown socket error!"
sys.exit(1)
else:
if len(msg) == 0: return full_msg
else: ALERT(msg)
def snooze(delay,jitter):
s = randint(delay-(delay*jitter),delay+(delay*jitter))
if args['debug']: ALERT("Snoozing for %sms" % s, ALERT.INFO)
time.sleep(s/1000)
def print_time():
ALERT(datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S'))
class ALERT(object):
def __init__(self, message, level=0, spacing=0, ansi=True):
# default to ansi alerting, if it's detected as windows platform then disable
if platform.system() is "Windows": ansi = False
good = '[+]'
bad = '[-]'
normal = '[*]'
severe = '[!]'
info = '[>]'
space = ''
for i in range(spacing):
space += ' '
if ansi == True:
if level == ALERT.GOOD: print("%s%s%s%s" % ('\033[1;32m',good,"\033[0;0m",space)),
elif level == ALERT.BAD: print("%s%s%s%s" % ('\033[1;31m',bad,"\033[0;0m",space)),
elif level == ALERT.SEVERE: print("%s%s%s%s" % ('\033[1;31m',severe,"\033[0;0m",space)),
elif level == ALERT.INFO: print("%s%s%s%s" % ('\033[1;33m',info,"\033[0;0m",space)),
else: print("%s%s%s%s" % ('\033[1;34m',normal,"\033[0;0m",space)),
else:
if level == ALERT.GOOD: print('%s%s' % good,space),
elif level == ALERT.BAD: print('%s%s' % bad,space),
elif level == ALERT.SEVERE: print('%s%s' % (severe,space)),
elif level == ALERT.INFO: print('%s%s' % (info,space)),
else: print('%s%s' % normal,space),
print message
@staticmethod
@property
def BAD(self): return -1
@staticmethod
@property
def NORMAL(self): return 0
@staticmethod
@property
def GOOD(self): return 1
@staticmethod
@property
def SEVERE(self): return -2
@staticmethod
@property
def INFO(self): return 2
if __name__ == '__main__':
print hello_msg
main()