├── README.MD ├── __init__.py ├── cdnCheck.py ├── domain.txt ├── imgs └── README │ ├── image-20221202102336233.png │ ├── image-20221202102347816.png │ └── image-20221202102538908.png ├── main.py └── requirements.txt /README.MD: -------------------------------------------------------------------------------- 1 | # FCDN 批量检测脚本 2 | 3 | 更强大的cdn检测,但不止cdn检测。 4 | 5 | ## 如何使用? 6 | 7 | 安装依赖: 8 | ``` 9 | pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt 10 | ``` 11 | 12 | 单个url测试: 13 | 14 | ``` 15 | python cdnCheck.py -u huya.com 16 | ``` 17 | 18 | 批量子域名检测: 19 | 20 | ``` 21 | python main.py -f domain.txt -t 100 22 | ``` 23 | 24 | 返回的结果在 result 目录下,共有三个: 25 | 26 | nocdnResult.txt 27 | cdnResult.txt 28 | errorResult.txt 29 | 30 | ## 它可以做什么? 31 | 32 | 先说优点:**快速**、**精准**、**全面** 33 | 34 | 功能是从一大堆子域名中查找使用了真实IP的子域名,方便使用者精准定位服务器所在网段 35 | 36 | **你可以去对比一下 Oneforall 的cdn识别结果,再来对比该工具的 cdn 识别结果,你会发现 Oneforall 的误报率很高,这就是传统 cdn 检测手段的不足之处。** 37 | 38 | 支持多线程,但不是太重要,因为它的主要作用不是探测子域名,虽然它也可以做到,但不是最好的,在我看来最好的子域名探测工具是 OneForAll。 39 | 40 | 比如使用了云waf、cdn等技术的网站是无法解析真实IP的,而现在大量的网站使用了云waf,这时候我们的技术手段不应停留在单纯的检测cdn上,这是不准确不充分不完整的,所以有了这个工具。 41 | 42 | ## 为什么重要? 43 | 44 | 因为如果能快速准确的识别出使用了真实IP的站点,我们就可以精准的去扫该IP段,从而发现更多的资产,避免误伤和浪费时间。 45 | 46 | ## 检测原理 47 | 48 | 放弃了传统的检测白名单CDN网段/ASN的方式,因为这种方式需要去维护和更新 cdn 网段,不太可靠。 49 | 也不使用第三方的站点来判断。 50 | 转而是使用 Python 的 dnspython 库去判断是否存在 CDN,该库的功能相当于使用 nslookup 命令去解析目标站点。 51 | 52 | 有三种情况下,可以知道目标域名是否使用了真实IP和域名有效性。 53 | 54 | 1. 返回结果多于两个ip 55 | [<104.21.23.10>, <172.67.208.68>] 56 | 这种情况,目标站点一定是使用了cdn服务 57 | 58 | 2. 返回结果里面 nameserver 的主域名不是检测的host的主域名 59 | 比如:www.abc.com 的 CNAME 是 www-abc-com.site.nscloudwaf.com 60 | 出现这种情况是因为目标站点使用了云 waf,这种站点也是获取不到真实IP的。 61 | 62 | 63 | ## 测试 64 | 65 | 在线cdn检测网站 https://myssl.com/cdn_check.html 66 | 67 | 测试了以下几个站点,返回的结果都不同,精准确认 68 | 69 | ``` 70 | giihg.com 云waf 71 | huya.com cdn 72 | www.cip.cc 真实IP 73 | www.csu.edu.cn 使用了 F5 Big-IP 负载均衡 74 | ``` 75 | 76 | 部分测试结果 77 | 78 | ![image-20221202102347816](./imgs/README/image-20221202102347816.png) 79 | 80 | 脚本测试结果: 81 | 82 | ![image-20221202102336233](./imgs/README/image-20221202102336233.png) 83 | 84 | ![image-20221202102538908](./imgs/README/image-20221202102538908.png) 85 | 86 | 脚本测试的结果是否准确: 87 | 88 | type .\result\errorResult.txt | nslookup 89 | 90 | ## 写在最后 91 | 92 | 使用案例:在做某集团的资产梳理的时候,需要扫描其资产的服务器IP端口,我使用该工具快速从一大堆子域名中筛选出了真实IP,进而使用 masscan+nmap 完成了端口扫描,成功避免扫到云waf、cdn IP 污染我的扫描结果,整个过程高效、简洁、快速。这就是它的使用方法。 93 | 94 | 如有错漏,请提到 issues。 95 | 96 | 如果该项目对你有帮助,麻烦点个 star,万分感谢。 97 | 98 | ## 免责声明 99 | 100 | ``` 101 | 本工具仅面向合法授权的企业安全建设行为,在使用本工具进行检测时,您应确保该行为符合当地的法律法规,并且已经取得了足够的授权。 102 | 如您在使用本工具的过程中存在任何非法行为,您需自行承担相应后果,我们将不承担任何法律及连带责任。 103 | 在使用本工具前,请您务必审慎阅读、充分理解各条款内容,限制、免责条款或者其他涉及您重大权益的条款可能会以加粗、加下划线等形式提示您重点注意。 除非您已充分阅读、完全理解并接受本协议所有条款,否则,请您不要使用本工具。 104 | 您的使用行为或者您以其他任何明示或者默示方式表示接受本协议的,即视为您已阅读并同意本协议的约束。 105 | ``` 106 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccc-f/FCDN/7c914fc2117936df4a68d825e6ddb857df4ce0e4/__init__.py -------------------------------------------------------------------------------- /cdnCheck.py: -------------------------------------------------------------------------------- 1 | import dns.resolver 2 | import argparse 3 | import re 4 | 5 | def cdn_check(domain): 6 | """ 7 | if cdn return True,domain 8 | else return False,domain 9 | """ 10 | ipcount = 0 11 | try: 12 | resolver = dns.resolver.Resolver() 13 | resolver.nameservers = ['1.1.1.1', '8.8.8.8'] 14 | domain = domain.strip() 15 | a = resolver.resolve(domain, 'A') 16 | for index,value in enumerate(a.response.answer): 17 | for j in value.items: 18 | if re.search(r'\d+\.\d+\.\d+\.\d+', j.to_text()): 19 | ipcount += 1 20 | if ipcount >= 2: 21 | return True,domain 22 | elif re.search(r'(\w+\.)+', j.to_text()): 23 | cname = j.to_text()[:-1] 24 | p1 = '.'.join(cname.split('.')[-2:]) 25 | p2 = '.'.join(domain.split('.')[-2:]) 26 | if p1 == p2: 27 | return False,domain 28 | else: 29 | return True,domain 30 | else: 31 | return False,domain 32 | if ipcount == 1: 33 | return False,domain 34 | except Exception as e: 35 | return None,domain 36 | 37 | if __name__ == "__main__": 38 | parser = argparse.ArgumentParser(description='by Scrboy.') 39 | parser.add_argument('-u', '--url', type=str, default='huya.com') 40 | args = parser.parse_args() 41 | domain = args.url 42 | print(cdn_check(domain)) -------------------------------------------------------------------------------- /domain.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccc-f/FCDN/7c914fc2117936df4a68d825e6ddb857df4ce0e4/domain.txt -------------------------------------------------------------------------------- /imgs/README/image-20221202102336233.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccc-f/FCDN/7c914fc2117936df4a68d825e6ddb857df4ce0e4/imgs/README/image-20221202102336233.png -------------------------------------------------------------------------------- /imgs/README/image-20221202102347816.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccc-f/FCDN/7c914fc2117936df4a68d825e6ddb857df4ce0e4/imgs/README/image-20221202102347816.png -------------------------------------------------------------------------------- /imgs/README/image-20221202102538908.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccc-f/FCDN/7c914fc2117936df4a68d825e6ddb857df4ce0e4/imgs/README/image-20221202102538908.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import concurrent.futures 2 | from concurrent.futures import ThreadPoolExecutor 3 | from cdnCheck import cdn_check 4 | import argparse 5 | import os 6 | 7 | def ThreadPool(func,urls,max_workers): 8 | with ThreadPoolExecutor(max_workers=max_workers) as executor: 9 | to_do = [] 10 | for url in urls: 11 | obj = executor.submit(func, url) 12 | to_do.append(obj) 13 | for future in concurrent.futures.as_completed(to_do): 14 | result, domain = future.result() 15 | if not os.path.exists('./result/'): 16 | os.mkdir('./result/') 17 | if result is False: 18 | savefile('./result/nocdnResult.txt',domain) 19 | elif result is True: 20 | savefile('./result/cdnResult.txt',domain) 21 | else: 22 | savefile('./result/errorResult.txt',domain) 23 | 24 | def savefile(filename, data): 25 | with open(filename, 'a', encoding='utf-8')as f: 26 | f.write(data + '\n') 27 | 28 | def readfile(filename): 29 | urls = [] 30 | with open(filename,'r',encoding='utf-8')as f: 31 | data = f.read() 32 | urls = data.strip().split() 33 | return urls 34 | 35 | if __name__ == "__main__": 36 | parser = argparse.ArgumentParser(description='by Scrboy.') 37 | parser.add_argument('-f', '--file', type=str, default='domain.txt') 38 | parser.add_argument('-t', '--thread',type=int, default=50) 39 | args = parser.parse_args() 40 | domain = args.file 41 | thread_count = args.thread 42 | urls = list(set(readfile(domain))) 43 | ThreadPool(cdn_check,urls,max_workers=thread_count) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | dnspython==2.2.1 --------------------------------------------------------------------------------