Commits

Dan Woodruff  committed a8f61ab Merge

Merged LaNMaSteR53/recon-ng into master

  • Participants
  • Parent commits 582368f, 8ee684b

Comments (0)

Files changed (122)

 *.pyc
 workspaces/
-experimental/

File core/framework.py

 import textwrap
 import socket
 import datetime
+import HTMLParser
 import subprocess
 import traceback
 import __builtin__
         self.do_help.__func__.__doc__ = '''Displays this menu'''
         self.doc_header = 'Commands (type [help|?] <topic>):'
         self.goptions = __builtin__.goptions
+        self.keys = __builtin__.keys
         self.workspace = __builtin__.workspace
         self.options = {}
 
             category = module.split(self.module_delimiter)[0]
             if category != last_category:
                 # print header
-                print ''
                 last_category = category
-                print '%s%s:' % (self.spacer, last_category.title())
-                print '%s%s' % (self.spacer, self.ruler*key_len)
+                self.heading(last_category)
             # print module
             print '%s%s' % (self.spacer*2, module)
         print ''
 
     def unescape(self, s):
         '''Unescapes HTML markup and returns an unescaped string.'''
-        import htmllib
-        p = htmllib.HTMLParser(None)
-        p.save_bgn()
-        p.feed(s)
-        return p.save_end()
+        h = HTMLParser.HTMLParser()
+        return h.unescape(s)
+        #p = htmllib.HTMLParser(None)
+        #p.save_bgn()
+        #p.feed(s)
+        #return p.save_end()
 
     def is_hash(self, hashstr):
         hashdict = [
     def table(self, tdata, header=False, table=None):
         '''Accepts a list of rows and outputs a table.'''
         if len(set([len(x) for x in tdata])) > 1:
-            self.error('Row lengths not consistent.')
-            return
+            raise FrameworkException('Row lengths not consistent.')
         lens = []
         cols = len(tdata[0])
         for i in range(0,cols):
 
         conn = sqlite3.connect('%s/data.db' % (self.workspace))
         cur = conn.cursor()
-        try: cur.execute(query, values)
-        except sqlite3.OperationalError as e:
-            self.error('Invalid query. %s %s' % (type(e).__name__, e.message))
-            return
+        cur.execute(query, values)
         conn.commit()
         conn.close()
         return cur.rowcount
 
-    def query(self, params, return_results=True):
+    def query(self, query):
         '''Queries the database and returns the results as a list.'''
-        # based on the do_ouput method
-        if not params:
-            self.help_query()
-            return
         conn = sqlite3.connect('%s/data.db' % (self.workspace))
         cur = conn.cursor()
-        try: cur.execute(params)
-        except sqlite3.OperationalError as e:
-            self.error('Invalid query. %s %s' % (type(e).__name__, e.message))
-            return False
-        if not return_results:
-            if cur.rowcount == -1 and cur.description:
-                header = tuple([x[0] for x in cur.description])
-                tdata = cur.fetchall()
-                if not tdata:
-                    self.output('No data returned.')
-                else:
-                    tdata.insert(0, header)
-                    self.table(tdata, True)
-                    self.output('%d rows returned' % (len(tdata)))
-            else:
-                conn.commit()
-                self.output('%d rows affected.' % (cur.rowcount))
-            conn.close()
-            return
+        cur.execute(query)
+        # a rowcount of -1 typically refers to a select statement
+        if cur.rowcount == -1:
+            rows = cur.fetchall()
+            results = rows
+        # a rowcount of 1 == success and 0 == failure
         else:
-            # a rowcount of -1 typically refers to a select statement
-            if cur.rowcount == -1:
-                rows = cur.fetchall()
-                result = rows
-            # a rowcount of 1 == success and 0 == failure
-            else:
-                conn.commit()
-                result = cur.rowcount
-            conn.close()
-            return result
+            conn.commit()
+            results = cur.rowcount
+        conn.close()
+        return results
 
     #==================================================
     # OPTIONS METHODS
             # if value type is bool or int, then we know the options is set
             if not type(self.options[option]['value']) in [bool, int]:
                 if self.options[option]['reqd'].lower() == 'yes' and not self.options[option]['value']:
-                    self.error('Value required for the \'%s\' option.' % (option))
-                    return False
-        return True
+                    raise FrameworkException('Value required for the \'%s\' option.' % (option))
+        return
 
     def get_source(self, params, query=None):
         source = params.split()[0].lower()
-        if source == 'query':
-            query = ' '.join(params.split()[1:])
-            results = self.query(query, True)
+        if source in ['query', 'db']:
+            query = ' '.join(params.split()[1:]) if source == 'query' else query
+            try: results = self.query(query)
+            except sqlite3.OperationalError as e:
+                raise FrameworkException('Invalid source query. %s %s' % (type(e).__name__, e.message))
             if not results:
-                self.error('No items found.')
                 sources = []
             elif len(results[0]) > 1:
-                self.error('Too many columns of data returned.')
-                source = []
+                raise FrameworkException('Too many columns of data as source input.')
             else: sources = [x[0] for x in results]
-        elif source == 'db' and query:
-            rows = self.query(query)
-            if not rows:
-                self.error('No items found.')
-                sources = []
-            else: sources = [x[0] for x in rows]
         elif os.path.exists(source):
             sources = open(source).read().split()
         else:
     #==================================================
 
     def display_keys(self):
-        conn = sqlite3.connect(self.goptions['key_file']['value'])
-        cur = conn.cursor()
-        cur.execute('SELECT * FROM keys')
-        rows = cur.fetchall()
-        conn.close()
-        tdata = [('Name', 'Value')]
-        for row in rows:
-            tdata.append((row[0], row[1]))
-        self.table(tdata, True)
-
-    def manage_key(self, key_name, key_text):
-        '''Automates the API key retrieval and storage process.'''
-        key = self.get_key_from_db(key_name)
-        if not key:
-            key = self.get_key_from_user(key_text)
-            if not key:
-                self.error('No %s.' % (key_text))
-                return False
-            if self.add_key_to_db(key_name, key):
-                self.output('%s added.' % (key_text))
-            else:
-                self.output('Error adding %s.' % (key_text))
-        return key
-
-    def get_key_from_db(self, key_name):
-        '''Retrieves an API key from the API key storage database.'''
-        conn = sqlite3.connect(self.goptions['key_file']['value'])
-        cur = conn.cursor()
-        cur.execute('SELECT value FROM keys WHERE name=?', (key_name,))
-        row = cur.fetchone()
-        conn.close()
-        if row:
-            return str(row[0])
-        else:
-            return False
+        tdata = []
+        for key in sorted(self.keys):
+            tdata.append([key, self.keys[key]])
+        if tdata:
+            tdata.insert(0, ['Name', 'Value'])
+            self.table(tdata, header=True)
+        else: self.output('No API keys stored.')
 
-    def get_key_from_user(self, key_text='API Key'):
-        '''Retrieves an API key from the user.'''
+    def load_keys(self):
+        key_path = './data/keys.dat'
+        if os.path.exists(key_path):
+            try:
+                key_data = json.loads(open(key_path, 'rb').read())
+                for key in key_data: self.keys[key] = key_data[key]
+            except:
+                self.error('Corrupt key file.')
+
+    def save_keys(self):
+        key_path = './data/keys.dat'
+        key_file = open(key_path, 'wb')
+        json.dump(self.keys, key_file)
+        key_file.close()
+
+    def get_key(self, key_name):
         try:
-            key = raw_input("Enter %s (blank to skip): " % (key_text))
-            return str(key)
-        except KeyboardInterrupt:
-            print ''
-            return False
+            return self.keys[key_name]
+        except KeyError:
+            raise FrameworkException('API key \'%s\' not found. Add API keys with the \'keys add\' command.' % (key_name))
 
-    def add_key_to_db(self, key_name, key_value):
-        '''Adds an API key to the API key storage database.'''
-        conn = sqlite3.connect(self.goptions['key_file']['value'])
-        cur = conn.cursor()
-        try: cur.execute('INSERT INTO keys VALUES (?,?)', (key_name, key_value))
-        except sqlite3.OperationalError:
-            return False
-        except sqlite3.IntegrityError:
-            try: cur.execute('UPDATE keys SET value=? WHERE name=?', (key_value, key_name))
-            except sqlite3.OperationalError:
-                return False
-        conn.commit()
-        conn.close()
-        return True
+    def add_key(self, name, value):
+        self.keys[name] = value
+        self.save_keys()
+
+    def delete_key(self, name):
+        del self.keys[name]
+        self.save_keys()
 
     #==================================================
     # REQUEST METHODS
     #==================================================
 
     def search_bing_api(self, query, limit=0):
-        api_key = self.manage_key('bing', 'Bing API key')
-        if not api_key: return
+        api_key = self.get_key('bing_api')
         url = 'https://api.datamarket.azure.com/Data.ashx/Bing/Search/v1/Web'
         payload = {'Query': query, '$format': 'json'}
         results = []
         while True:
             resp = None
             resp = self.request(url, payload=payload, auth=(api_key, api_key))
-            sys.stdout.write('.'); sys.stdout.flush()
             if resp.json == None:
-                self.error('Invalid JSON response.\n%s' % (resp.text))
-                continue
+                raise FrameworkException('Invalid JSON response.\n%s' % (resp.text))
             # add new results
             if 'results' in resp.json['d']:
                 results.extend(resp.json['d']['results'])
                 payload['$skip'] = resp.json['d']['__next'].split('=')[-1]
             else:
                 break
-        print ''
         return results
 
     def search_google_api(self, query, limit=0):
-        api_key = self.manage_key('google_api', 'Google API key')
-        if not api_key: return
-        cse_id = self.manage_key('google_cse', 'Google CSE ID')
-        if not cse_id: return
+        api_key = self.get_key('google_api')
+        cse_id = self.get_key('google_cse')
         url = 'https://www.googleapis.com/customsearch/v1'
         payload = {'alt': 'json', 'prettyPrint': 'false', 'key': api_key, 'cx': cse_id, 'q': query}
         results = []
         while True:
             resp = None
             resp = self.request(url, payload=payload)
-            sys.stdout.write('.'); sys.stdout.flush()
             if resp.json == None:
-                self.error('Invalid JSON response.\n%s' % (resp.text))
-                continue
+                raise FrameworkException('Invalid JSON response.\n%s' % (resp.text))
             # add new results
             if 'items' in resp.json:
                 results.extend(resp.json['items'])
                 payload['start'] = resp.json['queries']['nextPage'][0]['startIndex']
             else:
                 break
-        print ''
         return results
 
     def request(self, url, method='GET', timeout=None, payload={}, headers={}, cookies={}, auth=(), redirect=True):
         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:
     def do_info(self, params):
         '''Displays module information'''
         pattern = '%s%s:'
-        for item in ['Name', 'Author', 'Description']:
+        self.info['Path'] = 'modules/%s.py' % (self.modulename)
+        for item in ['Name', 'Path', 'Author', 'Description']:
             print ''
             print pattern % (self.spacer, item)
             print pattern[:-1] % (self.spacer*2, textwrap.fill(self.info[item], 100, initial_indent='', subsequent_indent=self.spacer*2))
                 self.options[name]['value'] = self.autoconvert(value)
             else: self.error('Invalid option.')
 
+    def do_keys(self, params):
+        '''Manages framework API keys'''
+        if params:
+            params = params.split()
+            arg = params.pop(0).lower()
+            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]))
+                else: print 'Usage: keys [add|update] <name> <value>'
+                return
+            elif arg == 'delete':
+                if len(params) == 1:
+                    self.delete_key(params[0])
+                    self.output('Key \'%s\' deleted.' % (params[0]))
+                else: print 'Usage: keys delete <name>'
+                return
+        self.help_keys()
+
     def do_query(self, params):
         '''Queries the database'''
