├── requirements.txt ├── README.md └── GitIntel.py /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | prompt_toolkit 3 | colorama 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitIntel: GitHub OSINT Tool 2 | 3 | **GitIntel** is a powerful GitHub OSINT tool designed to unearth hidden insights from GitHub repositories and user profiles. This tool allows users to extract valuable information, such as emails, from GitHub activities and repositories, making it an essential asset for ethical hacking and security testing. 4 | 5 | ## **Features** 6 | 7 | - **User Information Retrieval**: Collects comprehensive data about GitHub users, including bio, company, location, followers, and more. 8 | - **Email Extraction**: Gathers emails from commits and public events, with options to include hidden emails. 9 | - **Repository Analysis**: Retrieves and analyzes repositories owned by the user. 10 | - **Event Tracking**: Monitors public events to discover additional information. 11 | - **Customizable Options**: Offers advanced configuration for email collection based on user activity. 12 | 13 | ## **Prerequisites** 14 | 15 | - **Python 3.7+** 16 | - **requests** (For making HTTP requests) 17 | - **prompt_toolkit** (For user input and path completion) 18 | - **colorama** (For colored terminal output) 19 | 20 | ## **Installation** 21 | 22 | 1. **Clone the repository:** 23 | 24 | ```bash 25 | git clone https://github.com/AnonKryptiQuz/GitIntel.git 26 | cd GitIntel 27 | ``` 28 | 29 | 2. **Install the required packages:** 30 | 31 | ```bash 32 | pip install -r requirements.txt 33 | ``` 34 | 35 | **Ensure `requirements.txt` contains:** 36 | 37 | ```text 38 | requests==2.28.1 39 | prompt_toolkit==3.0.36 40 | colorama==0.4.6 41 | ``` 42 | 43 | ## **Configuration** 44 | 45 | To configure the API endpoint and potentially increase the rate limit, locate the following line in the script and replace it with your specific API path or key: 46 | 47 | ```python 48 | self.base_url = "https://api.github.com/" 49 | ``` 50 | 51 | If you prefer, you can also continue using this default API. 52 | 53 | ## **Usage** 54 | 55 | 1. **Run the tool:** 56 | 57 | ```bash 58 | python GitIntel.py 59 | ``` 60 | 61 | 2. **Enter the GitHub username when prompted.** 62 | 63 | 3. **Choose advanced options to configure email extraction preferences.** 64 | 65 | 4. **View the results displayed in the terminal and save them to a file if desired.** 66 | 67 | ## **Disclaimer** 68 | 69 | - **Educational Purposes Only**: GitIntel is intended for educational and research use. The tool should not be used for illegal or malicious activities. It is the user’s responsibility to ensure compliance with local laws and regulations. 70 | 71 | 72 | ## **Author** 73 | 74 | **Created by:** [AnonKryptiQuz](https://AnonKryptiQuz.github.io/) 75 | -------------------------------------------------------------------------------- /GitIntel.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import os 3 | import sys 4 | import subprocess 5 | import time 6 | import random 7 | from prompt_toolkit import prompt 8 | from prompt_toolkit.completion import PathCompleter 9 | from colorama import Fore, init 10 | 11 | init(autoreset=True) 12 | 13 | def check_and_install_packages(packages): 14 | print(Fore.YELLOW + "Checking Requirements...\n") 15 | for package, version in packages.items(): 16 | try: 17 | __import__(package) 18 | print(Fore.YELLOW + f"[+] {package} is already installed.") 19 | except ImportError: 20 | print(Fore.RED + f"[i] {package} not found. Installing...") 21 | subprocess.check_call([sys.executable, '-m', 'pip', 'install', f"{package}=={version}"]) 22 | print(Fore.GREEN + f"[+] {package} installed successfully.") 23 | 24 | def get_file_path(prompt_text): 25 | completer = PathCompleter() 26 | return prompt(prompt_text, completer=completer).strip() 27 | 28 | def clear_screen(): 29 | os.system('cls' if os.name == 'nt' else 'clear') 30 | 31 | def get_random_user_agent(): 32 | user_agents = [ 33 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", 34 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0", 35 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:91.0) Gecko/20100101 Firefox/91.0", 36 | "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0", 37 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36", 38 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0", 39 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36" 40 | ] 41 | return random.choice(user_agents) 42 | 43 | class GitHubEmailExtractor: 44 | def __init__(self, username): 45 | self.username = username 46 | self.base_url = "https://api.github.com" # Replace with your specific API path or key to have a longer rate limit. If you don't want to do that, you can keep using the default one. 47 | 48 | self.headers = { 49 | "User-Agent": get_random_user_agent() 50 | } 51 | self.user_info = {} 52 | 53 | def user_exists(self): 54 | response = requests.get(f"{self.base_url}/users/{self.username}", headers=self.headers) 55 | 56 | if response.status_code == 200: 57 | user_data = response.json() 58 | self.user_info = { 59 | 'name': user_data.get('name', 'N/A'), 60 | 'id': user_data.get('id', 'N/A'), 61 | 'bio': user_data.get('bio', 'No bio available'), 62 | 'html_url': user_data.get('html_url', 'N/A'), 63 | 'profile_pic': user_data.get('avatar_url', 'N/A'), 64 | 'created_at': user_data.get('created_at', 'N/A'), 65 | 'company': user_data.get('company', 'N/A'), 66 | 'location': user_data.get('location', 'N/A'), 67 | 'followers': user_data.get('followers', 'N/A'), 68 | 'following': user_data.get('following', 'N/A'), 69 | 'type': user_data.get('type', 'N/A'), 70 | 'email': user_data.get('email', 'N/A') 71 | } 72 | return True 73 | elif response.status_code == 404: 74 | print(Fore.RED + "[!] GitHub user does not exist.") 75 | elif response.status_code == 403: 76 | print(Fore.RED + "[!] GitHub API rate limit reached. Please try again later.") 77 | sys.exit(1) 78 | else: 79 | print(Fore.RED + f"[!] Failed to retrieve user: {response.text}") 80 | 81 | return False 82 | 83 | def get_repositories(self): 84 | response = requests.get(f"{self.base_url}/users/{self.username}/repos", headers=self.headers) 85 | if response.status_code == 200: 86 | try: 87 | return response.json() 88 | except ValueError: 89 | print(Fore.RED + "[!] Unexpected response format from repositories.") 90 | return [] 91 | else: 92 | print(Fore.RED + f"[!] Failed to retrieve repositories: {response.text}") 93 | return [] 94 | 95 | def get_commits(self, repo): 96 | response = requests.get(f"{self.base_url}/repos/{self.username}/{repo}/commits", headers=self.headers) 97 | if response.status_code == 200: 98 | try: 99 | return response.json() 100 | except ValueError: 101 | print(Fore.RED + "[!] Unexpected response format from commits.") 102 | return [] 103 | else: 104 | print(Fore.RED + f"[!] Failed to retrieve commits for {repo}") 105 | return [] 106 | 107 | def get_public_events(self): 108 | response = requests.get(f"{self.base_url}/users/{self.username}/events/public", headers=self.headers) 109 | if response.status_code == 200: 110 | try: 111 | return response.json() 112 | except ValueError: 113 | print(Fore.RED + "[!] Unexpected response format from public events.") 114 | return [] 115 | else: 116 | print(Fore.RED + f"[!] Failed to retrieve public events: {response.text}") 117 | return [] 118 | 119 | def collect_emails(self, include_hidden=False, user_specific=False): 120 | email_sources = {} 121 | 122 | repos = self.get_repositories() 123 | for repo in repos: 124 | if not repo.get("fork"): 125 | try: 126 | commits = self.get_commits(repo.get('name', '')) 127 | for commit in commits: 128 | if commit: 129 | email = commit.get('commit', {}).get('author', {}).get('email') 130 | if email: 131 | email_sources.setdefault(email, []).append(f"Repo: https://github.com/{self.username}/{repo['name']}, User: {commit.get('author', {}).get('login', 'unknown')}") 132 | except Exception as e: 133 | print(Fore.RED + f"[!] Error processing repo {repo.get('name', 'unknown')}") 134 | 135 | events = self.get_public_events() 136 | for event in events: 137 | if event.get('type') == 'PushEvent': 138 | try: 139 | for commit in event.get('payload', {}).get('commits', []): 140 | if commit: 141 | email = commit.get('author', {}).get('email') 142 | if email: 143 | email_sources.setdefault(email, []).append(f"Public Commit, User: {event.get('actor', {}).get('login', 'unknown')}") 144 | except Exception as e: 145 | print(Fore.RED + f"[!] Error processing event: {e}") 146 | 147 | if not include_hidden: 148 | email_sources = {email: sources for email, sources in email_sources.items() if not email.endswith('@users.noreply.github.com')} 149 | 150 | if user_specific: 151 | email_sources = {email: sources for email, sources in email_sources.items() if any(f"User: {self.username}" in source for source in sources)} 152 | 153 | return email_sources 154 | 155 | def get_user_input(prompt, default_value): 156 | while True: 157 | user_input = input(prompt).strip().lower() 158 | if user_input in ['y', 'n', '']: 159 | return user_input if user_input else default_value 160 | else: 161 | print(Fore.RED + "Invalid input. Please enter 'y' or 'n'.") 162 | print(Fore.YELLOW + "[i] Press Enter to try again...") 163 | input() 164 | clear_screen() 165 | banner() 166 | 167 | def banner(): 168 | print(Fore.MAGENTA + r""" 169 | _____ _ _ _____ _ _ 170 | / ____(_) | |_ _| | | | | 171 | | | __ _| |_ | | _ __ | |_ ___| | 172 | | | |_ | | __| | | | '_ \| __/ _ \ | 173 | | |__| | | |_ _| |_| | | | || __/ | 174 | \_____|_|\__|_____|_| |_|\__\___|_| 175 | """) 176 | print(Fore.GREEN + "Unmasking GitHub’s Hidden Insights, One Repo at a Time - AnonKryptiQuz\n") 177 | 178 | def main(): 179 | clear_screen() 180 | 181 | required_packages = { 182 | 'requests': '2.28.1', 183 | 'prompt_toolkit': '3.0.36', 184 | 'colorama': '0.4.6' 185 | } 186 | check_and_install_packages(required_packages) 187 | 188 | time.sleep(3) 189 | clear_screen() 190 | 191 | while True: 192 | banner() 193 | 194 | username = get_file_path("[?] Enter GitHub username: ") 195 | if not username: 196 | print(Fore.RED + "Username cannot be empty.") 197 | print(Fore.YELLOW + "[i] Press Enter to try again...") 198 | input() 199 | clear_screen() 200 | continue 201 | else: 202 | break 203 | 204 | extractor = GitHubEmailExtractor(username) 205 | if not extractor.user_exists(): 206 | print(Fore.RED + "\nGitHub user does not exist.") 207 | return 208 | 209 | user_choice = get_user_input(Fore.WHITE + "[?] Do you want to configure advanced options? (y/n, press Enter for n): ", 'n') 210 | 211 | if user_choice == 'y': 212 | include_hidden = get_user_input(Fore.WHITE + "[?] Include hidden emails (e.g., @users.noreply.github.com)? (y/n, press Enter for y): ", 'y') 213 | user_specific = get_user_input(Fore.WHITE + "[?] Filter emails by user activity? (y/n, press Enter for n): ", 'n') 214 | else: 215 | include_hidden = 'y' 216 | user_specific = 'n' 217 | 218 | include_hidden = include_hidden == 'y' 219 | user_specific = user_specific == 'y' 220 | 221 | print(Fore.YELLOW + "\nLoading, Please Wait...") 222 | time.sleep(3) 223 | clear_screen() 224 | 225 | print(Fore.BLUE + "[?] Starting Scan...\n") 226 | print(Fore.CYAN + f"[i] Profile URL: " + Fore.WHITE + f"{extractor.user_info['html_url']}") 227 | print(Fore.CYAN + f"[i] Username: " + Fore.WHITE + f"{username}") 228 | print(Fore.CYAN + f"[i] Name: " + Fore.WHITE + f"{extractor.user_info['name']}") 229 | print(Fore.CYAN + f"[i] Profile Picture: " + Fore.WHITE + f"{extractor.user_info['profile_pic']}") 230 | print(Fore.CYAN + f"[i] Bio: " + Fore.WHITE + f"{extractor.user_info['bio']}") 231 | print(Fore.CYAN + f"[i] Email: " + Fore.WHITE + f"{extractor.user_info['email']}") 232 | print(Fore.CYAN + f"[i] Company: " + Fore.WHITE + f"{extractor.user_info['company']}") 233 | print(Fore.CYAN + f"[i] Location: " + Fore.WHITE + f"{extractor.user_info['location']}") 234 | print(Fore.CYAN + f"[i] Account Type: " + Fore.WHITE + f"{extractor.user_info['type']}") 235 | print(Fore.CYAN + f"[i] Followers - Following: " + Fore.WHITE + f"{extractor.user_info.get('followers', 'N/A')} - {extractor.user_info.get('following', 'N/A')}") 236 | print(Fore.CYAN + f"[i] User ID: " + Fore.WHITE + f"{extractor.user_info['id']}") 237 | print(Fore.CYAN + f"[i] Account created on: " + Fore.WHITE + f"{extractor.user_info['created_at']}\n") 238 | print(Fore.BLUE + "[?] Extracting Email(s):\n") 239 | 240 | start_time = time.time() 241 | emails = extractor.collect_emails(include_hidden, user_specific) 242 | 243 | if not emails: 244 | print(Fore.RED + "No emails were found.\n") 245 | email_count = 0 246 | else: 247 | email_count = len(emails) 248 | for email, sources in emails.items(): 249 | print(Fore.GREEN + f"Email: {email}") 250 | print(Fore.BLUE + "Sources:") 251 | for source in sources: 252 | print(Fore.MAGENTA + f" - {source}") 253 | print() 254 | 255 | print(Fore.YELLOW + "[i] Email extraction completed.") 256 | print(Fore.YELLOW + f"[i] Number of emails found: {email_count}") 257 | print(Fore.YELLOW + f"[i] Time taken: {round(time.time() - start_time, 2)} seconds\n") 258 | 259 | save = input(Fore.BLUE + "[?] Do you want to save the results to a file? (y/n, press Enter for n): ").lower() 260 | if save == 'y': 261 | output_file = get_user_input(Fore.YELLOW + "[?] Enter the filename to save results (press Enter for 'results.txt'): ", 'results.txt') 262 | with open(output_file, 'w') as f: 263 | f.write(f"[i] Profile URL: {extractor.user_info['html_url']}\n") 264 | f.write(f"[i] Username: {username}\n") 265 | f.write(f"[i] Name: {extractor.user_info['name']}\n") 266 | f.write(f"[i] Profile Picture: {extractor.user_info['profile_pic']}\n") 267 | f.write(f"[i] Bio: {extractor.user_info['bio']}\n") 268 | f.write(f"[i] Email: {extractor.user_info['email']}\n") 269 | f.write(f"[i] Company: {extractor.user_info['company']}\n") 270 | f.write(f"[i] Location: {extractor.user_info['location']}\n") 271 | f.write(f"[i] Type: {extractor.user_info['type']}\n") 272 | f.write(f"[i] Followers - Following: {extractor.user_info.get('followers', 'N/A')} - {extractor.user_info.get('following', 'N/A')}\n") 273 | f.write(f"[i] User ID: {extractor.user_info['id']}\n") 274 | f.write(f"[i] Account created on: {extractor.user_info['created_at']}\n\n") 275 | 276 | for email, sources in emails.items(): 277 | f.write(f"Email: {email}\n") 278 | f.write("Sources:\n") 279 | for source in sources: 280 | f.write(f" - {source}\n") 281 | f.write("\n") 282 | print(Fore.GREEN + f"[i] Results have been saved to {output_file}") 283 | 284 | if __name__ == "__main__": 285 | try: 286 | main() 287 | except KeyboardInterrupt: 288 | print(Fore.RED + "\n[!] Program terminated by the user!") 289 | sys.exit(0) 290 | except Exception as e: 291 | print(Fore.RED + f"[!] An unexpected error occurred: {e}") 292 | sys.exit(1) 293 | --------------------------------------------------------------------------------