├── .gitignore ├── LICENSE ├── README.md └── github-osint.py /.gitignore: -------------------------------------------------------------------------------- 1 | wrapper/ 2 | .vscode/ 3 | *.txt 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Alexey Pronin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub OSINT tool 2 | 3 | This tool uses GitHub API to get email addresses from commit log of user/organisation repositories 4 | 5 | It can be operated with/without GitHub API token. 6 | 7 | ## How to: 8 | 9 | ``` 10 | git clone https://github.com/vulnbe/github-osint.git 11 | cd github-osint 12 | pip install -r requirements.txt 13 | python3 github-osint.py --owner vulnbe 14 | ``` 15 | 16 | ## Usage 17 | 18 | ``` 19 | github-osint.py [-h] [--forks] [--verbose] 20 | [--token TOKEN] [--threads THREADS] 21 | --owner OWNER 22 | 23 | optional arguments: 24 | -h, --help show this help message and exit 25 | --owner OWNER user or organisation name, eg. vulnbe 26 | --forks include forks 27 | --verbose show debug info 28 | --token TOKEN GitHub API token (for better rate limits) 29 | --threads THREADS thereads to fetch data 30 | ``` 31 | 32 | If you reach the API request limit, then use [token](https://github.com/settings/tokens). 33 | -------------------------------------------------------------------------------- /github-osint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | https://vuln.be/github-osint.html 4 | https://github.com/vulnbe/github-osint 5 | """ 6 | 7 | import sys 8 | import requests 9 | import json 10 | import logging 11 | import re 12 | import argparse 13 | 14 | from multiprocessing.dummy import Pool 15 | 16 | threads_num = 4 17 | logformat = '%(asctime)s %(message)s' 18 | 19 | class Identity(object): 20 | name = None 21 | email = None 22 | def __init__(self, name, email): 23 | self.name = name 24 | self.email = email 25 | def __key(self): 26 | return (self.name, self.email) 27 | def __eq__(self, x): 28 | return self.__key() == x.__key() 29 | def __hash__(self): 30 | return hash(self.__key()) 31 | 32 | class Supplicant(object): 33 | _headers = {'User-Agent':'Mozilla/5.0 (github-osint https://github.com/vulnbe/github-osint)'} 34 | def __init__(self, api_token): 35 | if api_token: 36 | self._headers['Authorization'] = 'token {}'.format(api_token) 37 | def get_content(self, url): 38 | logging.debug('Fetching file {}'.format(url)) 39 | response = requests.get(url, headers=self._headers) 40 | if response.ok: 41 | try: 42 | return response.json() + self.get_content(response.links['next']['url']) 43 | except: 44 | return response.json() 45 | else: 46 | return [] 47 | 48 | def print_banner(): 49 | print(' _______ __ __ _______ __ _______ _______ _______ _______ _______ ') 50 | print('| __|__| |_| | |.--.--.| |--.______| | __|_ _| | |_ _|') 51 | print('| | | | _| || | || _ |______| - |__ |_| |_| | | | ') 52 | print('|_______|__|____|___|___||_____||_____| |_______|_______|_______|__|____| |___| ') 53 | print('') 54 | 55 | def main(args): 56 | if args.verbose: 57 | logging.basicConfig(format=logformat, level=logging.DEBUG) 58 | else: 59 | logging.basicConfig(format=logformat, level=logging.INFO) 60 | 61 | account_apis = ['https://api.github.com/orgs/{}/repos', 'https://api.github.com/users/{}/repos'] 62 | supplicant = Supplicant(args.token) 63 | repos = None 64 | 65 | logging.info('Scanning account {}...'.format(args.owner)) 66 | for url in account_apis: 67 | repos = supplicant.get_content(url.format(args.owner)) 68 | if len(repos): 69 | break 70 | 71 | repos_commits = None 72 | if repos: 73 | logging.info('Found {} repositories'.format(len(repos))) 74 | repos_filtered = list(map(lambda x: x['commits_url'].replace('{/sha}',''), 75 | filter(lambda x: x['fork'] == False or x['fork'] == args.forks, repos))) 76 | 77 | logging.info('Fetching commits from {} repositories ({} skipped)...'.format(len(repos_filtered), 78 | len(repos)-len(repos_filtered))) 79 | 80 | pool = Pool(args.threads) 81 | repos_commits = pool.map(supplicant.get_content, repos_filtered) 82 | pool.close() 83 | pool.join() 84 | else: 85 | logging.info('Repositories not found') 86 | sys.exit(0) 87 | 88 | repos_identities = {} 89 | if repos_commits: 90 | for commits in repos_commits: 91 | identities = set() 92 | for commit in commits: 93 | for field in ['author', 'committer']: 94 | if commit['commit'][field]: 95 | identity = Identity(commit['commit'][field]['name'], 96 | commit['commit'][field]['email']) 97 | identities.add(identity) 98 | repos_identities[re.sub(r'/commits/.*', '', commits[0]['url'])] = identities 99 | 100 | for repo_name, identities in repos_identities.items(): 101 | print(repo_name) 102 | while len(identities) > 0: 103 | identity = identities.pop() 104 | print('\t{} <{}>'.format(identity.name, identity.email)) 105 | 106 | if __name__ == '__main__': 107 | print_banner() 108 | parser = argparse.ArgumentParser(description= 109 | 'Small intelligence tool for retrieving info about github organisation\'s or user\'s commit log') 110 | parser.add_argument('--owner', required=True, help='user or organisation name, eg. vulnbe') 111 | parser.add_argument('--forks', action='store_true', help='include forks') 112 | parser.add_argument('--verbose', action='store_true', help='show debug info') 113 | parser.add_argument('--token', required=False, help='GitHub API token (for better rate limits)') 114 | parser.add_argument('--threads', required=False, help='thereads to fetch data', type=int, default=threads_num) 115 | args = parser.parse_args() 116 | main(args) 117 | --------------------------------------------------------------------------------