-        self.query(params, False)
+        if not params:
+            self.help_query()
+            return
+        conn = sqlite3.connect('%s/data.db' % (self.workspace))
+        cur = conn.cursor()
+        try: cur.execute(params)
+        except sqlite3.OperationalError as e:
+            self.error('Invalid query. %s %s' % (type(e).__name__, e.message))
+            return
+        if cur.rowcount == -1 and cur.description:
+            header = tuple([x[0] for x in cur.description])
+            tdata = cur.fetchall()
+            if not tdata:
+                self.output('No data returned.')
+            else:
+                tdata.insert(0, header)
+                self.table(tdata, True)
+                self.output('%d rows returned' % (len(tdata)))
+        else:
+            conn.commit()
+            self.output('%d rows affected.' % (cur.rowcount))
+        conn.close()
+        return
 
     def do_show(self, params):
         '''Shows various framework items'''
             elif arg == 'options':
                 self.display_options(None)
                 return
+            elif arg == 'dashboard':
+                self.display_dashboard()
+                return
             elif arg == 'workspaces':
                 self.display_workspaces()
                 return
             elif arg == 'schema':
                 self.display_schema()
                 return
-            elif arg == 'keys':
-                self.display_keys()
-                return
-            elif arg == 'dashboard':
-                self.display_dashboard()
-                return
             elif arg in [x[0] for x in self.query('SELECT name FROM sqlite_master WHERE type=\'table\'')]:
