├── requirements.txt ├── LICENSE ├── .gitignore ├── README.md └── spoof_finder.py /requirements.txt: -------------------------------------------------------------------------------- 1 | httpx~=0.27.2 2 | aioconsole~=0.8.0 3 | netaddr~=1.3.0 4 | rich~=13.9.2 5 | git+https://github.com/soxoj/async-search-scraper 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 MatrixOrg 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 | Attribution: 16 | - caida.org: For information on ASN spoofing status. 17 | - arin.net: For obtaining contact information (email, phone) associated with ASNs. 18 | - ipapi.co: For IP geolocation and ASN details based on the target IP. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚨 SpoofFinder 🚨 2 | 3 | **SpoofFinder** is a tool designed to check whether a target ASN (Autonomous System Number) supports IP header modification, commonly referred to as IP spoofing. The tool fetches and analyzes data from multiple sources, providing a comprehensive report on the spoofing status of a given ASN, IP address, or CIDR range. 4 | 5 | ## ⚡️ Features 6 | 7 | - 🛡️ **ASN Spoofing Check**: Determines whether an ASN allows IP header modification (IPHM), indicating whether the ASN supports spoofed packet routing. 8 | - 📊 **Detailed ASN Information**: Retrieves detailed information about an ASN, including country, number of routed IPs, and last spoofing check. 9 | - 📧 **Email and Phone Parsing**: Extracts contact details (email, phone) from public ASN databases. 10 | - 🔍 **Related Links Search**: Performs search engine queries for related server information based on the ASN. 11 | - 🌈 **Rich CLI Output**: Utilizes `rich` for visually appealing, colorful logs and outputs. 12 | 13 | ## 🚀 Quick Run 14 | 15 | Follow these steps to quickly set up and run SpoofFinder: 16 | 17 | 1. **Clone the repository**: 18 | ```bash 19 | git clone https://github.com/MatrixTM/SpoofFinder.git 20 | cd spoof-finder 21 | ``` 22 | 23 | 2. **Install dependencies**: 24 | Ensure you have Python 3.7+ installed. Then, run: 25 | ```bash 26 | pip install -r requirements.txt 27 | ``` 28 | 29 | 3. **Run the tool**: 30 | You can check the spoofing status of an ASN, IP, or CIDR range using the following command: 31 | ```bash 32 | python spoof_finder.py -t AS15169 33 | ``` 34 | 35 | 4. **Interactive Mode**: 36 | If you don't pass any arguments, SpoofFinder will prompt you to input a target interactively: 37 | ```bash 38 | python spoof_finder.py 39 | ``` 40 | 41 | ## 🛠️ Requirements 42 | 43 | SpoofFinder depends on the following Python libraries: 44 | 45 | ```bash 46 | pip install httpx netaddr rich aioconsole git+https://github.com/soxoj/async-search-scraper 47 | ``` 48 | 49 | ## 🖥️ Usage 50 | 51 | SpoofFinder can be run from the command line, passing the target ASN, IP address, or CIDR range as an argument. 52 | 53 | ### Example 54 | 55 | To check if ASN 15169 (Google) supports IP header modification: 56 | 57 | ```bash 58 | python spoof_finder.py -t AS15169 59 | ``` 60 | 61 | You can also use an IP address or CIDR range to find the corresponding ASN and check its spoofing status: 62 | 63 | ```bash 64 | python spoof_finder.py -t 8.8.8.8 65 | ``` 66 | 67 | ### Input Types 68 | 69 | - **ASN**: Autonomous System Number (e.g., `AS15169` or just `15169`). 70 | - **IP Address**: Will resolve the IP to its corresponding ASN and check the spoofing status. 71 | - **CIDR Range**: Supports input of IP ranges in CIDR format (e.g., `8.8.8.0/24`). 72 | 73 | ## 📄 Output Example 74 | 75 | Here is an example of the tool's output: 76 | 77 | ```plaintext 78 | [21:23:25] 🔍 Fetching data for ASN: AS15169... 79 | [21:23:28] 🌍 ASN Name: GOOGLE 80 | 🔢 ASN Number: AS15169 81 | 🌐 Site: google.com 82 | 🏆 ASN Rank: 1790 83 | 🛡️ Spoofable: No 84 | 🌍 Country: USA 85 | 🌐 Client IPv4: 35.194.140.0/24 86 | ⏱️ Last Checked: Dec 21 2017 08:40 AM 87 | 📧 Contact Email: network-abuse@google.com 88 | 📞 Contact Phone: +1-650-253-0000 89 | [21:23:55] 🔗 Related Links: 90 | - https://cloud.google.com/ 91 | - https://console.cloud.google.com/ 92 | - https://cloud.google.com/gcp/ 93 | - https://cloud.google.com/compute/ 94 | - https://www.google.com/about/datacenters/ 95 | - https://cloud.google.com/products/calculator 96 | - https://cloud.google.com/hosting-options/ 97 | - https://www.google.com/about/datacenters/efficiency/ 98 | - https://www.google.com/about/datacenters/locations/ 99 | - https://en.wikipedia.org/wiki/Google_data_centers 100 | - https://cloud.google.com/serverless/ 101 | - https://cloud.google.com/compute/vm-instance-pricing 102 | - https://www.google.com/about/datacenters/gallery/ 103 | - https://blog.google/products/google-cloud/introducing-google-cloud/ 104 | - https://www.google.com/ 105 | - https://support.google.com/?hl=en 106 | - https://accounts.google.com/ 107 | - https://about.google/intl/ALL_us/ 108 | - https://www.google.com/advanced_search 109 | - https://maps.google.com/ 110 | - https://en.wikipedia.org/wiki/Google 111 | - https://www.google.de/ 112 | - https://www.google.es/ 113 | - https://www.google.com.br/ 114 | - https://www.google.ie/intl/en/ 115 | - https://www.google.com.mx/ 116 | - https://www.google.dk/index.html 117 | - https://www.google.com.tw/ 118 | ``` 119 | 120 | ## 📝 About Data Sources 121 | SpoofFinder gathers ASN and IP spoofing data from multiple sources, including: 122 | 123 | - [caida.org](https://caida.org): For information on ASN spoofing status. 124 | - [arin.net](https://arin.net): For obtaining contact information (email, phone) associated with ASNs. 125 | - [ipapi.co](https://ipapi.co): For IP geolocation and ASN details based on the target IP. 126 | 127 | 128 | ## 📁 File Structure 129 | 130 | - `spoof_finder.py`: The main script that handles checking ASN spoofing status and gathering additional information. 131 | - `README.md`: This file, providing project documentation. 132 | - `requirements.txt`: A list of Python libraries and their versions required to run the tool. 133 | - `LICENSE`: The license information for the project. 134 | 135 | ## 📝 License 136 | 137 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 138 | 139 | ## 🤝 Contributing 140 | 141 | We welcome contributions! Feel free to open issues or submit pull requests. Please follow the repository's guidelines for code style and contributions. 142 | 143 | ## 🚧 Future Enhancements 144 | 145 | - ⚙️ Add more data sources to check spoofing capabilities. 146 | - 🗂️ Implement caching to reduce API call overhead for repeated queries. 147 | - 🔧 Improved error handling and log management. 148 | 149 | # 👨‍💻 Best Way to get a server 150 | 151 | aeza 152 | ##### For this subject, the best hosting I found is [Aeza](https://aeza.net/?ref=375036 "Aeza Hosting") 153 | ##### You Can buy hourly 10Gbps & Ryzen 9 Servers with a cheap price 154 | -------------------------------------------------------------------------------- /spoof_finder.py: -------------------------------------------------------------------------------- 1 | from asyncio import new_event_loop, gather 2 | from datetime import datetime 3 | from re import compile 4 | from typing import Optional, List, Dict, Union, Tuple 5 | from aioconsole import ainput 6 | from httpx import AsyncClient 7 | from netaddr import IPNetwork, AddrFormatError 8 | from rich.console import Console 9 | from search_engines import * 10 | 11 | # Constants 12 | USER_AGENT = ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 ' 13 | 'Safari/537.36') 14 | REX_PHONE = compile(r"[+]\d+(?:[-\s]|)[\d\-\s]+") 15 | REX_MAIL = compile(r"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.']\w+)*") 16 | 17 | class SpoofFinder: 18 | def __init__(self, target: str = None, loop=None): 19 | self.logger = Console(force_terminal=True, markup=True, emoji=True, log_path=False) 20 | self.loop = loop or new_event_loop() 21 | self.target = target 22 | self.client = AsyncClient(timeout=10, headers={"User-Agent": USER_AGENT}) 23 | self.search_engines = ( 24 | Google, Yahoo, Aol, Duckduckgo, Startpage, Dogpile, Ask, Mojeek, Qwant 25 | ) 26 | 27 | async def fetch(self, url: str, as_json: bool = True) -> Union[Optional[Dict], Optional[str]]: 28 | """ 29 | Fetches the given URL using httpx and handles exceptions. 30 | Args: 31 | url (str): The URL to fetch. 32 | as_json (bool, optional): Whether to parse the response as JSON. Defaults to True. 33 | Returns: 34 | Union[Optional[Dict], Optional[str]]: The parsed JSON response or the raw text if `as_json` is False. 35 | """ 36 | try: 37 | response = await self.client.get(url) 38 | return response.json() if as_json else response.text 39 | except Exception as e: 40 | self.logger.log(f"[red]Error fetching {url}: {str(e)}") 41 | return None 42 | 43 | @staticmethod 44 | def parse_asn(target: str) -> str: 45 | """ 46 | Parses an ASN from the given target string. 47 | If the target string starts with "AS", it removes the "AS" prefix and returns the remaining string. 48 | If the target string is a digit-only string, it returns the string as it is. 49 | Otherwise, it returns the target string as it is. 50 | Args: 51 | target (str): The target string to parse. 52 | Returns: 53 | str: The parsed ASN string. 54 | """ 55 | if target.lower().startswith("as"): 56 | return target[2:] 57 | return target if target.isdigit() else target 58 | 59 | async def find_links(self, query: str) -> Optional[List[str]]: 60 | """ 61 | Searches the given query using multiple search engines and returns a list of links. 62 | Args: 63 | query (str): The query to search. 64 | Returns: 65 | Optional[List[str]]: A list of links found by the search engines, or None if no links are found. 66 | """ 67 | for engine in self.search_engines: 68 | async with engine(print_func=lambda *args, **kwargs: None) as e: 69 | e.set_headers({'User-Agent': USER_AGENT}) 70 | data = await e.search(query, pages=2) 71 | links = data.links() 72 | if links: 73 | return links 74 | return None 75 | 76 | async def get_asn_info(self, target: str) -> Optional[Dict]: 77 | """ 78 | Fetches the ASN information for the given target string. 79 | Args: 80 | target (str): The target string to fetch ASN information for. 81 | Returns: 82 | Optional[Dict]: The ASN information as a dictionary, or None if no ASN information is found. 83 | """ 84 | return await self.fetch(f"https://ipapi.co/{target}/json/") or None 85 | 86 | async def find_contact(self, asn: str) -> Tuple[Optional[str], Optional[str], Optional[str]]: 87 | """ 88 | Fetches the contact information for the given ASN. 89 | Args: 90 | asn (str): The ASN to fetch contact information for. 91 | Returns: Tuple[Optional[str], Optional[str], Optional[str]]: A tuple containing the contact site, email, 92 | and phone number, or None if no contact information is found. 93 | """ 94 | response = await self.fetch(f"https://rdap.arin.net/registry/autnum/{asn}", as_json=False) 95 | if not response: 96 | return None, None, None 97 | mail_match = REX_MAIL.search(response) 98 | phone_match = REX_PHONE.search(response) 99 | site = mail_match.group(0).split('@')[1] if mail_match else None 100 | return site, mail_match.group(0) if mail_match else None, phone_match.group(0) if phone_match else None 101 | 102 | async def fetch_spoof_data(self, asn: str) -> Tuple[Optional[Dict], Optional[Dict]]: 103 | """ 104 | Fetches the spoofing data for the given ASN from the CAIDA API and the ASRank API. 105 | Args: 106 | asn (str): The ASN to fetch spoofing data for. 107 | Returns: Tuple[Optional[Dict], Optional[Dict]]: A tuple containing two dictionaries. The first dictionary 108 | contains the spoofing data from the CAIDA API, or None if no data is found. The second dictionary contains 109 | the ASRank data, or None if no data is found. 110 | """ 111 | return await gather( 112 | self.fetch(f"https://api.spoofer.caida.org/sessions?asn={asn}"), 113 | self.fetch(f"https://api.asrank.caida.org/v2/restful/asns/{asn}") 114 | ) 115 | 116 | async def handle_asn(self, asn: str) -> None: 117 | """ 118 | Handles the given ASN and fetches data from the CAIDA API and the ASRank API. 119 | Args: 120 | asn (str): The ASN to handle. 121 | Returns: 122 | None 123 | """ 124 | self.logger.log(f"[bold cyan]🔍 Fetching data for ASN: AS{asn}...") 125 | spoof_data, asrank_data = await self.fetch_spoof_data(asn) 126 | if not spoof_data or not asrank_data or not asrank_data.get("data", {}).get("asn"): 127 | return self.logger.log(f"[bold red]❌ No data found for ASN: {asn}") 128 | spoof_data = spoof_data.get("hydra:member", spoof_data) 129 | if not spoof_data: 130 | return self.logger.log(f"[bold red]❌ No data found for ASN: {asn}") 131 | spoof_data = spoof_data[-1] if isinstance(spoof_data, list) else spoof_data 132 | as_name = asrank_data['data']['asn'].get('asnName', 'Unknown') 133 | try: 134 | last_check = datetime.strptime(spoof_data.get("timestamp", ''), '%Y-%m-%dT%H:%M:%S+00:00') 135 | except ValueError: 136 | last_check = None 137 | spoofable_localv4 = spoof_data.get("routedspoof", "") == "received" 138 | spoofable_internetv4 = spoof_data.get("privatespoof", "") == "sent" 139 | spoofable_localv6 = spoof_data.get("routedspoof6", "") == "received" 140 | spoofable_internetv6 = spoof_data.get("privatespoof6", "") == "sent" 141 | ipv4_client = spoof_data.get("client4", "") 142 | ipv6_client = spoof_data.get("client6", "") 143 | asn_val = spoof_data.get("asn4", asn) 144 | asn6 = spoof_data.get("asn6", "") 145 | site, mail, phone = await self.find_contact(asn) 146 | # Log main ASN details 147 | self.logger.log(f"[bold green]🌍 ASN Name: {as_name}") 148 | if asn6: 149 | self.logger.log(f"[bold blue]🔢 ASN6 Number: [cyan]AS{asn6}") 150 | if asn_val: 151 | self.logger.log(f"[bold blue]🔢 ASN Number: [cyan]AS{asn_val}") 152 | if site: 153 | self.logger.log(f"[bold yellow]🌐 Site: {site}") 154 | self.logger.log(f"[bold magenta]🏆 ASN Rank: [cyan]{asrank_data['data']['asn'].get('rank', 'N/A')}") 155 | # Log spoofability details 156 | if spoofable_localv4 or spoofable_internetv4: 157 | labels = [lbl for lbl in ['Local' if spoofable_localv4 else None, 'Internet' if spoofable_internetv4 else None] if lbl] 158 | self.logger.log(f"[bold yellow]🛡️ Spoofable IPv4: [cyan]{', '.join(labels) or 'No'}") 159 | elif spoofable_localv6 or spoofable_internetv6: 160 | labels = [lbl for lbl in ['Local' if spoofable_localv6 else None, 'Internet' if spoofable_internetv6 else None] if lbl] 161 | self.logger.log(f"[bold yellow]🛡️ Spoofable IPv6: [cyan]{', '.join(labels) or 'No'}") 162 | else: 163 | self.logger.log("[bold red]🛡️ Spoofable: [cyan]No") 164 | self.logger.log(f"[bold cyan]🌍 Country: [green]{spoof_data.get('country', 'N/A').upper()}") 165 | # Log client IPs 166 | if ipv4_client: 167 | self.logger.log(f"[bold cyan]🌐 Client IPv4: [yellow]{ipv4_client}") 168 | if ipv6_client: 169 | self.logger.log(f"[bold cyan]🌌 Client IPv6: [yellow]{ipv6_client}") 170 | # Log last check date 171 | if last_check: 172 | self.logger.log(f"[bold cyan]⏱️ Last Checked: [green]{last_check.strftime('%b %d %Y %I:%M %p')}") 173 | else: 174 | self.logger.log("[bold cyan]⏱️ Last Checked: [green]N/A") 175 | # Log contact information 176 | if mail: 177 | self.logger.log(f"[bold cyan]📧 Contact Email: [yellow]{mail}") 178 | if phone: 179 | self.logger.log(f"[bold cyan]📞 Contact Phone: [yellow]{phone}") 180 | # Log related links 181 | links = await self.find_links(as_name + " server") 182 | if site: 183 | site_links = await self.find_links(site) 184 | if site_links: 185 | links.extend(site_links) 186 | if links: 187 | self.logger.log("[bold green]🔗 Related Links:") 188 | for link in links: 189 | self.logger.log(f"[yellow]- {link}") 190 | 191 | async def _run(self) -> None: 192 | asn = self.target or (await ainput("Enter ASN, IP, CIDR: ")).strip() 193 | asn = self.parse_asn(asn) 194 | if "/" in asn or "-" in asn: 195 | try: 196 | asn = str(IPNetwork(asn)[0]) 197 | except AddrFormatError as e: 198 | self.logger.log(f"[red]Invalid CIDR/Range: {str(e)}") 199 | return 200 | if not asn.isdigit(): 201 | asn_info = await self.get_asn_info(asn) 202 | if not asn_info or not asn_info.get("asn"): 203 | self.logger.log("[red]No ASN info found.") 204 | return 205 | asn = asn_info["asn"].replace("AS", "") if asn_info.get("asn") else asn 206 | await self.handle_asn(asn) 207 | 208 | def __enter__(self): 209 | return self 210 | 211 | def __exit__(self, *args): 212 | self.loop.close() 213 | 214 | def run(self) -> None: 215 | self.loop.run_until_complete(self._run()) 216 | 217 | async def close(self): 218 | await self.client.aclose() 219 | 220 | def main() -> None: 221 | import argparse 222 | parser = argparse.ArgumentParser(description="Spoof Finder CLI") 223 | parser.add_argument('-t', '--target', help="Target ASN, IP, or CIDR", required=False) 224 | args = parser.parse_args() 225 | with SpoofFinder(args.target) as spoof_finder: 226 | spoof_finder.run() 227 | 228 | if __name__ == "__main__": 229 | main() 230 | --------------------------------------------------------------------------------