├── LICENCE ├── README.md ├── config.ini ├── requirements.txt ├── static └── cover.png └── subhound.py /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Abdelrhman Allam 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 |
Monitor and notify new subdomains found for a domain on Discord or/and Telegram.
6 | 7 | 14 | 15 |16 | Installation | 17 | Configuration | 18 | Usage | 19 | Running in the Background | 20 | References 21 |
22 | 23 | # Installation 24 | 25 | ```console 26 | $ git clone https://github.com/exampleuser/subhound.git 27 | $ cd subhound 28 | $ pip install -r requirements.txt 29 | ``` 30 | 31 | # Configuration 32 | 33 | ```console 34 | [discord] 35 | webhook_url =https://discord.com/api/webhooks/12345678910/qwertyuiopasdfghjklzxcvbnm 36 | channel_name = #channel-name 37 | [telegram] 38 | bot_token =123456789:qwertyuiopasdfghjklzxcvbnm 39 | chat_id =123456789 40 | ``` 41 | 42 | - Open `config.ini` file with any Text-Editor and Replace your own Values 43 | - Remember to see the References Section for Configure you own Webhook and API keys. 44 | 45 | 46 | 47 | # Usage 48 | 49 | To monitor a domain for new subdomains: 50 | 51 | ```console 52 | $ python subhound.py -d example.com 53 | ``` 54 | 55 | This will retrieve the current subdomains for `example.com` and save them to two files in a subdirectory named `example.com_files`. It will then continuously monitor for new subdomains and send notifications to the Discord or/and Telegram channel when new subdomains are found. 56 | 57 | By default, SubHound checks for new subdomains every 60 minutes. You can adjust this interval with the `-i` option: 58 | 59 | ```console 60 | python subhound.py -d example.com -i 30 61 | ``` 62 | This will check for new subdomains every 30 minutes. 63 | 64 | ## Running in the Background 65 | > This part for Bug Hunters and Security Reseachers 66 | 67 | To run SubHound in the background so that it continues to run even if you close your VPS's SSH session, you can use the `tmux` command. `tmux` allows you to create and manage terminal sessions, and you can detach from a session to leave it running in the background. 68 | 69 | To start a new `tmux` session: 70 | 71 | ```console 72 | $ tmux new-session -s subhound 73 | ``` 74 | 75 | This will create a new `tmux` session named "subhound". You can now run the SubHound command as usual: 76 | 77 | ```console 78 | $ python subhound.py -d example.com 79 | ``` 80 | 81 | To detach from the `tmux` session and leave it running in the background, press `Ctrl-b` and then `d`. 82 | 83 | To reattach to the `tmux` session later: 84 | 85 | ```console 86 | $ tmux attach -t subhound 87 | ``` 88 | 89 | This will reattach to the "subhound" session and allow you to view the output of the SubHound command. 90 | 91 | ## References 92 | 93 | - [Creating Discord webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) 94 | - [Creating Telegram bot](https://core.telegram.org/bots#3-how-do-i-create-a-bot) 95 | 96 | # Credits 97 | - Inspired by: [GitHound](https://github.com/tillson/git-hound) and [Sublert](https://github.com/yassineaboukir/sublert) 98 | 99 | 100 | [![Buy me a coffee][buymeacoffee-shield]][buymeacoffee] 101 | 102 | [buymeacoffee]: https://www.buymeacoffee.com/sl4x0 103 | [buymeacoffee-shield]: https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png 104 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [discord] 2 | webhook_url =https://discord.com/api/webhooks/12345678910/qwertyuiopasdfghjklzxcvbnm 3 | channel_name = #channel-name 4 | [telegram] 5 | bot_token =123456789:qwertyuiopasdfghjklzxcvbnm 6 | chat_id =123456789 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | argparse 2 | configparser 3 | pandas 4 | requests 5 | -------------------------------------------------------------------------------- /static/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sl4x0/SubHound/d0a3fada74ec73e486aad57f2bccd157bf8675c8/static/cover.png -------------------------------------------------------------------------------- /subhound.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import pandas as pd 3 | import time 4 | import configparser 5 | import os 6 | import argparse 7 | import sys 8 | import logging 9 | 10 | 11 | BANNER = ''' 12 | _____ __ __ __ __ 13 | / ___/__ __/ /_ / / / /___ __ ______ ____/ / 14 | \__ \/ / / / __ \/ /_/ / __ \/ / / / __ \/ __ / 15 | ___/ / /_/ / /_/ / __ / /_/ / /_/ / / / / /_/ / 16 | /____/\__,_/_.___/_/ /_/\____/\__,_/_/ /_/\__,_/ 17 | 18 | By: Abdelrhman Allam (@sl4x0) 19 | 20 | ''' 21 | print(BANNER) 22 | 23 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') 24 | 25 | 26 | def get_subdomains(domain): 27 | try: 28 | # Retrieve the subdomains for a domain from crt.sh 29 | url = f'https://crt.sh/?q=%25.{domain}&output=json' 30 | response = requests.get(url) 31 | response.raise_for_status() 32 | subdomains = response.json() 33 | 34 | # Extract the subdomains from the response 35 | subdomain_list = [entry['name_value'] for entry in subdomains] 36 | 37 | return subdomain_list 38 | 39 | except requests.exceptions.RequestException as e: 40 | logging.error(f'🛑 Error getting subdomains for {domain}: {e}') 41 | sys.exit(1) 42 | 43 | 44 | def save_subdomains_to_file(subdomains, filename): 45 | try: 46 | # Load the existing subdomains from the file 47 | with open(filename, 'r') as f: 48 | existing_subdomains = set(f.read().splitlines()) 49 | except FileNotFoundError: 50 | existing_subdomains = set() 51 | 52 | # Save any new subdomains to the file 53 | with open(filename, 'a') as f: 54 | for subdomain in subdomains: 55 | if not subdomain.startswith('*') and not subdomain.startswith('.'): 56 | if subdomain not in existing_subdomains: 57 | f.write(f'{subdomain}\n') 58 | existing_subdomains.add(subdomain) 59 | 60 | # Sort the subdomains and remove duplicates 61 | with open(filename, 'r') as f: 62 | subdomains = f.read().splitlines() 63 | subdomains = sorted(set(subdomains)) 64 | with open(filename, 'w') as f: 65 | f.write('\n'.join(subdomains)) 66 | 67 | 68 | def send_discord_message(channel_name, message, webhook_url): 69 | # Send a message to the specified Discord channel using the specified webhook URL 70 | payload = {'content': message, 'username': 'Subdomain Monitor'} 71 | headers = {'Content-Type': 'application/json'} 72 | response = requests.post(webhook_url, json=payload, headers=headers) 73 | response.raise_for_status() 74 | 75 | 76 | def send_telegram_message(bot_token, chat_id, message): 77 | # Send a message to the specified Telegram chat using the specified bot token 78 | url = f'https://api.telegram.org/bot{bot_token}/sendMessage' 79 | params = {'chat_id': chat_id, 'text': message} 80 | response = requests.post(url, params=params) 81 | response.raise_for_status() 82 | 83 | 84 | def send_telegram_message(bot_token, chat_id, message): 85 | # Send a message to the specified Telegram chat using the specified bot token 86 | url = f'https://api.telegram.org/bot{bot_token}/sendMessage' 87 | params = {'chat_id': chat_id, 'text': message} 88 | response = requests.post(url, params=params) 89 | response.raise_for_status() 90 | 91 | 92 | def check_for_new_subdomains(domain, config, interval=60): 93 | # Retrieve the Discord and Telegram details from the configuration 94 | try: 95 | discord_webhook_url = config.get('discord', 'webhook_url') 96 | discord_channel_name = config.get('discord', 'channel_name') 97 | telegram_bot_token = config.get('telegram', 'bot_token') 98 | telegram_chat_id = config.get('telegram', 'chat_id') 99 | except configparser.Error as e: 100 | logging.error(f'Error reading configuration file: {e}') 101 | sys.exit(1) 102 | 103 | # Create a directory for the domain's files 104 | directory = f"{domain}_files" 105 | if not os.path.exists(directory): 106 | os.makedirs(directory) 107 | 108 | # Define the filenames for the subdomains and new subdomains 109 | subdomains_filename = f'{directory}/{domain}_subdomains.txt' 110 | new_subdomains_filename = f'{directory}/{domain}_new_subdomains.txt' 111 | 112 | # Retrieve the existing subdomains from the file 113 | try: 114 | with open(subdomains_filename, 'r') as f: 115 | existing_subdomains = set(f.read().splitlines()) 116 | except FileNotFoundError: 117 | existing_subdomains = set() 118 | 119 | while True: 120 | # Retrieve the subdomains for the domain 121 | subdomains = get_subdomains(domain) 122 | 123 | # Save the subdomains to a file 124 | save_subdomains_to_file(subdomains, subdomains_filename) 125 | 126 | # Check for new subdomains 127 | new_subdomains = set(subdomains) - existing_subdomains 128 | 129 | # Save the new subdomains to a file 130 | with open(new_subdomains_filename, 'a') as f: 131 | for subdomain in new_subdomains: 132 | f.write(f'{subdomain}\n') 133 | 134 | # Send a message if there are new subdomains 135 | if new_subdomains: 136 | message = f'New subdomains found for {domain}:\n\n' 137 | message += '\n'.join(sorted(new_subdomains)) 138 | 139 | try: 140 | send_discord_message(discord_channel_name, message, discord_webhook_url) 141 | except Exception as e: 142 | logging.error(f'Error sending Discord message: {e}') 143 | 144 | try: 145 | send_telegram_message(telegram_bot_token, telegram_chat_id, message) 146 | except Exception as e: 147 | logging.error(f'Error sending Telegram message: {e}') 148 | 149 | logging.info(f'New subdomains found for {domain}!') 150 | 151 | # Update the existing subdomains 152 | existing_subdomains |= new_subdomains 153 | 154 | # Wait for the specified interval before checking for new subdomains again 155 | time.sleep(interval) 156 | 157 | if __name__ == '__main__': 158 | parser = argparse.ArgumentParser(description='Monitor subdomains for a domain.') 159 | parser.add_argument('-d', '--domain', type=str, help='Domain to search subdomains for', required=True) 160 | parser.add_argument('--config', type=str, help='the configuration file to use', default='config.ini') 161 | parser.add_argument('--interval', type=int, help='the interval between checks (in seconds)', default=60) 162 | args = parser.parse_args() 163 | 164 | config = configparser.ConfigParser() 165 | config.read(args.config) 166 | check_for_new_subdomains(args.domain, config, args.interval) --------------------------------------------------------------------------------