-                self.query('SELECT * FROM %s ORDER BY 1' % (arg), False)
+                self.do_query('SELECT * FROM %s ORDER BY 1' % (arg))
                 return
         self.help_show()
 
 
     def do_run(self, params):
         '''Runs the module'''
-        if not self.validate_options(): return
         try:
+            self.validate_options()
             self.module_run()
-        except:
-            print '-'*60
-            traceback.print_exc()
-            print '-'*60
-        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})
+        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())
+        finally:
+            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})
 
     def module_run(self):
         pass
         print 'Usage: set <option> <value>'
         self.display_options(None)
 
+    def help_keys(self):
+        print 'Usage: keys [list|add|delete|update]'
+
     def help_query(self):
         print 'Usage: query <sql>'
         print ''
         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|workspaces|schema|keys|<table>]'
+        print 'Usage: show [modules|options|dashboard|workspaces|schema|<table>]'
 
     def help_shell(self):
         print 'Usage: [shell|!] <command>'
     def complete_set(self, text, *ignored):
         return [x for x in self.options if x.startswith(text)]
 
+    def complete_keys(self, text, line, *ignored):
+        args = line.split()
+        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)]
+
     def complete_show(self, text, line, *ignored):
         args = line.split()
         if len(args) > 1 and args[1].lower() == 'modules':
             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)]
 
             return json.loads(self.text)
         except ValueError:
             return None
