Commits

Tim Tomes  committed 382c698

added a debug global option to the framework to enable tracebacks for all errors and http protocol debugging, reorganized the module tree, fixed a minor bug that effected all pwnedlist modules, moved the 'show keys' command to 'keys list', added a list reporting module for creating text lists of data stored in the database, and made minor grammar corrections.

  • Participants
  • Parent commits 60d2c77

Comments (0)

Files changed (111)

File core/framework.py

         try:
             return self.keys[key_name]
         except KeyError:
-            raise FrameworkException('API key \'%s\' not found. Add API keys with the \'key add\' command.' % (key_name))
+            raise FrameworkException('API key \'%s\' not found. Add API keys with the \'keys add\' command.' % (key_name))
 
     def add_key(self, name, value):
         self.keys[name] = value
         socket.setdefaulttimeout(timeout)
         
         # set handlers
-        handlers = [] #urllib2.HTTPHandler(debuglevel=1)
+        # declare handlers list according to debug setting
+        handlers = [urllib2.HTTPHandler(debuglevel=1), urllib2.HTTPSHandler(debuglevel=1)] if self.goptions['debug']['value'] else []
         # process redirect and add handler
         if redirect == False:
             handlers.append(NoRedirectHandler)
             req = urllib2.Request(url, headers=headers)
             req.get_method = lambda : 'HEAD'
         else:
-            raise Exception('Request method \'%s\' is not a supported method.' % (method))
+            raise FrameworkException('Request method \'%s\' is not a supported method.' % (method))
         try:
             resp = urllib2.urlopen(req)
         except urllib2.HTTPError as e:
         if params:
             params = params.split()
             arg = params.pop(0).lower()
-            if arg in ['add', 'update']:
+            if arg == 'list':
+                self.display_keys()
+                return
+            elif arg in ['add', 'update']:
                 if len(params) == 2:
                     self.add_key(params[0], params[1])
                     self.output('Key \'%s\' added.' % (params[0]))
             elif arg == 'schema':
                 self.display_schema()
                 return
-            elif arg == 'keys':
-                self.display_keys()
-                return
             elif arg in [x[0] for x in self.query('SELECT name FROM sqlite_master WHERE type=\'table\'')]:
                 self.do_query('SELECT * FROM %s ORDER BY 1' % (arg))
                 return
         try:
             self.validate_options()
             self.module_run()
-        except Exception as e:
-            self.error(e.__str__())
         except KeyboardInterrupt:
             print ''
+        except Exception as e:
+            if self.goptions['debug']['value']:
+                print '%s%s' % (R, '-'*60)
+                traceback.print_exc()
+                print '%s%s' % ('-'*60, N)
+            else:
+                error = e.__str__()
+                if not re.search('[.,;!?]$', error):
+                    error += '.'
+                self.error(error.capitalize())
         else:
             self.query('INSERT OR REPLACE INTO dashboard (module, runs) VALUES (\'%(x)s\', COALESCE((SELECT runs FROM dashboard WHERE module=\'%(x)s\')+1, 1))' % {'x': self.modulename})
 
         self.display_options(None)
 
     def help_keys(self):
-        print 'Usage: keys [add|delete|update]'
+        print 'Usage: keys [list|add|delete|update]'
 
     def help_query(self):
         print 'Usage: query <sql>'
         print '%s%s' % (self.spacer, 'UPDATE table_name SET column1=value1, column2=value2,... WHERE some_column=some_value')
 
     def help_show(self):
-        print 'Usage: show [modules|options|dashboard|workspaces|schema|keys|<table>]'
+        print 'Usage: show [modules|options|dashboard|workspaces|schema|<table>]'
 
     def help_shell(self):
         print 'Usage: [shell|!] <command>'
 
     def complete_keys(self, text, line, *ignored):
         args = line.split()
-        options = ['add', 'delete', 'update']
+        options = ['list', 'add', 'delete', 'update']
         if len(args) > 1 and args[1].lower() in options:
             return [x for x in self.keys.keys() if x.startswith(text)]
         return [x for x in options if x.startswith(text)]
             if len(args) > 2: return [x for x in __builtin__.loaded_modules if x.startswith(args[2])]
             else: return [x for x in __builtin__.loaded_modules]
         tables = [x[0] for x in self.query('SELECT name FROM sqlite_master WHERE type=\'table\'')]
-        options = ['modules', 'options', 'workspaces', 'schema', 'keys']
+        options = ['modules', 'options', 'workspaces', 'schema']
         options.extend(tables)
         return [x for x in options if x.startswith(text)]
 

File core/pwnedlist.py

     payload['ts'] = timestamp
     payload['key'] = key
     msg = '%s%s%s%s' % (key, timestamp, method, secret)
-    hm = hmac.new(secret, msg, hashlib.sha1)
+    hm = hmac.new(str(secret), msg, hashlib.sha1)
     payload['hmac'] = hm.hexdigest() 
     return payload
 

