├── README.md └── smtp-user-enum.py /README.md: -------------------------------------------------------------------------------- 1 | recon-smtp 2 | ========== 3 | 4 | smtp-user-enum.pl ported into a recon-ng module. 5 | -------------------------------------------------------------------------------- /smtp-user-enum.py: -------------------------------------------------------------------------------- 1 | # packages required for framework integration 2 | import module 3 | # module specific packages 4 | import socket 5 | import re 6 | import time 7 | 8 | # Module is a simple port of smtp-user-enum.pl from 9 | # http://pentestmonkey.net/tools/user-enumeration/smtp-user-enum 10 | 11 | class Module(module.Module): 12 | 13 | def __init__(self, params): 14 | module.Module.__init__(self, params) 15 | # module options 16 | self.register_option('rhost', None, 'yes', 'Remote mail server to query.') 17 | self.register_option('rport', 25, 'yes', 'Remote port of listening mail server.') 18 | self.register_option('method', 'VRFY', 'yes', 'Method of verification to use: VRFY, EXPN, or RCPT.') 19 | self.register_option('delay', 3000, 'yes', 'Delay (ms) between sequential requests to the mail server.') 20 | self.register_option('clobber', False, 'yes', 'Drop existing entries in \'verified\' table.') 21 | self.register_option('from', None, 'no', 'MAIL FROM address to use with RCPT verification method.') 22 | self.info = { 23 | 'Name': 'SMTP User Enumerator', 24 | 'Author': 'Cody Wass', 25 | 'Description': 'Queries a mail server using addresses in the \'contacts\' database to determine which are valid. Results are currently stored in a newly created \'verified\' table.', 26 | } 27 | 28 | def module_run(self): 29 | self.output("Starting smtp-user-enum module...") 30 | self.output("---------------------------------------------------------") 31 | self.output("| Scan Results |") 32 | self.output("---------------------------------------------------------") 33 | 34 | if not self.options['from'] and self.options['method'] == 'RCPT': 35 | self.alert('Using RCPT with no \'from\' address specified - using user@example.com.') 36 | self.options['from'] = 'user@example.com' 37 | 38 | # Prepare table to hold results 39 | if self.options['clobber']: 40 | self.query("DROP TABLE IF EXISTS verified") 41 | self.query("CREATE TABLE verified (email TEXT)") 42 | else: 43 | self.query("CREATE TABLE IF NOT EXISTS verified (email TEXT)") 44 | 45 | # Grab email addresses from the database 46 | email_list = self.query('SELECT email FROM contacts WHERE email IS NOT NULL') 47 | 48 | # Make sure we actually got results 49 | if len(email_list) == 0: 50 | self.error('No email addresses found in table') 51 | return 52 | 53 | # Step through entries in our result set 54 | for entry in email_list: 55 | self.verify_email(entry[0]) 56 | time.sleep(self.options['delay'] / 1000) 57 | 58 | self.output("---------------------------------------------------------") 59 | self.output("| Scan Finished |") 60 | self.output("---------------------------------------------------------") 61 | 62 | 63 | def verify_email(self, email): 64 | 65 | # Make sure we successfully create the socket for SMTP communication. 66 | # Would wrap in try, but framework catches timeout/socket errors. 67 | server_conn = socket.create_connection([self.options['rhost'], self.options['rport']]) 68 | # Read server's 220 ready. 69 | data = server_conn.recv(1024) 70 | self.verbose('Server says: ' + data) 71 | 72 | if (data[:3] == '220'): 73 | # server responded with '220 go ahead' 74 | 75 | if (self.options['method'] == 'VRFY'): 76 | # Send our HELO. 77 | server_conn.send('HELO\r\n') 78 | data = server_conn.recv(1024) 79 | # Send VRFY and grab response. 80 | self.verbose('Trying address: ' + email + '...') 81 | server_conn.send('VRFY ' + email + '\r\n') 82 | 83 | elif (self.options['method'] == 'EXPN'): 84 | # Send our HELO. 85 | server_conn.send('HELO\r\n') 86 | data = server_conn.recv(1024) 87 | # Send EXPN and grab response. 88 | self.verbose('Trying address: ' + email + '...') 89 | server_conn.send('EXPN ' + email + '\r\n') 90 | 91 | elif (self.options['method'] == 'RCPT'): 92 | # Send our HELO. 93 | server_conn.send('HELO\r\n') 94 | data = server_conn.recv(1024) 95 | server_conn.send('MAIL FROM:<' + self.options['from'] + '>\r\n') 96 | data = server_conn.recv(1024) 97 | self.verbose('Trying address: ' + email + '...') 98 | server_conn.send('RCPT TO:<' + email + '>\r\n') 99 | 100 | else: 101 | self.error('Unknown method: ' + self.options['method'] + ' in use.') 102 | 103 | 104 | # Grab server's response. 105 | data = server_conn.recv(1024) 106 | 107 | # Check server response for match. 108 | if (re.search(r'2\d\d', data)): 109 | # We got a 2xx response, should mean user exists. 110 | self.output(email + ' exists!') 111 | self.verbose('Server reponse: ' + data) 112 | 113 | # Add email to verified-contacts table. 114 | self.query('INSERT INTO verified VALUES (?)', (email,)) 115 | 116 | elif (re.search(r'5\d\d', data)): 117 | # 5xx response, user does not exist. 118 | self.verbose(email + ' does not exist.') 119 | self.verbose('Server response: ' + data) 120 | 121 | else: 122 | # Other (4xx or similar) response. Print out to user. 123 | self.output('Unknown response: ' + data) 124 | 125 | # Reset SMTP connection in preparation for another loop. 126 | server_conn.send('RSET\r\n') 127 | data = server_conn.recv(1024) 128 | 129 | # Blow away socket for next connection. 130 | server_conn.shutdown(socket.SHUT_RDWR) 131 | server_conn.close() 132 | return 133 | 134 | else: 135 | self.error('ERROR: Server didn\'t respond with \'220\'.') 136 | self.error('ERROR: Response was: ' + data) 137 | 138 | server_conn.shutdown(socket.SHUT_RDWR) 139 | server_conn.close() 140 | return 141 | --------------------------------------------------------------------------------