+
+class FrameworkException(Exception):
+    pass

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/discovery/exploitable/http/dnn_fcklinkgallery.py

 
     def module_run(self):
         hosts = self.get_source(self.options['source']['value'], 'SELECT DISTINCT host FROM hosts WHERE host IS NOT NULL ORDER BY host')
-        if not hosts: return
 
         # check all hosts for DNN fcklinkgallery page
         protocols = ['http', 'https']
                     resp = self.request(url, redirect=False)
                     code = resp.status_code
                 except KeyboardInterrupt:
-                    print ''
-                    return
+                    raise KeyboardInterrupt
                 except:
                     code = 'Error'
                 if code == 200 and '> Link Gallery' in resp.text:

File modules/discovery/exploitable/http/generic_restaurantmenu.py

                      }
 
     def module_run(self):
-        validate = self.options['validate']['value']
         hosts = self.get_source(self.options['source']['value'], 'SELECT DISTINCT host FROM hosts WHERE host IS NOT NULL ORDER BY host')
-        if not hosts: return
+        validate = self.options['validate']['value']
 
         # check all hosts for GenericRestaurantMenu Menu Categories Editor Page, SQL Query Info Disclosure, and Possible SQLi Vulnerbility
         protocols = ['http', 'https']
                     resp = self.request(url, redirect=False)
                     code = resp.status_code
                 except KeyboardInterrupt:
