#!/usr/bin/env python # -*- encoding: utf-8 -*- import github3 as github import os import argparse import time import feedparser from copy import copy from sys import stderr gh_user = os.getenv('GH_USER', None) gh_pass = os.getenv('GH_PWD', None) gh_token = os.getenv('GH_TOKEN', None) gh_url = os.getenv('GH_URL', None) if gh_url is None: gh = github.GitHub(username=gh_user, password=gh_pass, token=gh_token) else: gh = github.GitHubEnterprise( url=gh_url, username=gh_user, password=gh_pass, token=gh_token) def search_wrapper(gen): while True: gen_back = copy(gen) try: yield next(gen) except StopIteration: return except github.exceptions.ForbiddenError as e: search_rate_limit = gh.rate_limit()['resources']['search'] # limit_remaining = search_rate_limit['remaining'] reset_time = search_rate_limit['reset'] current_time = int(time.time()) sleep_time = reset_time - current_time + 1 stderr.write( 'GitHub Search API rate limit reached. Sleeping for %d seconds.\n\n' % (sleep_time)) time.sleep(sleep_time) yield next(gen_back) except Exception as e: raise e def metasearch(repo_to_search=None, user_to_search=None, gh_dorks_file=None, active_monit=None, output_filename=None, refresh_time=60): if active_monit is None: search(repo_to_search, user_to_search, gh_dorks_file, active_monit, output_filename) else: monit(gh_dorks_file, active_monit, refresh_time) def monit(gh_dorks_file=None, active_monit=None, refresh_time=60): if gh_user is None: raise Exception('Error, env Github user variable needed') else: print( 'Monitoring user private feed searching new code to be dorked.' + 'Every new merged pull request trigger user scan.' ) print('-----') items_history = list() gh_private_feed = "https://github.com/{}.private.atom?token={}".format( gh_user, active_monit) while True: feed = feedparser.parse(gh_private_feed) for i in feed['items']: if 'merged pull' in i['title']: if i['title'] not in items_history: search( user_to_search=i['author_detail']['name'], gh_dorks_file=gh_dorks_file) items_history.append(i['title']) print('Waiting for new items...') time.sleep(refresh_time) def search(repo_to_search=None, user_to_search=None, gh_dorks_file=None, active_monit=None, output_filename=None): if gh_dorks_file is None: gh_dorks_file = 'github-dorks.txt' if not os.path.isfile(gh_dorks_file): raise Exception('Error, the dorks file path is not valid') if user_to_search: print("Scanning User: ", user_to_search) if repo_to_search: print("Scanning Repo: ", repo_to_search) found = False outputFile = None if output_filename: outputFile = open(output_filename, 'w') with open(gh_dorks_file, 'r') as dork_file: # Write CSV Header if outputFile: outputFile.write('Issue Type (Dork), Text Matches, File Path, Score/Relevance, URL of File\n') for dork in dork_file: dork = dork.strip() if not dork or dork[0] in '#;': continue addendum = '' if repo_to_search: addendum = ' repo:' + repo_to_search elif user_to_search: addendum = ' user:' + user_to_search dork = dork + addendum search_results = search_wrapper(gh.search_code(dork)) try: for search_result in search_results: found = True fmt_args = { 'dork': dork, 'text_matches': search_result.text_matches, 'path': search_result.path, 'score': search_result.score, 'url': search_result.html_url } # Either write to file or print output if outputFile: outputFile.write('{dork}, {text_matches}, {path}, {score}, {url}\n'.format(**fmt_args)) else: result = '\n'.join([ 'Found result for {dork}', 'Text matches: {text_matches}', 'File path: {path}', 'Score/Relevance: {score}', 'URL of File: {url}', '' ]).format(**fmt_args) print(result) except github.exceptions.GitHubError as e: print('GitHubError encountered on search of dork: ' + dork) print(e) return except Exception as e: print(e) print('Error encountered on search of dork: ' + dork) if not found: print('No results for your dork search' + addendum + '. Hurray!') def main(): parser = argparse.ArgumentParser( description='Search github for github dorks', epilog='Use responsibly, Enjoy pentesting') parser.add_argument( '-v', '--version', action='version', version='%(prog)s 0.1.1') group = parser.add_mutually_exclusive_group(required=True) group.add_argument( '-u', '--user', dest='user_to_search', action='store', help='Github user/org to search within. Eg: techgaun') group.add_argument( '-r', '--repo', dest='repo_to_search', action='store', help='Github repo to search within. Eg: techgaun/github-dorks') parser.add_argument( '-d', '--dork', dest='gh_dorks_file', action='store', help='Github dorks file. Eg: github-dorks.txt') group.add_argument( '-m', '--monit', dest='active_monit', action='store', help='Monitors Github user private feed with feed token' ) parser.add_argument( '-o', '--outputFile', dest='output_filename', action='store', help='CSV File to write results to. This overwrites the file provided! Eg: out.csv' ) args = parser.parse_args() metasearch( repo_to_search=args.repo_to_search, user_to_search=args.user_to_search, gh_dorks_file=args.gh_dorks_file, active_monit=args.active_monit, output_filename=args.output_filename) if __name__ == '__main__': main()