├── requirements.txt ├── utils ├── __init__.py ├── loader.py ├── request.py └── CDNEngine.py ├── .gitignore ├── plugins ├── __init__.py ├── DNSDetection │ ├── __init__.py │ └── behaviors.py ├── HTTPHeaderDetection │ ├── __init__.py │ └── behaviors.py ├── WhoisDetection │ ├── __init__.py │ └── behaviors.py ├── ErrorServerDetection │ ├── __init__.py │ └── behaviors.py └── SubdomainDetection │ ├── __init__.py │ └── behaviors.py ├── DISCLAIMER.md ├── setup.py ├── LICENCE.txt ├── README.md └── whichCDN /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | __pycache__/ 4 | -------------------------------------------------------------------------------- /plugins/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from . import * # import all plugins 4 | -------------------------------------------------------------------------------- /DISCLAIMER.md: -------------------------------------------------------------------------------- 1 | # DISCLAIMER.MD 2 | 3 | This tool has been developed as part of my work at BSSI consulting and cybersecurity. 4 | 5 | For any further information, please contact us at contact@bssi.fr 6 | -------------------------------------------------------------------------------- /plugins/DNSDetection/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | DNSDetection plugin performs CDN detection through nslookup results. 5 | """ 6 | 7 | from plugins.DNSDetection.behaviors import detect 8 | -------------------------------------------------------------------------------- /plugins/HTTPHeaderDetection/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | HTTPHeaderDetection plugin performs CDN detection by assessing HTTP headers. 5 | """ 6 | 7 | from plugins.HTTPHeaderDetection.behaviors import detect 8 | -------------------------------------------------------------------------------- /plugins/WhoisDetection/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Whois Detection plugin performs CDN detection by analyzing data returned by 5 | whois command's. 6 | """ 7 | 8 | from plugins.WhoisDetection.behaviors import detect 9 | -------------------------------------------------------------------------------- /plugins/ErrorServerDetection/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | ErrorServerDetection plugin performs CDN detection when attempts to access 5 | the web server via its IP address fail and disclose information about 6 | the CDN in place. 7 | """ 8 | 9 | from plugins.ErrorServerDetection.behaviors import detect 10 | -------------------------------------------------------------------------------- /plugins/SubdomainDetection/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | CDN are, sometimes, deployed on a specific subdomain. SubdomainDetection 5 | plugin performs CDN detection by trying to access this specific subdomain and 6 | by analyzing its DNS. 7 | """ 8 | 9 | from plugins.SubdomainDetection.behaviors import detect 10 | -------------------------------------------------------------------------------- /plugins/WhoisDetection/behaviors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | import sys 5 | from utils import CDNEngine 6 | 7 | if sys.version_info >= (3, 0): 8 | import subprocess as commands 9 | import urllib.parse as urlparse 10 | else: 11 | import commands 12 | import urlparse 13 | 14 | def detect(hostname): 15 | """ 16 | Performs CDN detection through whois command's. 17 | 18 | Parameters 19 | ---------- 20 | hostname : str 21 | Hostname to assess 22 | """ 23 | 24 | print('[+] Whois detection\n') 25 | 26 | hostname = urlparse.urlparse(hostname).netloc 27 | 28 | out = commands.getoutput("whois " + hostname) 29 | 30 | CDNEngine.find(out.lower()) 31 | -------------------------------------------------------------------------------- /plugins/SubdomainDetection/behaviors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | import sys 5 | from utils import CDNEngine 6 | 7 | if sys.version_info >= (3, 0): 8 | import subprocess as commands 9 | import urllib.parse as urlparse 10 | else: 11 | import commands 12 | import urlparse 13 | 14 | def detect(hostname): 15 | """ 16 | Performs CDN detection by trying to access the cdn subdomain of the 17 | specified hostname. 18 | 19 | Parameters 20 | ---------- 21 | hostname : str 22 | Hostname to assess 23 | """ 24 | 25 | print('[+] CDN subdomain detection\n') 26 | 27 | hostname = "cdn." + urlparse.urlparse(hostname).netloc 28 | 29 | out = commands.getoutput("host -a " + hostname) 30 | 31 | CDNEngine.find(out.lower()) 32 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | from setuptools import setup, find_packages 5 | from setuptools.command.install import install 6 | 7 | setup( 8 | name='whichCDN', 9 | version='1.0', 10 | description='WhichCDN allows to detect if a given website is protected by a Content Delivery Network', 11 | author_email='nitrax@lokisec.fr', 12 | author='Nitrax', 13 | license='MIT', 14 | packages=find_packages(), 15 | url='https://github.com/Nitr4x/whichCDN', 16 | scripts=['whichCDN'], 17 | classifiers=[ 18 | 'Development Status :: 5 - Production/Stable', 19 | 'License :: MIT', 20 | 'Operating system :: Unix', 21 | 'Operating system :: Windows', 22 | 'Operating system :: MacOS', 23 | 'Programming Language :: Python :: 2.7', 24 | 'Programming Language :: Python :: 3' 25 | ], 26 | install_requires=[ 27 | 'requests' 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /plugins/DNSDetection/behaviors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | import sys 5 | import re 6 | from utils import CDNEngine 7 | 8 | if sys.version_info >= (3, 0): 9 | import subprocess as commands 10 | import urllib.parse as urlparse 11 | else: 12 | import commands 13 | import urlparse 14 | 15 | def detect(hostname): 16 | """ 17 | Performs CDN detection through the DNS, using the nslookup command. 18 | 19 | Parameters 20 | ---------- 21 | hostname : str 22 | Hostname to assess 23 | """ 24 | 25 | print('[+] DNS detection\n') 26 | 27 | hostname = urlparse.urlparse(hostname).netloc 28 | regexp = re.compile('\\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\\b') 29 | 30 | out = commands.getoutput("host " + hostname) 31 | addresses = regexp.finditer(out) 32 | 33 | for addr in addresses: 34 | CDNEngine.find(commands.getoutput('nslookup ' + addr.group())) 35 | -------------------------------------------------------------------------------- /utils/loader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import imp 4 | import os 5 | 6 | PluginFolder = "./plugins" 7 | MainModule = "__init__" 8 | 9 | def getPlugins(): 10 | """ 11 | List the plugins located in the plugins folder. 12 | """ 13 | 14 | plugins = [] 15 | pluginList = os.listdir(PluginFolder) 16 | for pluginName in pluginList: 17 | location = os.path.join(PluginFolder, pluginName) 18 | if not os.path.isdir(location) or not MainModule + ".py" in os.listdir(location): 19 | continue 20 | info = imp.find_module(MainModule, [location]) 21 | plugins.append({"name": pluginName, "info": info}) 22 | return plugins 23 | 24 | def loadPlugin(plugin): 25 | """ 26 | Loads the specified plugin. 27 | 28 | Parameters 29 | ---------- 30 | plugin : Plugin 31 | Plugin to load 32 | 33 | Return 34 | ------ 35 | The plugin loaded 36 | """ 37 | 38 | return imp.load_module(MainModule, *plugin["info"]) 39 | -------------------------------------------------------------------------------- /utils/request.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | import signal 5 | import requests 6 | 7 | class TimeoutException(Exception): 8 | """ 9 | Exception called on timeouts. 10 | """ 11 | pass 12 | 13 | def requestTimeout(signum, frame): 14 | """ 15 | Request timeout. 16 | """ 17 | 18 | raise TimeoutException() 19 | 20 | def do(hostname): 21 | """ 22 | Performs a GET request. 23 | 24 | Parameters 25 | ---------- 26 | hostname : str 27 | Target request 28 | 29 | Return 30 | ------ 31 | The request results 32 | """ 33 | 34 | try: 35 | return requests.get(hostname, timeout=10) 36 | 37 | except TimeoutException: 38 | print("\033[1;31mRequest timeout: test aborted\n\033[1;m") 39 | return None 40 | 41 | except requests.ConnectionError: 42 | print("\033[1;31mServer not found: test aborted\n\033[1;m") 43 | return None 44 | 45 | finally: 46 | signal.alarm(0) 47 | -------------------------------------------------------------------------------- /plugins/ErrorServerDetection/behaviors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | import sys 5 | import re 6 | from utils import CDNEngine 7 | from utils import request 8 | 9 | if sys.version_info >= (3, 0): 10 | import subprocess as commands 11 | import urllib.parse as urlparse 12 | else: 13 | import commands 14 | import urlparse 15 | 16 | def detect(hostname): 17 | """ 18 | Performs CDN detection thanks to information disclosure from server error. 19 | 20 | Parameters 21 | ---------- 22 | hostname : str 23 | Hostname to assess 24 | """ 25 | 26 | print('[+] Error server detection\n') 27 | 28 | hostname = urlparse.urlparse(hostname).netloc 29 | regexp = re.compile('\\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\\b') 30 | 31 | out = commands.getoutput("host " + hostname) 32 | addresses = regexp.finditer(out) 33 | 34 | for addr in addresses: 35 | res = request.do('http://' + addr.group()) 36 | if res is not None and res.status_code == 500: 37 | CDNEngine.find(res.text.lower()) 38 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Nitrax - nitrax@lokisec.fr 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 | # whichCDN 2 | 3 | WhichCDN allows to detect if a given website is protected by a Content Delivery Network 4 | 5 | ## Requirements 6 | 7 | Install the necessary python packages. 8 | 9 | ``` 10 | pip install -r requirements.txt 11 | ``` 12 | 13 | ## Usage 14 | 15 | To scan a website, run whichCDN followed by the target URL or its domain name 16 | 17 | ``` 18 | whichCDN http://www.example.com | example.com 19 | ``` 20 | 21 | ## CDN supported 22 | 23 | * Cloudflare 24 | * Incapsula 25 | * Cloudfront 26 | * Akamai 27 | * Airee 28 | * CacheFly 29 | * EdgeCast 30 | * MaxCDN 31 | * Beluga 32 | * Limelight 33 | * Fastly 34 | * Myracloud 35 | * Microsft Azure 36 | 37 | ## Todo 38 | 39 | * Azion 40 | * ArvanCloud 41 | * Beluga 42 | * DN77 43 | * CDNetwork 44 | * CDNsun 45 | * CDNvideo 46 | * ChinaCache 47 | * ChinaNetCenter 48 | * Highwinds 49 | * KeyCDN 50 | * Level3 51 | * NGENIX 52 | * Quantil 53 | * SkyparkCDN 54 | * Verizon Digital Media services 55 | * Turbobyte 56 | 57 | ## More docs 58 | 59 | Help is available by running ```whichCDN -help``` 60 | 61 | ## Contribution 62 | 63 | Pull requests for new features, bug fixes, and suggestions are welcome ! 64 | -------------------------------------------------------------------------------- /plugins/HTTPHeaderDetection/behaviors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | import sys 5 | from utils import CDNEngine 6 | from utils import request 7 | 8 | if sys.version_info >= (3, 0): 9 | import urllib.parse as urlparse 10 | else: 11 | import urlparse 12 | 13 | def detect(hostname): 14 | """ 15 | Performs CDN detection thanks to HTTP headers. 16 | 17 | Parameters 18 | ---------- 19 | hostname : str 20 | Hostname to assess 21 | """ 22 | 23 | print('[+] HTTP header detection\n') 24 | 25 | hostname = urlparse.urlparse(hostname).scheme + '://' + urlparse.urlparse(hostname).netloc 26 | 27 | fields = { 28 | 'Server': True, 29 | 'X-CDN': True, 30 | 'x-cache': True, 31 | 'X-CDN-Forward': True, 32 | 'Fastly-Debug-Digest': False 33 | } 34 | 35 | res = request.do(hostname) 36 | 37 | if res is None: 38 | return 39 | 40 | for field, state in fields.items(): 41 | value = res.headers.get(field) 42 | if state and value is not None: 43 | CDNEngine.find(value.lower()) 44 | elif not state and value is not None: 45 | CDNEngine.find(field.lower()) 46 | -------------------------------------------------------------------------------- /utils/CDNEngine.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | import sys 5 | 6 | CDN = { 7 | 'Cloudflare': 'Cloudflare - https://www.cloudflare.com', 8 | 'Incapsula': 'Incapsula - https://www.incapsula.com/', 9 | 'Cloudfront': 'Cloudfront - https://aws.amazon.com/cloudfront/', 10 | 'Akamai': 'Akamai - https://akamai.com', 11 | 'Airee': 'Airee - https://airee.international', 12 | 'CacheFly': 'CacheFly - https://www.cachefly.com/', 13 | 'EdgeCast': 'EdgeCast - https://verizondigitalmedia.com', 14 | 'MaxCDN': 'MaxCDN - https://www.maxcdn.com/', 15 | 'Beluga': 'BelugaCDN - https://belugacdn.com', 16 | 'Limelight': 'Limelight - https://www.limelight.com', 17 | 'Fastly': 'Fastly - https://www.fastly.com/', 18 | 'Myracloud': 'Myra - https://myracloud.com', 19 | 'msecnd.ne': 'Microsoft Azure - https://azure.microsoft.com/en-us/services/cdn/', 20 | 'Clever-cloud': 'Clever Cloud - https://www.clever-cloud.com/' 21 | } 22 | 23 | def find(data): 24 | """ 25 | Compares the provided data to the CDN supported. 26 | 27 | Parameters 28 | ---------- 29 | data : str 30 | Data to analyze 31 | """ 32 | 33 | for keyword, description in CDN.items(): 34 | if data.find(keyword.lower()) != -1: 35 | print('\033[1;32mCDN found: ' + description + '\033[1;m\n') 36 | sys.exit(0) 37 | -------------------------------------------------------------------------------- /whichCDN: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | import argparse 5 | import signal 6 | import sys 7 | 8 | from utils import loader 9 | from utils import request 10 | 11 | if sys.version_info >= (3, 0): 12 | import urllib.parse as urlparse 13 | else: 14 | import urlparse 15 | 16 | def gracefulExit(signal, frame): 17 | sys.exit(0) 18 | 19 | def parser(): 20 | """ 21 | Parse arguments. 22 | """ 23 | 24 | parser = argparse.ArgumentParser(description="""\ 25 | WhichCDN allows to detect if a given website is protected by a Content 26 | Delivery Network.\r Fell free to contact the maintainer for any further 27 | questions or improvement vectors.\r Maintained by Nitrax 28 | 29 | """) 30 | 31 | parser.add_argument('target', type=str, help='hostname to scan') 32 | 33 | parser.parse_args() 34 | 35 | def sanitizeURL(hostname): 36 | """ 37 | Sanitizes the hostname by adding the http protocol if it has not been 38 | provided. 39 | 40 | Parameters 41 | ---------- 42 | hostname : str 43 | Hostname to assess 44 | 45 | Return 46 | ------ 47 | The hostname sanitized 48 | """ 49 | 50 | components = urlparse.urlparse(hostname) 51 | 52 | hostname = "http://" + hostname if components.scheme == '' else hostname 53 | return hostname 54 | 55 | 56 | if __name__ == "__main__": 57 | 58 | print(""" 59 | __ __.__ .__ .__ _________ ________ _______ 60 | / \ / \ |__ |__| ____ | |__ \_ ___ \\\\______ \ \ \\ 61 | \ \/\/ / | \| |/ ___\| | \/ \ \/ | | \ / | \\ 62 | \ /| Y \ \ \___| Y \ \____| ` \/ | \\ 63 | \__/\ / |___| /__|\___ >___| /\______ /_______ /\____|__ / 64 | \/ \/ \/ \/ \/ \/ \/ 65 | """) 66 | 67 | parser() 68 | 69 | signal.signal(signal.SIGALRM, request.requestTimeout) 70 | signal.signal(signal.SIGINT, gracefulExit) 71 | signal.alarm(5) 72 | 73 | hostname = sanitizeURL(sys.argv[1]) 74 | 75 | for module in loader.getPlugins(): 76 | plugin = loader.loadPlugin(module) 77 | plugin.detect(hostname) 78 | 79 | print('\033[1;31mNo CDN found\033[1;m') 80 | --------------------------------------------------------------------------------