-                    print ''
-                    return
+                    raise KeyboardInterrupt
                 except:
                     code = 'Error'
                 if code == 200 and 'Menu Categories' in resp.text:
                             resp = self.request(vulncode, redirect=False)
                             code = resp.status_code
                         except KeyboardInterrupt:
-                            print ''
-                            return
+                            raise KeyboardInterrupt
                         except:
                             code = 'Error'
                         if code == 500 and 'Executing Database Query' in resp.text:

File modules/discovery/exploitable/http/webwiz_rte.py

 
     def module_run(self):
         hosts = self.get_source(self.options['source']['value'], 'SELECT DISTINCT host FROM hosts WHERE host IS NOT NULL ORDER BY host')
-        if not hosts: return
 
         protocols = ['http', 'https']
         files = [
                         resp = self.request(url, redirect=False)
                         code = resp.status_code
                     except KeyboardInterrupt:
-                        print ''
-                        return
+                        raise KeyboardInterrupt
                     except:
                         code = 'Error'
                     if code == 200 and 'Attach File Properties' in resp.text:

File modules/discovery/info_disclosure/dns/cache_snoop.py

                      }
 
     def module_run(self):
-        nameserver = self.options['nameserver']['value']
-        
         domains = self.get_source(self.options['domains']['value'])
-        if not domains: return
+        nameserver = self.options['nameserver']['value']
 
         self.output('Starting queries...')
         
             query = dns.message.make_query(domain, dns.rdatatype.A, dns.rdataclass.IN)
             # unset the Recurse flag 
             query.flags ^= dns.flags.RD
-            try:
-                # try the query
-                response = dns.query.udp(query, nameserver)
-                if len(response.answer) > 0:
-                    self.alert('%s => Snooped!' % (domain))
-                else:
-                    self.verbose('%s => Not Found.' % (domain))
-                continue
-            except KeyboardInterrupt:
-                print ''
-                return
-            except Exception as e:
-                self.error(e.__str__())
-                return
+            response = dns.query.udp(query, nameserver)
+            if len(response.answer) > 0:
+                self.alert('%s => Snooped!' % (domain))
+            else:
+                self.verbose('%s => Not Found.' % (domain))
+            continue

File modules/discovery/info_disclosure/http/backup_finder.py

                      }
 
     def module_run(self):
+        hosts = self.get_source(self.options['source']['value'], 'SELECT DISTINCT host FROM hosts WHERE host IS NOT NULL ORDER BY host')
         uri = self.options['uri']['value']
         searchstr = self.options['searchstr']['value']
-        
-        hosts = self.get_source(self.options['source']['value'], 'SELECT DISTINCT host FROM hosts WHERE host IS NOT NULL ORDER BY host')
-        if not hosts: return
 
         protocols = ['http', 'https']
 
                         resp = self.request(url, redirect=False)
                         code = resp.status_code
                     except KeyboardInterrupt:
-                        print ''
-                        return
+                        raise KeyboardInterrupt
                     except:
                         code = 'Error'
                     if code == 200 and searchstr in resp.text:

File modules/discovery/info_disclosure/http/interesting_files.py

 import framework
 # unique to module
+import warnings
 import gzip
 from StringIO import StringIO
 
         return data_ct
 
     def module_run(self):
-        download = self.options['download']['value']
-        
         hosts = self.get_source(self.options['source']['value'], 'SELECT DISTINCT host FROM hosts WHERE host IS NOT NULL ORDER BY host')