File modules/experimental/rce.py

         self.register_option('mark_start', None, 'no', 'string to match page content preceding the command output')
         self.register_option('mark_end', None, 'no', 'string to match page content following the command output')
         self.info = {
-                     'Name': 'Remote Commnd Execution Shell Interface',
+                     'Name': 'Remote Command Execution Shell Interface',
                      'Author': 'Tim Tomes (@LaNMaSteR53)',
                      'Description': 'Provides a shell interface for remote command execution flaws in web applications.',
                      'Comments': []

File modules/recon/contacts/enum/http/namechk.py

-import framework
-# unique to module
-import re
-from hashlib import sha1
-from hmac import new as hmac
-import socket
-
-class Module(framework.module):
-
-    def __init__(self, params):
-        framework.module.__init__(self, params)
-        self.register_option('username', None, 'yes', 'username to validate')
-        self.info = {
-                     'Name': 'NameChk.com Username Validator',
-                     'Author': 'Tim Tomes (@LaNMaSteR53) and thrapt (thrapt@gmail.com)',
-                     'Description': 'Leverages NameChk.com to validate the existance of usernames at specific web sites.',
-                     'Comments': [
-                                  'Note: The global socket_timeout may need to be increased to support slower sites.']
-                     }
-
-    def module_run(self):
-        username = self.options['username']['value']
-
-        # retrive list of sites
-        url = 'http://namechk.com/Content/sites.min.js'
-        resp = self.request(url)
-        
-        # extract sites info from the js file
-        pattern = 'n:"(.+?)",r:\d+,i:(\d+)'
-        sites = re.findall(pattern, resp.text)
-
-        # output table of sites info
-        if self.goptions['verbose']['value']:
-            tdata = [['Code', 'Name']]
-            for site in sites:
-                tdata.append([site[1], site[0]])
-            self.table(tdata, True)
-
-        # retrive statuses
-        key = "shhh it's :] super secret"
-        url = 'http://namechk.com/check'
-
-        # this header is required
-        headers = {'X-Requested-With': 'XMLHttpRequest'}
-
-        status_dict = {
-                       '1': 'Available',
-                       '2': 'User Exists!',
-                       '3': 'Unknown',
-                       '4': 'Indefinite'
-                       }
-
-        for site in sites:
-            i = site[1]
-            name = site[0]
-            # build the hmac payload
-            message = "POST&%s?i=%s&u=%s" % (url, i, username)
-            b64_hmac_sha1 = '%s' % hmac(key, message, sha1).digest().encode('base64')[:-1]
-            payload = {'i': i, 'u': username, 'o_0': b64_hmac_sha1}
-            # build and send the request
-            try: resp = self.request(url, method='POST', headers=headers, payload=payload)
-            except KeyboardInterrupt:
-                raise KeyboardInterrupt
-            except Exception as e:
-                self.error('%s: %s' % (name, e.__str__()))
-                continue
-            x = resp.text
-            if int(x) > 0:
-                status = status_dict[x]
-                if int(x) == 2:
-                    self.alert('%s: %s' % (name, status))
-                else:
-                    self.verbose('%s: %s' % (name, status))
-            else:
-                self.error('%s: %s' % (name, 'Error'))

File modules/recon/contacts/enum/http/pwnedlist.py

-import framework
-# unique to module
-import hashlib
-import re
-
-class Module(framework.module):
-
-    def __init__(self, params):
-        framework.module.__init__(self, params)
-        self.register_option('source', 'db', 'yes', 'source of accounts for module input (see \'info\' for options)')
-        self.info = {
-                     'Name': 'PwnedList Validator',
-                     'Author': 'Tim Tomes (@LaNMaSteR53)',
-                     'Description': 'Leverages PwnedList.com to determine if email addresses are associated with leaked credentials and updates the \'creds\' table of the database with the positive results.',
-                     'Comments': [
-                                  'Source options: [ db | email.address@domain.com | ./path/to/file | query <sql> ]'
-                                  ]
-                     }
-
-    def module_run(self):
-        accounts = self.get_source(self.options['source']['value'], 'SELECT DISTINCT email FROM contacts WHERE email IS NOT NULL ORDER BY email')
-
-        # retrieve status
-        cnt = 0
-        pwned = 0
-        for account in accounts:
-            account = "".join([i for i in account if ord(i) in range(32, 126)])
-            status = None
-            url = 'https://www.pwnedlist.com/query'
-            payload = {'inputEmail': hashlib.sha512(account).hexdigest(), 'form.submitted': ''}
-            resp = self.request(url, payload=payload, method='POST', redirect=False)
-            content = resp.text
-            if '<h3>Nope,' in content:
-                status = 'safe'
-                self.verbose('%s => %s.' % (account, status))
-            elif '<h3>Yes.</h3>' in content:
-                status = 'pwned'
-                qty  = re.search('<li>We have found this account (\d+?) times since', content).group(1)
-                last = re.search('years ago, on (.+?).</li>', content).group(1)
-                self.alert('%s => %s! Seen %s times as recent as %s.' % (account, status, qty, last))
-                pwned += self.add_cred(account)
-            else:
-                self.error('%s => Response not understood.' % (account))
-                continue
-            cnt += 1
-        self.output('%d/%d targets pwned.' % (pwned, cnt))

File modules/recon/contacts/enum/http/should_change_password.py

-import framework
-# unique to module
-
-class Module(framework.module):
-
-    def __init__(self, params):
-        framework.module.__init__(self, params)
-        self.register_option('source', 'db', 'yes', 'source of accounts for module input (see \'info\' for options)')
-        self.info = {
-                     'Name': 'Should I Change My Password Breach Check',
-                     'Author': 'Dan Woodruff (@dewoodruff)',
-                     'Description': 'Leverages ShouldIChangeMyPassword.com to determine if email addresses are associated with leaked credentials and updates the \'creds\' table of the database with the positive results.',
-                     'Comments': [
-                                  'Source options: [ db | email.address@domain.com | ./path/to/file | query <sql> ]',
-                                  ]
-                     }
-
-    def module_run(self):
-        emails = self.get_source(self.options['source']['value'], 'SELECT DISTINCT email FROM contacts WHERE email IS NOT NULL ORDER BY email')
-        
-        total = 0
-        emailsFound = 0
-        # lookup each hash
-        url = 'https://shouldichangemypassword.com/check-single.php'
-        for emailstr in emails:
-            # build the request
-            payload = {'email': emailstr}
-            resp = self.request(url, method="POST", payload=payload)
-            # retrieve the json response
-            jsonobj = resp.json
-            numFound = jsonobj['num']
-            total += 1
-            # if any breaches were found, show the number found and the last found date
-            if numFound != "0":
-                last = jsonobj['last']
-                self.alert('%s => breached! Seen %s times as recent as %s.' % (emailstr, numFound, last))
-                emailsFound += 1
-            else:
-                self.verbose('%s => safe.' % (emailstr))
-        self.output('%d/%d targets breached.' % (emailsFound, total))

File modules/recon/contacts/enum/http/web/namechk.py

+import framework
+# unique to module
+import re
+from hashlib import sha1
+from hmac import new as hmac
+import socket
+
+class Module(framework.module):
+
+    def __init__(self, params):
+        framework.module.__init__(self, params)
+        self.register_option('username', None, 'yes', 'username to validate')
+        self.info = {
+                     'Name': 'NameChk.com Username Validator',
+                     'Author': 'Tim Tomes (@LaNMaSteR53) and thrapt (thrapt@gmail.com)',
+                     'Description': 'Leverages NameChk.com to validate the existance of usernames at specific web sites.',
+                     'Comments': [
+                                  'Note: The global socket_timeout may need to be increased to support slower sites.']
+                     }
+
+    def module_run(self):
+        username = self.options['username']['value']
+
+        # retrive list of sites
+        url = 'http://namechk.com/Content/sites.min.js'
+        resp = self.request(url)
+        
+        # extract sites info from the js file
+        pattern = 'n:"(.+?)",r:\d+,i:(\d+)'
+        sites = re.findall(pattern, resp.text)
+
+        # output table of sites info
+        if self.goptions['verbose']['value']:
+            tdata = [['Code', 'Name']]
+            for site in sites:
+                tdata.append([site[1], site[0]])
+            self.table(tdata, True)
+
+        # retrive statuses
+        key = "shhh it's :] super secret"
+        url = 'http://namechk.com/check'
+
+        # this header is required
+        headers = {'X-Requested-With': 'XMLHttpRequest'}
+
+        status_dict = {
+                       '1': 'Available',
+                       '2': 'User Exists!',
+                       '3': 'Unknown',
+                       '4': 'Indefinite'
+                       }
+
+        for site in sites:
+            i = site[1]
+            name = site[0]
+            # build the hmac payload
+            message = "POST&%s?i=%s&u=%s" % (url, i, username)
+            b64_hmac_sha1 = '%s' % hmac(key, message, sha1).digest().encode('base64')[:-1]
+            payload = {'i': i, 'u': username, 'o_0': b64_hmac_sha1}
+            # build and send the request
+            try: resp = self.request(url, method='POST', headers=headers, payload=payload)
+            except KeyboardInterrupt:
+                raise KeyboardInterrupt
+            except Exception as e:
+                self.error('%s: %s' % (name, e.__str__()))
+                continue
+            x = resp.text
+            if int(x) > 0:
+                status = status_dict[x]
+                if int(x) == 2:
+                    self.alert('%s: %s' % (name, status))
+                else:
+                    self.verbose('%s: %s' % (name, status))
+            else:
+                self.error('%s: %s' % (name, 'Error'))

File modules/recon/contacts/enum/http/web/pwnedlist.py

+import framework
+# unique to module
+import hashlib
+import re
+
+class Module(framework.module):
+
+    def __init__(self, params):
+        framework.module.__init__(self, params)
+        self.register_option('source', 'db', 'yes', 'source of accounts for module input (see \'info\' for options)')
+        self.info = {
+                     'Name': 'PwnedList Validator',
+                     'Author': 'Tim Tomes (@LaNMaSteR53)',
+                     'Description': 'Leverages PwnedList.com to determine if email addresses are associated with leaked credentials and updates the \'creds\' table of the database with the positive results.',
+                     'Comments': [
+                                  'Source options: [ db | email.address@domain.com | ./path/to/file | query <sql> ]'
+                                  ]
+                     }
+
+    def module_run(self):
+        accounts = self.get_source(self.options['source']['value'], 'SELECT DISTINCT email FROM contacts WHERE email IS NOT NULL ORDER BY email')
+
+        # retrieve status
+        cnt = 0
+        pwned = 0
+        for account in accounts:
+            account = "".join([i for i in account if ord(i) in range(32, 126)])
+            status = None
+            url = 'https://www.pwnedlist.com/query'
+            payload = {'inputEmail': hashlib.sha512(account).hexdigest(), 'form.submitted': ''}
+            resp = self.request(url, payload=payload, method='POST', redirect=False)
+            content = resp.text
+            if '<h3>Nope,' in content:
+                status = 'safe'
+                self.verbose('%s => %s.' % (account, status))
+            elif '<h3>Yes.</h3>' in content:
+                status = 'pwned'
+                qty  = re.search('<li>We have found this account (\d+?) times since', content).group(1)
+                last = re.search('ago, on (.+?).</li>', content).group(1)
+                self.alert('%s => %s! Seen %s times as recent as %s.' % (account, status, qty, last))
+                pwned += self.add_cred(account)
+            else:
+                self.error('%s => Response not understood.' % (account))
+                continue
+            cnt += 1
+        self.output('%d/%d targets pwned.' % (pwned, cnt))

File modules/recon/contacts/enum/http/web/should_change_password.py

+import framework
+# unique to module
+
+class Module(framework.module):
+
+    def __init__(self, params):
+        framework.module.__init__(self, params)
+        self.register_option('source', 'db', 'yes', 'source of accounts for module input (see \'info\' for options)')
+        self.info = {
+                     'Name': 'Should I Change My Password Breach Check',
+                     'Author': 'Dan Woodruff (@dewoodruff)',
+                     'Description': 'Leverages ShouldIChangeMyPassword.com to determine if email addresses are associated with leaked credentials and updates the \'creds\' table of the database with the positive results.',
+                     'Comments': [
+                                  'Source options: [ db | email.address@domain.com | ./path/to/file | query <sql> ]',
+                                  ]
+                     }
+
+    def module_run(self):
+        emails = self.get_source(self.options['source']['value'], 'SELECT DISTINCT email FROM contacts WHERE email IS NOT NULL ORDER BY email')
+        
+        total = 0
+        emailsFound = 0
+        # lookup each hash
+        url = 'https://shouldichangemypassword.com/check-single.php'
+        for emailstr in emails:
+            # build the request
+            payload = {'email': emailstr}
+            resp = self.request(url, method="POST", payload=payload)
+            # retrieve the json response
+            jsonobj = resp.json
+            numFound = jsonobj['num']
+            total += 1
+            # if any breaches were found, show the number found and the last found date
+            if numFound != "0":
+                last = jsonobj['last']
+                self.alert('%s => breached! Seen %s times as recent as %s.' % (emailstr, numFound, last))
+                emailsFound += 1
+            else:
+                self.verbose('%s => safe.' % (emailstr))
+        self.output('%d/%d targets breached.' % (emailsFound, total))

File modules/recon/contacts/gather/http/api/jigsaw/point_usage.py

+import framework
+# unique to module
+
+class Module(framework.module):
+
+    def __init__(self, params):
+        framework.module.__init__(self, params)
+        self.register_option('username', None, 'yes', 'jigsaw account username')
+        self.register_option('password', None, 'yes', 'jigsaw account password')
+        self.info = {
+                     'Name': 'Jigsaw - Point Usage Statistics Fetcher',
+                     'Author': 'Tim Tomes (@LaNMaSteR53)',
+                     'Description': 'Queries the Jigsaw API for the point usage statistics of the given account.',
+                     'Comments': []
+                     }
+
+    def module_run(self):
+        username = self.options['username']['value']
+        password = self.options['password']['value']
+        key = self.get_key('jigsaw_api')
+
+        url = 'https://www.jigsaw.com/rest/user.json'
+        payload = {'token': key, 'username': username, 'password': password}
+        resp = self.request(url, payload=payload, redirect=False)
+        if resp.json: jsonobj = resp.json
+        else:
+            self.error('Invalid JSON response.\n%s' % (resp.text))
+            return
+
+        # handle output
+        self.output('%d Jigsaw points remaining.' % (jsonobj['points']))

File modules/recon/contacts/gather/http/api/jigsaw/purchase_contact.py

+import framework
+# unique to module
+import urllib
+import time
+
+class Module(framework.module):
+
+    def __init__(self, params):
+        framework.module.__init__(self, params)
+        self.register_option('username', None, 'yes', 'jigsaw account username')
+        self.register_option('password', None, 'yes', 'jigsaw account password')
+        self.register_option('contact', None, 'yes', 'jigsaw contact id')
+        self.info = {
+                     'Name': 'Jigsaw - Single Contact Retriever',
+                     'Author': 'Tim Tomes (@LaNMaSteR53)',
+                     'Description': 'Retrieves a single complete contact from the Jigsaw.com API using points from the given account.',
+                     'Comments': [
+                                  'Account Point Cost: 5 points per request.',
+                                  'This module is typically used to validate email address naming conventions and gather alternative social engineering information.'
+                                  ]
+                     }
+
+    def module_run(self):
+        username = self.options['username']['value']
+        password = self.options['password']['value']
+        key = self.get_key('jigsaw_api')
+
+        # point guard
+        if not self.api_guard(5): return
+
+        url = 'https://www.jigsaw.com/rest/contacts/%s.json' % (self.options['contact']['value'])
+        payload = {'token': key, 'username': username, 'password': password, 'purchaseFlag': 'true'}
+        resp = self.request(url, payload=payload, redirect=False)
+        if resp.json: jsonobj = resp.json
+        else:
+            self.error('Invalid JSON response.\n%s' % (resp.text))
+            return
+
+        # handle output
+        contacts = jsonobj['contacts']
+        for contact in contacts:
+            tdata = []
+            for key in contact:
+                tdata.append((key.title(), contact[key]))
+            self.table(tdata)

File modules/recon/contacts/gather/http/api/jigsaw/search_contacts.py

+import framework
+# unique to module
+import urllib
+import time
+
+class Module(framework.module):
+
+    def __init__(self, params):
+        framework.module.__init__(self, params)
+        self.register_option('company', self.goptions['company']['value'], 'yes', self.goptions['company']['desc'])
+        self.register_option('keywords', '', 'no', 'additional keywords to identify company')
+        self.info = {
+                     'Name': 'Jigsaw Contact Enumerator',
+                     'Author': 'Tim Tomes (@LaNMaSteR53)',
+                     'Description': 'Harvests contacts from the Jigsaw.com API and updates the \'contacts\' table of the database with the results.',
+                     'Comments': []
+                     }
+
+    def module_run(self):
+        self.api_key = self.get_key('jigsaw_api')
+        company_id = self.get_company_id()
+        if company_id:
+            self.get_contacts(company_id)
+
+    def get_company_id(self):
+        self.output('Gathering Company IDs...')
+        all_companies = []
+        cnt = 0
+        size = 50
+        params = '%s %s' % (self.options['company']['value'], self.options['keywords']['value'])
+        url = 'https://www.jigsaw.com/rest/searchCompany.json'
+        while True:
+            payload = {'token': self.api_key, 'name': params, 'offset': cnt, 'pageSize': size}
+            self.verbose('Query: %s?%s' % (url, urllib.urlencode(payload)))
+            resp = self.request(url, payload=payload, redirect=False)
+            jsonobj = resp.json
+            if jsonobj['totalHits'] == 0:
+                self.output('No Company Matches Found.')
+                return
+            else:
+                companies = jsonobj['companies']
+                for company in companies:
+                    if company['activeContacts'] > 0:
+                        location = '%s, %s, %s' % (company['city'], company['state'], company['country'])
+                        all_companies.append((company['companyId'], company['name'], company['activeContacts'], location))
+                cnt += size
+                if cnt > jsonobj['totalHits']: break
+                # jigsaw rate limits requests per second to the api
+                time.sleep(.25)
+        if len(all_companies) == 1:
+            company_id = all_companies[0][0]
+            company_name = all_companies[0][1]
+            contact_cnt = all_companies[0][2]
+            self.output('Unique Company Match Found: [%s - %s (%s contacts)]' % (company_name, company_id, contact_cnt))
+            return company_id
+        id_len = len(max([str(x[0]) for x in all_companies], key=len))
+        for company in all_companies:
+            self.output('[%s] %s - %s (%s contacts)' % (str(company[0]).ljust(id_len), company[1], company[3], company[2]))
+        company_id = raw_input('Enter Company ID from list [%s - %s]: ' % (all_companies[0][1], all_companies[0][0]))
+        if not company_id: company_id = all_companies[0][0]
+        return company_id
+
+    def get_contacts(self, company_id):
+        self.output('Gathering Contacts...')
+        tot = 0
+        cnt = 0
+        new = 0
+        size = 100
+        url = 'https://www.jigsaw.com/rest/searchContact.json'
+        while True:
+            payload = {'token': self.api_key, 'companyId': company_id, 'offset': cnt, 'pageSize': size}
+            resp = self.request(url, payload=payload, redirect=False)
+            jsonobj = resp.json
+            for contact in jsonobj['contacts']:
+                contact_id = contact['contactId']
+                fname = contact['firstname']
+                lname = contact['lastname']
+                title = self.unescape(contact['title'])
+                city = contact['city'].title()
+                state = contact['state'].upper()
+                region = []
+                for item in [city, state]:
+                    if item: region.append(item)
+                region = ', '.join(region)
+                country = contact['country'].title()
+                self.output('[%s] %s %s - %s (%s - %s)' % (contact_id, fname, lname, title, region, country))
+                new += self.add_contact(fname=fname, lname=lname, title=title, region=region, country=country)
+                tot += 1
+            cnt += size
+            if cnt > jsonobj['totalHits']: break
+            # jigsaw rate limits requests per second to the api
+            time.sleep(.25)
+        self.output('%d total contacts found.' % (tot))
+        if new: self.alert('%d NEW contacts found!' % (new))

File modules/recon/contacts/gather/http/api/linkedin_auth.py

+import framework
+# unique to module
+import oauth2 as oauth
+import httplib2
+import urlparse
+import webbrowser
+import urllib
+import json
+import re
+
+class Module(framework.module):
+
+    def __init__(self, params):
+        framework.module.__init__(self, params)
+        self.register_option('company', self.goptions['company']['value'], 'yes', self.goptions['company']['desc'])
+        self.info = {
+                     'Name': 'LinkedIn Authenticated Contact Enumerator',
+                     'Author': 'Tim Tomes (@LaNMaSteR53)',
+                     'Description': 'Harvests contacts from the LinkedIn.com API using an authenticated connections network and updates the \'contacts\' table of the database with the results.',
+                     'Comments': []
+                     }
+
+    def module_run(self):
+        consumer_key = self.get_key('linkedin_api')
+        consumer_secret = self.get_key('linkedin_secret')
+        # Use API key and secret to instantiate consumer object
+        self.consumer = oauth.Consumer(consumer_key, consumer_secret)
+        self.access_token = {}
+        try: self.access_token = {'oauth_token': self.get_key('linkedin_token'),'oauth_token_secret': self.get_key('linkedin_token_secret')}
+        except framework.FrameworkException: pass
+        if not self.access_token: self.get_access_tokens()
+        if self.access_token: self.get_contacts()
+
+    def get_access_tokens(self):
+        client = oauth.Client(self.consumer)
+        request_token_url = 'https://api.linkedin.com/uas/oauth/requestToken?scope=r_basicprofile+r_network'
+        resp, content = client.request(request_token_url, "POST")
+        if resp['status'] != '200':
+            raise Exception(self.error('Error: Invalid Response %s.' % resp['status']))
+        request_token = dict(urlparse.parse_qsl(content))
+        base_authorize_url = 'https://api.linkedin.com/uas/oauth/authorize'
+        authorize_url = "%s?oauth_token=%s" % (base_authorize_url, request_token['oauth_token'])
+        self.output('Go to the following link in your browser and enter the pin below:') 
+        self.output(authorize_url)
+        w = webbrowser.get()
+        w.open(authorize_url)
+        oauth_verifier = ''
+        oauth_verifier = raw_input('Enter PIN: ')
+        if not oauth_verifier: return None
+        access_token_url = 'https://api.linkedin.com/uas/oauth/accessToken'
+        token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret'])
+        token.set_verifier(oauth_verifier)
+        client = oauth.Client(self.consumer, token)
+        resp, content = client.request(access_token_url, "POST")
+        self.access_token = dict(urlparse.parse_qsl(content))
+        self.add_key('linkedin_token', self.access_token['oauth_token'])
+        self.add_key('linkedin_token_secret', self.access_token['oauth_token_secret'])
+    
+    def get_contacts(self):
+        if not hasattr(self, 'access_token'): return
+        # Use developer token and secret to instantiate access token object
+        token = oauth.Token(key=self.access_token['oauth_token'], secret=self.access_token['oauth_token_secret'])
+        client = oauth.Client(self.consumer, token)
+        count = 25
+        base_url = "http://api.linkedin.com/v1/people-search:(people:(id,first-name,last-name,headline,location:(name,country:(code))))?format=json&company-name=%s&current-company=true&count=%d" % (urllib.quote_plus(self.options['company']['value']), count)
+        url = base_url
+        cnt, tot = 0, 0
+        page = 1
+        while True:
+            resp, content = client.request(url)
+            jsonstr = content
+            try: jsonobj = json.loads(jsonstr)
+            except ValueError as e:
+                self.error(e.__str__())
+                continue
+            if resp['status'] == '401':
+                self.error('Access Token Needed or Expired.')
+                self.get_access_tokens()
+                self.get_contacts()
+                break
+            elif resp['status'] == '403':
+                self.error('Error accessing API: %s' % jsonobj['message'])
+                break
+            if not 'values' in jsonobj['people']: break
+            for contact in jsonobj['people']['values']:
+                if 'headline' in contact:
+                    fname = self.unescape(re.split('[\s]',contact['firstName'])[0])
+                    lname = self.unescape(re.split('[,;]',contact['lastName'])[0])
+                    title = self.unescape(contact['headline'])
+                    region = re.sub('(?:Greater\s|\sArea)', '', self.unescape(contact['location']['name']).title())
+                    country = self.unescape(contact['location']['country']['code']).upper()
+                    self.output('%s %s - %s (%s - %s)' % (fname, lname, title, region, country))
+                    tot += 1
+                    cnt += self.add_contact(fname=fname, lname=lname, title=title, region=region, country=country)
+            if not '_start' in jsonobj['people']: break
+            if jsonobj['people']['_start'] + jsonobj['people']['_count'] == jsonobj['people']['_total']: break
+            start = page * jsonobj['people']['_count']
+            url = '%s&start=%d' % (base_url, start)
+            page += 1
+        self.output('%d total contacts found.' % (tot))
+        if cnt: self.alert('%d NEW contacts found!' % (cnt))

File modules/recon/contacts/gather/http/api/twitter.py

+import framework
+# unique to module
+import urllib
+import re
+import sys
+
+class Module(framework.module):
+
+    def __init__(self, params):
+        framework.module.__init__(self, params)
+        self.register_option('handle', '@lanmaster53', 'yes', 'target twitter handle')
+        self.register_option('dtg', None, 'no', 'date-time group in the form YYYY-MM-DD')
+        self.info = {
+                     'Name': 'Twitter Handles',
+                     'Author': 'Robert Frost (@frosty_1313, frosty[at]unluckyfrosty.net)',
+                     'Description': 'Searches Twitter for users that mentioned, or were mentioned by, the given handle.',
+                     'Comments': [
+                                  'Twitter only saves tweets for 6-8 days at this time.'
+                                  ]
+                     }
+    def module_run(self):
+        self.handle_options()
+        header = ['Handle', 'Name', 'Time']
+        
+        self.tdata = []
+        # search for mentions tweeted by the given handle
+        self.output('Searching for users mentioned by the given handle.')
+        self.search_handle_tweets()
+        if self.tdata:
+            print ''
+            self.tdata.insert(0, header)
+            self.table(self.tdata, header=True)
+
+        self.tdata = []
+        # search for tweets mentioning the given handle
+        self.output('Searching for users who mentioned the given handle.')
+        self.search_handle_mentions()
+        if self.tdata:
+            print ''
+            self.tdata.insert(0, header)
+            self.table(self.tdata, header=True)
+
+    def handle_options(self):
+        '''
+        Method built to do quick and dirty parsing of options supplied by the user.
+        Sets two properties of this class instance, self.handle and self.dtg.
+        '''
+        # handle
+        handle = self.options['handle']['value']
+        self.handle = handle if not handle.startswith('@') else handle[1:]
+        # dtg
+        dtg = self.options['dtg']['value']
+        if not dtg:
+            dtg = '2011-01-01'
+        elif not re.match(r'\d\d\d\d-\d\d-\d\d', dtg):
+            dtg = '2011-01-01'
+            self.output('DTG should be in the format: YYYY-MM-DD. Using the default value of \'%s\'.' % (dtg))
+        self.dtg = dtg
+
+    def get_user_info(self, handle, time):
+        '''
+        Queries twitter for information on a given twitter handle.
+        Twitter API returns ALOT of good info, database does not currently handle most of it.
+        '''
+        url = 'https://api.twitter.com/1/users/show.json'
+        payload = {'screen_name': handle, 'include_entities': 'true'}
+        resp = self.request(url, payload=payload)
+        
+        jsonobj = resp.json
+        for item in ['error', 'errors']:
+            if item in jsonobj:
+                self.error(jsonobj[item])
+                return
+
+        name = jsonobj['name']
+        if not [handle, name, time] in self.tdata: self.tdata.append([handle, name, time])
+
+    def search_api(self, query):
+        payload = {'q': query}
+        url = 'http://search.twitter.com/search.json'
+        resp = self.request(url, payload=payload)
+        
+        jsonobj = resp.json
+        for item in ['error', 'errors']:
+            if item in jsonobj:
+                self.error(jsonobj[item])
+                return
+
+        return jsonobj
+
+    def search_handle_tweets(self):
+        '''
+        Searches for mentions tweeted by the given handle.
+        Pulls usernames out and sends to get_user_info.
+        '''
+        resp = self.search_api('from:%s since:%s' % (self.handle, self.dtg))
+        if resp:
+            for tweet in resp['results']:
+                if 'to_user' in tweet:
+                    self.get_user_info(tweet['to_user'], tweet['created_at'])
+
+    def search_handle_mentions(self):
+        '''
+        Searches for tweets mentioning the given handle.
+        Checks using "to:" and "@" operands in the API.
+        Passes identified handles to get_user_info.
+        '''
+        for operand in ['to:', '@']:
+            resp = self.search_api('%s%s since:%s' % (operand, self.handle, self.dtg))
+            if resp:
+                for tweet in resp['results']:
+                    if 'to_user' in tweet:
+                        self.get_user_info(tweet['from_user'], tweet['created_at'])

File modules/recon/contacts/gather/http/api/whois_pocs.py

+import framework
+# unique to module
+from urlparse import urlparse
+
+class Module(framework.module):
+
+    def __init__(self, params):
+        framework.module.__init__(self, params)
+        self.register_option('domain', self.goptions['domain']['value'], 'yes', self.goptions['domain']['desc'])
+        self.register_option('store', False, 'yes', 'add discovered hosts to the database.')
+        self.info = {
+                     'Name': 'Whois POC Harvester',
+                     'Author': 'Tim Tomes (@LaNMaSteR53)',
+                     'Description': 'Uses the ARIN Whois RWS to harvest POC data from whois queries for the given domain.',
+                     'Comments': [
+                                  'Source options: [ db | <domain> | ./path/to/file | query <sql> ]',
+                                  ]
+                     }
+
+    def module_run(self):
+        domain = self.options['domain']['value']
+        store = self.options['store']['value']
+
+        headers = {'Accept': 'application/json'}
+        cnt = 0
+        new = 0
+        url = 'http://whois.arin.net/rest/pocs;domain=%s' % (domain)
+        self.verbose('URL: %s' % url)
+        resp = self.request(url, headers=headers)
+        if 'Your search did not yield any results.' in resp.text:
+            self.output('No contacts found.')
+            return
+        if not resp.json:
+            self.error('Invalid JSON response for \'%s\'.\n%s' % (domain, resp.text))
+            return
+        handles = [x['@handle'] for x in resp.json['pocs']['pocRef']]
+        for handle in handles:
+            url = 'http://whois.arin.net/rest/poc/%s' % (handle)
+            self.verbose('URL: %s' % url)
+            resp = self.request(url, headers=headers)
+            if resp.json: jsonobj = resp.json
+            else:
+                self.error('Invalid JSON response for \'%s\'.\n%s' % (handle, resp.text))
+                continue
+            poc = jsonobj['poc']
+            title = 'Whois contact'
+            city = poc['city']['$'].title()
+            country = poc['iso3166-1']['name']['$'].title()
+            fname = poc['firstName']['$'] if 'firstName' in poc else None
+            lname = poc['lastName']['$']
+            emails = poc['emails']['email'] if type(poc['emails']['email']) == list else [poc['emails']['email']]
+            email = emails[0]['$']
+            state = poc['iso3166-2']['$'].upper()
+            region = '%s, %s' % (city, state)
+            name = ' '.join([x for x in [fname, lname] if x])
+            self.output('%s (%s) - %s (%s - %s)' % (name, email, title, region, country))
+            if store: new += self.add_contact(fname=fname, lname=lname, email=email, title=title, region=region, country=country)
+            cnt += 1
+        self.output('%d total contacts found.' % (cnt))
+        if new: self.alert('%d NEW contacts found!' % (new))
+        

File modules/recon/contacts/gather/http/jigsaw/contacts_api.py

-import framework
-# unique to module
-import urllib
-import time
-
-class Module(framework.module):
-
-    def __init__(self, params):
-        framework.module.__init__(self, params)
-        self.register_option('company', self.goptions['company']['value'], 'yes', self.goptions['company']['desc'])
-        self.register_option('keywords', '', 'no', 'additional keywords to identify company')
-        self.info = {
-                     'Name': 'Jigsaw Contact Enumerator',
-                     'Author': 'Tim Tomes (@LaNMaSteR53)',
-                     'Description': 'Harvests contacts from the Jigsaw.com API and updates the \'contacts\' table of the database with the results.',
-                     'Comments': []
-                     }
-
-    def module_run(self):
-        self.api_key = self.get_key('jigsaw_api')
-        company_id = self.get_company_id()
-        if company_id:
-            self.get_contacts(company_id)
-
-    def get_company_id(self):
-        self.output('Gathering Company IDs...')
-        all_companies = []
-        cnt = 0
-        size = 50
-        params = '%s %s' % (self.options['company']['value'], self.options['keywords']['value'])
-        url = 'https://www.jigsaw.com/rest/searchCompany.json'
-        while True:
-            payload = {'token': self.api_key, 'name': params, 'offset': cnt, 'pageSize': size}
-            self.verbose('Query: %s?%s' % (url, urllib.urlencode(payload)))
-            resp = self.request(url, payload=payload, redirect=False)
-            jsonobj = resp.json
-            if jsonobj['totalHits'] == 0:
-                self.output('No Company Matches Found.')
-                return
-            else:
-                companies = jsonobj['companies']
-                for company in companies:
-                    if company['activeContacts'] > 0:
-                        location = '%s, %s, %s' % (company['city'], company['state'], company['country'])
-                        all_companies.append((company['companyId'], company['name'], company['activeContacts'], location))
-                cnt += size
-                if cnt > jsonobj['totalHits']: break
-                # jigsaw rate limits requests per second to the api
-                time.sleep(.25)
-        if len(all_companies) == 1:
-            company_id = all_companies[0][0]
-            company_name = all_companies[0][1]
-            contact_cnt = all_companies[0][2]
-            self.output('Unique Company Match Found: [%s - %s (%s contacts)]' % (company_name, company_id, contact_cnt))
-            return company_id
-        id_len = len(max([str(x[0]) for x in all_companies], key=len))
-        for company in all_companies:
-            self.output('[%s] %s - %s (%s contacts)' % (str(company[0]).ljust(id_len), company[1], company[3], company[2]))
-        company_id = raw_input('Enter Company ID from list [%s - %s]: ' % (all_companies[0][1], all_companies[0][0]))
-        if not company_id: company_id = all_companies[0][0]
-        return company_id
-
-    def get_contacts(self, company_id):
-        self.output('Gathering Contacts...')
-        tot = 0
-        cnt = 0
-        new = 0
-        size = 100
-        url = 'https://www.jigsaw.com/rest/searchContact.json'
-        while True:
-            payload = {'token': self.api_key, 'companyId': company_id, 'offset': cnt, 'pageSize': size}
-            resp = self.request(url, payload=payload, redirect=False)
-            jsonobj = resp.json
-            for contact in jsonobj['contacts']:
-                contact_id = contact['contactId']
-                fname = contact['firstname']
-                lname = contact['lastname']
-                title = self.unescape(contact['title'])
-                city = contact['city'].title()
-                state = contact['state'].upper()
-                region = []
-                for item in [city, state]:
-                    if item: region.append(item)
-                region = ', '.join(region)
-                country = contact['country'].title()
-                self.output('[%s] %s %s - %s (%s - %s)' % (contact_id, fname, lname, title, region, country))
-                new += self.add_contact(fname=fname, lname=lname, title=title, region=region, country=country)
-                tot += 1
-            cnt += size
-            if cnt > jsonobj['totalHits']: break
-            # jigsaw rate limits requests per second to the api
-            time.sleep(.25)
-        self.output('%d total contacts found.' % (tot))
-        if new: self.alert('%d NEW contacts found!' % (new))

File modules/recon/contacts/gather/http/jigsaw/contacts_web.py

-import framework
-# unique to module
-import urllib
-import re
-
-class Module(framework.module):
-
-    def __init__(self, params):
-        framework.module.__init__(self, params)
-        self.register_option('company', self.goptions['company']['value'], 'yes', self.goptions['company']['desc'])
-        self.register_option('keywords', '', 'no', 'additional keywords to identify company')
-        self.info = {
-                     'Name': 'Jigsaw Contact Enumerator',
-                     'Author': 'Tim Tomes (@LaNMaSteR53)',
-                     'Description': 'Harvests contacts from Jigsaw.com and updates the \'contacts\' table of the database with the results.',
-                     'Comments': []
-                     }
-
-    def module_run(self):
-        company_id = self.get_company_id()
-        if company_id:
-            contact_ids = self.get_contact_ids(company_id)
-            if contact_ids:
-                self.get_contacts(contact_ids)
-
-    def get_company_id(self):
-        self.output('Gathering Company IDs...')
-        company_name = self.options['company']['value']
-        all_companies = []
-        page_cnt = 1
-        params = '%s %s' % (company_name, self.options['keywords']['value'])
-        url = 'http://www.jigsaw.com/FreeTextSearchCompany.xhtml'
-        payload = {'opCode': 'search', 'freeText': params}
-        while True:
-            self.verbose('Query: %s?%s' % (url, urllib.urlencode(payload)))
-            resp = self.request(url, payload=payload, redirect=False)
-            if resp.status_code == 301:
-                header = resp.headers['location']
-                company_id = re.search('\/(\d+?)\/', resp.headers['location']).group(1)
-                self.output('Unique Company Match Found: %s' % company_id)
-                return company_id
-            content = resp.text
-            pattern = "href=./id(\d+?)/.+?>(.+?)<.+?\n.+?title='([\d,]+?)'"
-            companies = re.findall(pattern, content)
-            if not companies: break
-            for company in companies:
-                all_companies.append((company[0], company[1], company[2]))
-            page_cnt += 1
-            payload['rpage'] = str(page_cnt)
-        if len(all_companies) == 0:
-            self.output('No Company Matches Found.')
-            return False
-        else:
-            id_len = len(max([str(x[0]) for x in all_companies], key=len))
-            for company in all_companies:
-                self.output('[%s] %s (%s contacts)' % (str(company[0]).ljust(id_len), company[1], company[2]))
-            company_id = raw_input('Enter Company ID from list [%s - %s]: ' % (all_companies[0][1], all_companies[0][0]))
-            if not company_id: company_id = all_companies[0][0]
-            return company_id
-
-    def get_contact_ids(self, company_id):
-        self.output('Gathering Contact IDs for Company \'%s\'...' % (company_id))
-        page_cnt = 1
-        contact_ids = []
-        url = 'http://www.jigsaw.com/SearchContact.xhtml'
-        payload = {'companyId': company_id, 'opCode': 'showCompDir'}
-        while True:
-            payload['rpage'] = str(page_cnt)
-            self.verbose('Query: %s?%s' % (url, urllib.urlencode(payload)))
-            content = self.request(url, payload=payload).text
-            pattern = "showContact\('(\d+?)'\)"
-            contacts = re.findall(pattern, content)
-            if not contacts: break
-            contact_ids.extend(contacts)
-            page_cnt += 1
-        return contact_ids
-
-    def get_contacts(self, contact_ids):
-        self.output('Gathering Contacts...')
-        cnt, tot = 0, 0
-        for contact_id in contact_ids:
-            url = 'http://www.jigsaw.com/BC.xhtml'
-            payload = {'contactId': contact_id}
-            content = self.request(url, payload=payload).text
-            if 'Contact Not Found' in content: continue
-            fname = self.unescape(re.search('<span id="firstname">(.+?)</span>', content).group(1))
-            lname = self.unescape(re.search('<span id="lastname">(.+?)</span>', content).group(1))
-            title = self.unescape(re.search('<span id="title" title=".*?">(.*?)</span>', content).group(1))
-            city = self.unescape(re.search('<span id="city">(.+?)</span>', content).group(1)).title()
-            state = re.search('<span id="state">(.+?)</span>', content)
-            if state: state = self.unescape(state.group(1)).upper()
-            region = []
-            for item in [city, state]:
-                if item: region.append(item)
-            region = ', '.join(region)
-            country = self.unescape(re.search('<span id="country">(.+?)</span>', content).group(1)).title()
-            self.output('[%s] %s %s - %s (%s - %s)' % (contact_id, fname, lname, title, region, country))
-            tot += 1
-            cnt += self.add_contact(fname=fname, lname=lname, title=title, region=region, country=country)
-        self.output('%d total contacts found.' % (tot))
-        if cnt: self.alert('%d NEW contacts found!' % (cnt))

File modules/recon/contacts/gather/http/jigsaw/point_usage.py

-import framework
-# unique to module
-
-class Module(framework.module):
-
-    def __init__(self, params):
-        framework.module.__init__(self, params)
-        self.register_option('username', None, 'yes', 'jigsaw account username')
-        self.register_option('password', None, 'yes', 'jigsaw account password')
-        self.info = {
-                     'Name': 'Jigsaw - Point Usage Statistics Fetcher',
-                     'Author': 'Tim Tomes (@LaNMaSteR53)',
-                     'Description': 'Queries the Jigsaw API for the point usage statistics of the given account.',
-                     'Comments': []
-                     }
-
-    def module_run(self):
-        username = self.options['username']['value']
-        password = self.options['password']['value']
-        key = self.get_key('jigsaw_api')
-
-        url = 'https://www.jigsaw.com/rest/user.json'
-        payload = {'token': key, 'username': username, 'password': password}
-        resp = self.request(url, payload=payload, redirect=False)
-        if resp.json: jsonobj = resp.json
-        else:
-            self.error('Invalid JSON response.\n%s' % (resp.text))
-            return
-
-        # handle output
-        self.output('%d Jigsaw points remaining.' % (jsonobj['points']))

File modules/recon/contacts/gather/http/jigsaw/purchase_contact.py

-import framework
-# unique to module
-import urllib
-import time
-
-class Module(framework.module):
-
-    def __init__(self, params):
-        framework.module.__init__(self, params)
-        self.register_option('username', None, 'yes', 'jigsaw account username')
-        self.register_option('password', None, 'yes', 'jigsaw account password')
-        self.register_option('contact', None, 'yes', 'jigsaw contact id')
-        self.info = {
-                     'Name': 'Jigsaw - Single Contact Retriever',
-                     'Author': 'Tim Tomes (@LaNMaSteR53)',
-                     'Description': 'Retrieves a single complete contact from the Jigsaw.com API using points from the given account.',
-                     'Comments': [
-                                  'Account Point Cost: 5 points per request.',
-                                  'This module is typically used to validate email address naming conventions and gather alternative social engineering information.'
-                                  ]
-                     }
-
-    def module_run(self):
-        username = self.options['username']['value']
-        password = self.options['password']['value']
-        key = self.get_key('jigsaw_api')
-
-        # point guard
-        if not self.api_guard(5): return
-
-        url = 'https://www.jigsaw.com/rest/contacts/%s.json' % (self.options['contact']['value'])
-        payload = {'token': key, 'username': username, 'password': password, 'purchaseFlag': 'true'}
-        resp = self.request(url, payload=payload, redirect=False)
-        if resp.json: jsonobj = resp.json
-        else:
-            self.error('Invalid JSON response.\n%s' % (resp.text))
-            return
-
-        # handle output
-        contacts = jsonobj['contacts']
-        for contact in contacts:
-            tdata = []
-            for key in contact:
-                tdata.append((key.title(), contact[key]))
-            self.table(tdata)

File modules/recon/contacts/gather/http/linkedin_auth.py

-import framework
-# unique to module
-import oauth2 as oauth
-import httplib2
-import urlparse
-import webbrowser
-import urllib
-import json
-import re
-
-class Module(framework.module):
-
-    def __init__(self, params):
-        framework.module.__init__(self, params)
-        self.register_option('company', self.goptions['company']['value'], 'yes', self.goptions['company']['desc'])
-        self.info = {
-                     'Name': 'LinkedIn Authenticated Contact Enumerator',
-                     'Author': 'Tim Tomes (@LaNMaSteR53)',
-                     'Description': 'Harvests contacts from the LinkedIn.com API using an authenticated connections network and updates the \'contacts\' table of the database with the results.',
-                     'Comments': []
-                     }
-
-    def module_run(self):
-        consumer_key = self.get_key('linkedin_api')
-        consumer_secret = self.get_key('linkedin_secret')
-        # Use API key and secret to instantiate consumer object
-        self.consumer = oauth.Consumer(consumer_key, consumer_secret)
-        self.access_token = {}
-        try: self.access_token = {'oauth_token': self.get_key('linkedin_token'),'oauth_token_secret': self.get_key('linkedin_token_secret')}
-        except framework.FrameworkException: pass
-        if not self.access_token: self.get_access_tokens()
-        if self.access_token: self.get_contacts()
-
-    def get_access_tokens(self):
-        client = oauth.Client(self.consumer)
-        request_token_url = 'https://api.linkedin.com/uas/oauth/requestToken?scope=r_basicprofile+r_network'
-        resp, content = client.request(request_token_url, "POST")
-        if resp['status'] != '200':
-            raise Exception(self.error('Error: Invalid Response %s.' % resp['status']))
-        request_token = dict(urlparse.parse_qsl(content))
-        base_authorize_url = 'https://api.linkedin.com/uas/oauth/authorize'
-        authorize_url = "%s?oauth_token=%s" % (base_authorize_url, request_token['oauth_token'])
-        self.output('Go to the following link in your browser and enter the pin below:') 
-        self.output(authorize_url)
-        w = webbrowser.get()
-        w.open(authorize_url)
-        oauth_verifier = ''
-        oauth_verifier = raw_input('Enter PIN: ')
-        if not oauth_verifier: return None
-        access_token_url = 'https://api.linkedin.com/uas/oauth/accessToken'
-        token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret'])
-        token.set_verifier(oauth_verifier)
-        client = oauth.Client(self.consumer, token)
-        resp, content = client.request(access_token_url, "POST")
-        self.access_token = dict(urlparse.parse_qsl(content))
-        self.add_key('linkedin_token', self.access_token['oauth_token'])
-        self.add_key('linkedin_token_secret', self.access_token['oauth_token_secret'])
-    
-    def get_contacts(self):
-        if not hasattr(self, 'access_token'): return
-        # Use developer token and secret to instantiate access token object
-        token = oauth.Token(key=self.access_token['oauth_token'], secret=self.access_token['oauth_token_secret'])
-        client = oauth.Client(self.consumer, token)
-        count = 25
-        base_url = "http://api.linkedin.com/v1/people-search:(people:(id,first-name,last-name,headline,location:(name,country:(code))))?format=json&company-name=%s&current-company=true&count=%d" % (urllib.quote_plus(self.options['company']['value']), count)
-        url = base_url
-        cnt, tot = 0, 0
-        page = 1
-        while True:
-            resp, content = client.request(url)
-            jsonstr = content
-            try: jsonobj = json.loads(jsonstr)
-            except ValueError as e:
-                self.error(e.__str__())
-                continue
-            if resp['status'] == '401':
-                self.error('Access Token Needed or Expired.')
-                self.get_access_tokens()
-                self.get_contacts()
-                break
-            elif resp['status'] == '403':
-                self.error('Error accessing API: %s' % jsonobj['message'])
-                break
-            if not 'values' in jsonobj['people']: break
-            for contact in jsonobj['people']['values']:
-                if 'headline' in contact:
-                    fname = self.unescape(re.split('[\s]',contact['firstName'])[0])
-                    lname = self.unescape(re.split('[,;]',contact['lastName'])[0])
-                    title = self.unescape(contact['headline'])
-                    region = re.sub('(?:Greater\s|\sArea)', '', self.unescape(contact['location']['name']).title())
-                    country = self.unescape(contact['location']['country']['code']).upper()
-                    self.output('%s %s - %s (%s - %s)' % (fname, lname, title, region, country))
-                    tot += 1
-                    cnt += self.add_contact(fname=fname, lname=lname, title=title, region=region, country=country)
-            if not '_start' in jsonobj['people']: break
-            if jsonobj['people']['_start'] + jsonobj['people']['_count'] == jsonobj['people']['_total']: break
-            start = page * jsonobj['people']['_count']
-            url = '%s&start=%d' % (base_url, start)
-            page += 1
-        self.output('%d total contacts found.' % (tot))
-        if cnt: self.alert('%d NEW contacts found!' % (cnt))

File modules/recon/contacts/gather/http/pgp_search.py

-import framework
-import re
-
-class Module(framework.module):
-    def __init__(self, params):
-        framework.module.__init__(self, params)
-        self.register_option('domain', self.goptions['domain']['value'], 'yes', 'Domain to search.')
-        self.register_option('store', False, 'yes', 'add discovered hosts to the database.')
-        self.info = {
-                     'Name': 'RedIRIS PGP Key Owner Lookup',
-                     'Author': 'Robert Frost (@frosty_1313, frosty[at]unluckyfrosty.net)',
-                     'Description': 'Searches pgp.rediris for email addresses for the given domain.',
-                     'Comments': [
-                                  'Inspiration from theHarvester.py by Christan Martorella: cmarorella[at]edge-seecurity.com'
-                                  ]
-                     }
-
-    def module_run(self):
-        store = self.options['store']['value']
-
-        url = 'http://pgp.rediris.es/pks/lookup'
-        payload= {'search' : self.options['domain']['value'] }
-        resp = self.request(url, payload=payload)
-
-        results = []
-        results.extend(re.findall('([^>]*?)(?:\s\(.+?\))?\s&lt;(.*?@%s)&gt;<' % (self.options['domain']['value']), resp.text))
-        results.extend(re.findall('[\s]{10,}(\w.*?)(?:\s\(.+?\))?\s&lt;(.*?@%s)&gt;' % (self.options['domain']['value']), resp.text))
-        results = list(set(results))
-        if not results:
-            self.output('No results found.')
-            return
-
-        cnt = 0
-        new = 0
-        for contact in results:
-            name = contact[0].strip()
-            names = name.split(' ')
-            if len(names) == 2:
-                first = names[0]
-                last = names[1]
-            elif len(names) > 2:
-                if '.' in names[1] or len(names[1]) == 1:
-                    first = names[0]
-                    last = names[2]
-                else:
-                    first = names[0]
-                    last = ' '.join(names[1:])
-            else:
-                first = None
-                last = names[0]
-            email = contact[1]
-            self.output('%s (%s)' % (name, email))
-            cnt += 1
-            if store: new += self.add_contact(first, last, 'PGP key association', email)
-        self.output('%d total contacts found.' % (cnt))
-        if new: self.alert('%d NEW contacts found!' % (new))

File modules/recon/contacts/gather/http/twitter.py

-import framework
-# unique to module
-import urllib
-import re
-import sys
-
-class Module(framework.module):
-
-    def __init__(self, params):
-        framework.module.__init__(self, params)
-        self.register_option('handle', '@lanmaster53', 'yes', 'target twitter handle')
-        self.register_option('dtg', None, 'no', 'date-time group in the form YYYY-MM-DD')
-        self.info = {
-                     'Name': 'Twitter Handles',
-                     'Author': 'Robert Frost (@frosty_1313, frosty[at]unluckyfrosty.net)',
-                     'Description': 'Searches Twitter for users that mentioned, or were mentioned by, the given handle.',
-                     'Comments': [
-                                  'Twitter only saves tweets for 6-8 days at this time.'
-                                  ]
-                     }
-    def module_run(self):
-        self.handle_options()
-        header = ['Handle', 'Name', 'Time']
-        
-        self.tdata = []
-        # search for mentions tweeted by the given handle
-        self.output('Searching for users mentioned by the given handle.')
-        self.search_handle_tweets()
-        if self.tdata:
-            print ''
-            self.tdata.insert(0, header)
-            self.table(self.tdata, header=True)
-
-        self.tdata = []
-        # search for tweets mentioning the given handle
-        self.output('Searching for users who mentioned the given handle.')
-        self.search_handle_mentions()
-        if self.tdata:
-            print ''
-            self.tdata.insert(0, header)
-            self.table(self.tdata, header=True)
-
-    def handle_options(self):
-        '''
-        Method built to do quick and dirty parsing of options supplied by the user.
-        Sets two properties of this class instance, self.handle and self.dtg.
-        '''
-        # handle
-        handle = self.options['handle']['value']
-        self.handle = handle if not handle.startswith('@') else handle[1:]
-        # dtg
-        dtg = self.options['dtg']['value']
-        if not dtg:
-            dtg = '2011-01-01'
-        elif not re.match(r'\d\d\d\d-\d\d-\d\d', dtg):
-            dtg = '2011-01-01'
-            self.output('DTG should be in the format: YYYY-MM-DD. Using the default value of \'%s\'.' % (dtg))
-        self.dtg = dtg
-
-    def get_user_info(self, handle, time):
-        '''
-        Queries twitter for information on a given twitter handle.
-        Twitter API returns ALOT of good info, database does not currently handle most of it.
-        '''
-        url = 'https://api.twitter.com/1/users/show.json'
-        payload = {'screen_name': handle, 'include_entities': 'true'}
-        resp = self.request(url, payload=payload)
-        
-        jsonobj = resp.json
-        for item in ['error', 'errors']:
-            if item in jsonobj:
-                self.error(jsonobj[item])
-                return
-
-        name = jsonobj['name']
-        if not [handle, name, time] in self.tdata: self.tdata.append([handle, name, time])
-
-    def search_api(self, query):
-        payload = {'q': query}
-        url = 'http://search.twitter.com/search.json'
-        resp = self.request(url, payload=payload)
-        
-        jsonobj = resp.json
-        for item in ['error', 'errors']:
-            if item in jsonobj:
-                self.error(jsonobj[item])
-                return
-
-        return jsonobj
-
-    def search_handle_tweets(self):
-        '''
-        Searches for mentions tweeted by the given handle.
-        Pulls usernames out and sends to get_user_info.
-        '''
-        resp = self.search_api('from:%s since:%s' % (self.handle, self.dtg))
-        if resp:
-            for tweet in resp['results']:
-                if 'to_user' in tweet:
-                    self.get_user_info(tweet['to_user'], tweet['created_at'])
-
-    def search_handle_mentions(self):
-        '''
-        Searches for tweets mentioning the given handle.
-        Checks using "to:" and "@" operands in the API.
-        Passes identified handles to get_user_info.
-        '''
-        for operand in ['to:', '@']:
-            resp = self.search_api('%s%s since:%s' % (operand, self.handle, self.dtg))
-            if resp:
-                for tweet in resp['results']:
-                    if 'to_user' in tweet:
-                        self.get_user_info(tweet['from_user'], tweet['created_at'])

File modules/recon/contacts/gather/http/web/jigsaw.py

+import framework
+# unique to module
+import urllib
+import re
+
+class Module(framework.module):
+
+    def __init__(self, params):
+        framework.module.__init__(self, params)
+        self.register_option('company', self.goptions['company']['value'], 'yes', self.goptions['company']['desc'])
+        self.register_option('keywords', '', 'no', 'additional keywords to identify company')
+        self.info = {
+                     'Name': 'Jigsaw Contact Enumerator',
+                     'Author': 'Tim Tomes (@LaNMaSteR53)',
+                     'Description': 'Harvests contacts from Jigsaw.com and updates the \'contacts\' table of the database with the results.',
+                     'Comments': []
+                     }
+
+    def module_run(self):
+        company_id = self.get_company_id()
+        if company_id:
+            contact_ids = self.get_contact_ids(company_id)
+            if contact_ids:
+                self.get_contacts(contact_ids)
+
+    def get_company_id(self):
+        self.output('Gathering Company IDs...')
+        company_name = self.options['company']['value']
+        all_companies = []
+        page_cnt = 1
+        params = '%s %s' % (company_name, self.options['keywords']['value'])
+        url = 'http://www.jigsaw.com/FreeTextSearchCompany.xhtml'
+        payload = {'opCode': 'search', 'freeText': params}
+        while True:
+            self.verbose('Query: %s?%s' % (url, urllib.urlencode(payload)))
+            resp = self.request(url, payload=payload, redirect=False)
+            if resp.status_code == 301:
+                header = resp.headers['location']
+                company_id = re.search('\/(\d+?)\/', resp.headers['location']).group(1)
+                self.output('Unique Company Match Found: %s' % company_id)
+                return company_id
+            content = resp.text
+            pattern = "href=./id(\d+?)/.+?>(.+?)<.+?\n.+?title='([\d,]+?)'"
+            companies = re.findall(pattern, content)
+            if not companies: break
+            for company in companies:
+                all_companies.append((company[0], company[1], company[2]))
+            page_cnt += 1
+            payload['rpage'] = str(page_cnt)
+        if len(all_companies) == 0:
+            self.output('No Company Matches Found.')
+            return False
+        else:
+            id_len = len(max([str(x[0]) for x in all_companies], key=len))
+            for company in all_companies:
+                self.output('[%s] %s (%s contacts)' % (str(company[0]).ljust(id_len), company[1], company[2]))
+            company_id = raw_input('Enter Company ID from list [%s - %s]: ' % (all_companies[0][1], all_companies[0][0]))
+            if not company_id: company_id = all_companies[0][0]
+            return company_id
+
+    def get_contact_ids(self, company_id):
+        self.output('Gathering Contact IDs for Company \'%s\'...' % (company_id))
+        page_cnt = 1
+        contact_ids = []
+        url = 'http://www.jigsaw.com/SearchContact.xhtml'
+        payload = {'companyId': company_id, 'opCode': 'showCompDir'}
+        while True:
+            payload['rpage'] = str(page_cnt)
+            self.verbose('Query: %s?%s' % (url, urllib.urlencode(payload)))
+            content = self.request(url, payload=payload).text
+            pattern = "showContact\('(\d+?)'\)"
+            contacts = re.findall(pattern, content)
+            if not contacts: break
+            contact_ids.extend(contacts)
+            page_cnt += 1
+        return contact_ids
+
+    def get_contacts(self, contact_ids):
+        self.output('Gathering Contacts...')
+        cnt, tot = 0, 0
+        for contact_id in contact_ids:
+            url = 'http://www.jigsaw.com/BC.xhtml'
+            payload = {'contactId': contact_id}
+            content = self.request(url, payload=payload).text
+            if 'Contact Not Found' in content: continue
+            fname = self.unescape(re.search('<span id="firstname">(.+?)</span>', content).group(1))
+            lname = self.unescape(re.search('<span id="lastname">(.+?)</span>', content).group(1))
+            title = self.unescape(re.search('<span id="title" title=".*?">(.*?)</span>', content).group(1))
+            city = self.unescape(re.search('<span id="city">(.+?)</span>', content).group(1)).title()
+            state = re.search('<span id="state">(.+?)</span>', content)
+            if state: state = self.unescape(state.group(1)).upper()
+            region = []
+            for item in [city, state]:
+                if item: region.append(item)
+            region = ', '.join(region)
+            country = self.unescape(re.search('<span id="country">(.+?)</span>', content).group(1)).title()
+            self.output('[%s] %s %s - %s (%s - %s)' % (contact_id, fname, lname, title, region, country))
+            tot += 1
+            cnt += self.add_contact(fname=fname, lname=lname, title=title, region=region, country=country)
+        self.output('%d total contacts found.' % (tot))
+        if cnt: self.alert('%d NEW contacts found!' % (cnt))

File modules/recon/contacts/gather/http/web/pgp_search.py

+import framework
+import re
+
+class Module(framework.module):
+    def __init__(self, params):
+        framework.module.__init__(self, params)
+        self.register_option('domain', self.goptions['domain']['value'], 'yes', 'Domain to search.')
+        self.register_option('store', False, 'yes', 'add discovered hosts to the database.')
+        self.info = {
+                     'Name': 'RedIRIS PGP Key Owner Lookup',
+                     'Author': 'Robert Frost (@frosty_1313, frosty[at]unluckyfrosty.net)',
+                     'Description': 'Searches pgp.rediris for email addresses for the given domain.',
+                     'Comments': [
+                                  'Inspiration from theHarvester.py by Christan Martorella: cmarorella[at]edge-seecurity.com'
+                                  ]
+                     }
+
+    def module_run(self):
+        store = self.options['store']['value']
+
+        url = 'http://pgp.rediris.es/pks/lookup'
+        payload= {'search' : self.options['domain']['value'] }
+        resp = self.request(url, payload=payload)
+
+        results = []
+        results.extend(re.findall('([^>]*?)(?:\s\(.+?\))?\s&lt;(.*?@%s)&gt;<' % (self.options['domain']['value']), resp.text))
+        results.extend(re.findall('[\s]{10,}(\w.*?)(?:\s\(.+?\))?\s&lt;(.*?@%s)&gt;' % (self.options['domain']['value']), resp.text))
+        results = list(set(results))
+        if not results:
+            self.output('No results found.')
+            return
+
+        cnt = 0
+        new = 0
+        for contact in results:
+            name = contact[0].strip()
+            names = name.split(' ')
+            if len(names) == 2:
+                first = names[0]
+                last = names[1]
+            elif len(names) > 2:
+                if '.' in names[1] or len(names[1]) == 1:
+                    first = names[0]
+                    last = names[2]
+                else:
+                    first = names[0]
+                    last = ' '.join(names[1:])
+            else:
+                first = None
+                last = names[0]
+            email = contact[1]
+            self.output('%s (%s)' % (name, email))
+            cnt += 1
+            if store: new += self.add_contact(first, last, 'PGP key association', email)
+        self.output('%d total contacts found.' % (cnt))
+        if new: self.alert('%d NEW contacts found!' % (new))

File modules/recon/contacts/gather/http/whois_pocs.py

-import framework
-# unique to module
-from urlparse import urlparse
-
-class Module(framework.module):
-
-    def __init__(self, params):
-        framework.module.__init__(self, params)
-        self.register_option('domain', self.goptions['domain']['value'], 'yes', self.goptions['domain']['desc'])
-        self.register_option('store', False, 'yes', 'add discovered hosts to the database.')
-        self.info = {
-                     'Name': 'Whois POC Harvester',
-                     'Author': 'Tim Tomes (@LaNMaSteR53)',
-                     'Description': 'Uses the ARIN Whois RWS to harvest POC data from whois queries for the given domain.',
-                     'Comments': [
-                                  'Source options: [ db | <domain> | ./path/to/file | query <sql> ]',
-                                  ]
-                     }
-
-    def module_run(self):
-        domain = self.options['domain']['value']
-        store = self.options['store']['value']
-
-        headers = {'Accept': 'application/json'}
-        cnt = 0
-        new = 0
-        url = 'http://whois.arin.net/rest/pocs;domain=%s' % (domain)
-        self.verbose('URL: %s' % url)
-        resp = self.request(url, headers=headers)
-        if 'Your search did not yield any results.' in resp.text:
-            self.output('No contacts found.')
-            return
-        if not resp.json:
-            self.error('Invalid JSON response for \'%s\'.\n%s' % (domain, resp.text))
-            return
-        handles = [x['@handle'] for x in resp.json['pocs']['pocRef']]
-        for handle in handles:
-            url = 'http://whois.arin.net/rest/poc/%s' % (handle)
-            self.verbose('URL: %s' % url)
-            resp = self.request(url, headers=headers)
-            if resp.json: jsonobj = resp.json
-            else:
-                self.error('Invalid JSON response for \'%s\'.\n%s' % (handle, resp.text))
-                continue
-            poc = jsonobj['poc']
-            title = 'Whois contact'
-            city = poc['city']['$'].title()
-            country = poc['iso3166-1']['name']['$'].title()
-            fname = poc['firstName']['$'] if 'firstName' in poc else None
-            lname = poc['lastName']['$']