-        if not hosts: return
-
+        download = self.options['download']['value']
+        # ignore unicode warnings when trying to ungzip text type 200 repsonses
+        warnings.simplefilter("ignore")
         protocols = ['http', 'https']
         # (filename, string to search for to prevent false positive)
         filetypes = [
                         resp = self.request(url, timeout=2, redirect=False)
                         code = resp.status_code
                     except KeyboardInterrupt:
-                        print ''
-                        return
+                        raise KeyboardInterrupt
                     except:
                         code = 'Error'
                     if code == 200:
                         # uncompress if necessary
                         text = ('.gz' in filename and self.uncompress(resp.text)) or resp.text
-                        # check for file type since many custom 404s are returned as 200s 
+                        # check for file type since many custom 404s are returned as 200s
                         if verify.lower() in text.lower():
                             self.alert('%s => %s. \'%s\' found!' % (url, code, filename))
                             if download:

File modules/experimental/rce.py

+import framework
+# unique to module
+import urllib
+
+class Module(framework.module):
+
+    def __init__(self, params):
+        framework.module.__init__(self, params)
+        self.register_option('base_url', None, 'yes', 'the target resource url excluding any parameters')
+        self.register_option('parameters', None, 'yes', 'the query parameters with \'<rce>\' signifying the value of the vulnerable parameter')
+        self.register_option('basic_user', None, 'no', 'username for basic authentication')
+        self.register_option('basic_pass', None, 'no', 'password for basic authentication')
+        self.register_option('cookie', None, 'no', 'cookie string containing authenticated session data')
+        self.register_option('post', False, 'yes', 'set the request method to post. parameters should still be submitted in the url option')
+        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 Command Execution Shell Interface',
+                     'Author': 'Tim Tomes (@LaNMaSteR53)',
+                     'Description': 'Provides a shell interface for remote command execution flaws in web applications.',
+                     'Comments': []
+                     }
+
+    def help(self):
+        return 'Type \'exit\' or \'ctrl-c\' to exit the shell.'
+
+    def parse_params(self, params):
+        params = params.split('&')
+        params = [param.split('=') for param in params]
+        return [(urllib.unquote_plus(param[0]), urllib.unquote_plus(param[1])) for param in params]
+
+    def module_run(self):
+        base_url = self.options['base_url']['value']
+        base_params = self.options['parameters']['value']
+        username = self.options['basic_user']['value']
+        password = self.options['basic_pass']['value']
+        cookie = self.options['cookie']['value']
+        start = self.options['mark_start']['value']
+        end = self.options['mark_end']['value']
+
+        # process authentication
+        auth = (username, password) if username and password else ()
+        headers = {'Cookie': cookie} if cookie else {}
+
+        # set the request method
+        method = 'POST' if self.options['post']['value'] else 'GET'
+
+        print 'Type \'help\' or \'?\' for assistance.'
+        while True:
+            # get command from the terminal
+            cmd = raw_input("cmd> ")
+            if cmd.lower() == 'exit': return
+            elif cmd.lower() in ['help', '?']:
+                print self.help()
+                continue
+            # build the payload from the base_params string
+            payload = {}
+            params = self.parse_params(base_params.replace('<rce>', cmd))
+            for param in params:
+                payload[param[0]] = param[1]
+            # send the request
+            resp = self.request(base_url, method=method, payload=payload, headers=headers, auth=auth)
+            # process the response
+            output = resp.text
+            if start and end:
+                try: output = output[output.index(start)+len(start):]
+                except ValueError: self.error('Invalid start marker.')
+                try: output = output[:output.index(end)]
+                except ValueError: self.error('Invalid end marker.')
+            print '%s' % (output.strip())

File modules/exploitation/shell/http/rce.py

-import framework
-# unique to module
-import urllib
-
-class Module(framework.module):
-
-    def __init__(self, params):
-        framework.module.__init__(self, params)
-        self.register_option('base_url', None, 'yes', 'the target resource url excluding any parameters')
-        self.register_option('parameters', None, 'yes', 'the query parameters with \'<rce>\' signifying the value of the vulnerable parameter')
-        self.register_option('basic_user', None, 'no', 'username for basic authentication')
-        self.register_option('basic_pass', None, 'no', 'password for basic authentication')
-        self.register_option('cookie', None, 'no', 'cookie string containing authenticated session data')
-        self.register_option('post', False, 'yes', 'set the request method to post. parameters should still be submitted in the url option')
-        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',
-                     'Author': 'Tim Tomes (@LaNMaSteR53)',
-                     'Description': 'Provides a shell interface for remote command execution flaws in web applications.',
-                     'Comments': []
-                     }
-
-    def help(self):
-        return 'Type \'exit\' or \'ctrl-c\' to exit the shell.'
-
-    def parse_params(self, params):
-        params = params.split('&')
-        params = [param.split('=') for param in params]
-        return [(urllib.unquote_plus(param[0]), urllib.unquote_plus(param[1])) for param in params]
-
-    def module_run(self):
-        base_url = self.options['base_url']['value']
-        base_params = self.options['parameters']['value']
-        username = self.options['basic_user']['value']
-        password = self.options['basic_pass']['value']
-        cookie = self.options['cookie']['value']
-        start = self.options['mark_start']['value']
-        end = self.options['mark_end']['value']
-
-        # process authentication
-        auth = (username, password) if username and password else ()
-        headers = {'Cookie': cookie} if cookie else {}
-
-        # set the request method
-        method = 'POST' if self.options['post']['value'] else 'GET'
-
-        print 'Type \'help\' or \'?\' for assistance.'
-        while True:
-            # get command from the terminal
-            try:
-                cmd = raw_input("cmd> ")
-            except KeyboardInterrupt:
-                print ''
-                return
-            if cmd.lower() == 'exit': return
-            elif cmd.lower() in ['help', '?']:
-                print self.help()
-                continue
-            # build the payload from the base_params string
-            payload = {}
-            params = self.parse_params(base_params.replace('<rce>', cmd))
-            for param in params:
-                payload[param[0]] = param[1]
-            # send the request
-            try: resp = self.request(base_url, method=method, payload=payload, headers=headers, auth=auth)
-            except KeyboardInterrupt:
-                print ''
-                return
-            except Exception as e:
-                self.error(e.__str__())
-                continue
-            # process the response
-            output = resp.text
-            if start and end:
-                try: output = output[output.index(start)+len(start):]
-                except ValueError: self.error('Invalid start marker.')
-                try: output = output[:output.index(end)]
-                except ValueError: self.error('Invalid end marker.')
-            print '%s' % (output.strip())

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'
-        try: resp = self.request(url)
-        except KeyboardInterrupt:
-            print ''
-            return
-        except Exception as e:
-            self.error(e.__str__())
-            return
-        
-        # 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:
-                print ''
-                return
-            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')
-        if not accounts: return
-
-        # 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': ''}
-            try: resp = self.request(url, payload=payload, method='POST', redirect=False)
-            except KeyboardInterrupt:
-                print ''
-                break
-            except Exception as e:
-                self.error(e.__str__())
-                break
-            content = resp.text
-            #if 'Gotcha!' in content:
-            #    self.error('Hm... Got a captcha.')
-            #    return
-            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')
-        if not emails: return
-        
-        total = 0
-        emailsFound = 0
-        # lookup each hash
-        url = 'https://shouldichangemypassword.com/check-single.php'
-        for emailstr in emails:
-            # build the request
-            payload = {'email': emailstr}
-            try: resp = self.request(url, method="POST", payload=payload)
-            except KeyboardInterrupt:
-                print ''
-                break
-            except Exception as e:
-                self.error(e.__str__())
-                continue
-
-            # 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.manage_key('jigsaw_key', 'Jigsaw API Key')
-        if not self.api_key: return
-        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)))
-            try: resp = self.request(url, payload=payload, redirect=False)
-            except KeyboardInterrupt:
-                print ''
-                return
-            except Exception as e:
-                self.error(e.__str__())
-                return
-            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]))
-        try:
-            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
-        except KeyboardInterrupt:
-            print ''
-            return
-
-    def get_contacts(self, company_id):
-        self.output('Gathering Contacts...')
-        tot = 0
-        cnt = 0