├── utils ├── __init__.py └── tools.py ├── .gitignore ├── .gitattributes ├── README.md └── cms.py /utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.pyc 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fingerprint 2 | 3 | 一个简单的指纹识别小工具 4 | 5 | ## 指纹识别方式 6 | 7 | 本着偷懒的原则,直接采用的ast.parse 解析之后通过eval 来进行参数转换, 8 | 这样做的好处呢就是每一条指纹规则我只需要if 判断就行。 9 | 不需要在考虑里面是否存在and or () 这种多条件判断。 10 | 11 | ``` 12 | "product": "Spring-Boot-Admin", 13 | "rule": "\"spring boot admin\" in body and title==\"Spring boot admin\"", 14 | ``` 15 | 16 | 就拿这条指纹来说,通过eval执行之后 就会判断,字符串 spring boot admin 是否在我们传入的html里面 17 | 和 title等于 Spring boot admin 这个字符串,如果匹配成功 就返回Spring-Boot-Admin 18 | 19 | 然后又定义了一个flag,如果识别到这个指纹flag就+1,这样常用指纹识别的效率就越来越快,同理不常用的越来越慢。 20 | 21 | ### 使用方式 22 | 23 | python3 cms.py -u https://www.baiud.com 24 | 25 | python3 cms.py -f url.txt 26 | -------------------------------------------------------------------------------- /cms.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | import asyncio 3 | from utils.tools import titles_html, FingerPrintcms, parse_args, banner, readurlfile, save_csv 4 | from loguru import logger 5 | 6 | 7 | class Reqfing(object): 8 | def __init__(self): 9 | self.cms = FingerPrintcms() 10 | self.headers = {"Upgrade-Insecure-Requests": "1", 11 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5249.62 Safari/537.36", 12 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", 13 | "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"} 14 | self.args = parse_args() 15 | 16 | async def get_fingerprint(self, url, sem): 17 | async with sem: 18 | async with aiohttp.ClientSession(headers=self.headers) as session: 19 | try: 20 | async with session.get(url, ssl=False, timeout=self.args.t) as resp: 21 | title = await titles_html(await resp.text()) 22 | zhiwenname = await self.parser(title, resp.headers, await resp.text()) 23 | print(resp.status, title, zhiwenname, url) 24 | return [resp.status, title, zhiwenname, url] 25 | except Exception as e: 26 | print(e) 27 | 28 | async def parser(self, title, header, body): 29 | # 遍历规则字符串列表并解析 30 | name = self.cms.astz(title, header, body) 31 | return name 32 | 33 | async def start(self): 34 | sem = asyncio.Semaphore(int(self.args.sem)) 35 | if self.args.f != None and self.args.u != None: 36 | banner() 37 | logger.debug("请输入单个url或者文件") 38 | 39 | elif self.args.f != None: 40 | try: 41 | banner() 42 | urls = readurlfile(self.args.f) 43 | logger.info("开始进行批量cms识别 共计{}个".format(len(urls))) 44 | tasks = [asyncio.create_task(self.get_fingerprint(url, sem)) for url in urls] 45 | done, pending = await asyncio.wait(tasks) 46 | data = [result.result() for result in done] 47 | save_csv(data) 48 | 49 | except Exception as e: 50 | logger.error(e) 51 | elif self.args.u != None: 52 | try: 53 | banner() 54 | logger.info("开始进行单个cms识别") 55 | target = self.args.u 56 | if str(target).startswith(("http://", "https://")): 57 | data = await self.get_fingerprint(target, sem) 58 | self.cms.update() 59 | else: 60 | logger.debug("python3 cms.py -u https://www.baidu.com。") 61 | 62 | except Exception as e: 63 | logger.error(e) 64 | else: 65 | logger.info('python3 cms.py -h 查看帮助') 66 | 67 | async def main(self): 68 | await self.start() 69 | 70 | 71 | if __name__ == '__main__': 72 | import time 73 | 74 | start = time.time() 75 | test = Reqfing() 76 | asyncio.run(main=test.main()) 77 | print(time.time() - start) 78 | -------------------------------------------------------------------------------- /utils/tools.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import re 4 | import ast 5 | import operator 6 | import csv 7 | 8 | 9 | # 读取指纹文件 10 | def readfilelist(): 11 | with open("data/cms.json", 'r') as f: 12 | mark_list = json.load(f) 13 | jsonlist = sorted(mark_list, key=operator.itemgetter('flag'), reverse=True) 14 | return jsonlist 15 | 16 | 17 | # 更新指纹文件 18 | def updatefilelist(data): 19 | with open("data/cms.json", 'w') as f: 20 | json.dump(data, f, ensure_ascii=False) 21 | 22 | 23 | # 读取批量扫描的文件 24 | def readurlfile(filename): 25 | with open(filename, 'r', encoding='utf-8') as fp: 26 | wordlist = fp.read().splitlines() 27 | urls = set( 28 | url.strip() if url.strip().startswith(('http://', 'https://')) else ''.join(('http://', url.strip())) 29 | for url in wordlist if len(url) > 1) 30 | return urls 31 | 32 | 33 | # 通过正则匹配 获取title 34 | async def titles_html(html): 35 | titles = ( 36 | "(.*)", 37 | 'document.title\s=\s"(.*?)"', 38 | 'document.title="(.*?)"', 39 | '(.*?)', 40 | '', 41 | '(.*?)', 42 | '

(.*)

', 43 | ) 44 | html = html.replace(' ', '') 45 | for ti in titles: 46 | title = re.findall(ti, html, re.S | re.I) 47 | if len(title) == 0: 48 | pass 49 | elif len(title) >= 1: 50 | if len(title[0]) >= 1: 51 | titlename = str(title[0]).replace('\r', '').replace('\n', '').strip() 52 | return titlename 53 | elif len(title) >= 2: 54 | titlename = str(title[1]).replace('\r', '').replace('\n', '').strip() 55 | return titlename 56 | 57 | 58 | class FingerPrintcms(object): 59 | def __init__(self): 60 | self.fingerprintlist = readfilelist() 61 | 62 | def astz(self, title, header, body): 63 | for index, item in enumerate(self.fingerprintlist): 64 | # 解析规则字符串 65 | try: 66 | rule = item['rule'] 67 | parsed = ast.parse(rule, mode='eval') 68 | titlename = eval(compile(parsed, '', 'eval'), {'title': title, 'body': body, 'header': header}) 69 | if titlename: 70 | self.fingerprintlist[index]['flag'] += 1 71 | return item['product'] 72 | except Exception as e: 73 | print(e, item) 74 | 75 | # 更新指纹文件纹 76 | def update(self): 77 | updatefilelist(self.fingerprintlist) 78 | 79 | 80 | # 命令行参数 81 | def parse_args(): 82 | parser = argparse.ArgumentParser() 83 | parser.add_argument('-u', help='请输入目标url ', metavar='') 84 | parser.add_argument('-f', help='请输入需要批量扫描的文件', metavar='') 85 | parser.add_argument('-t', help='超时时间', metavar='', default=60) 86 | parser.add_argument('-sem', help='异步sem 数量', metavar='', default=20) 87 | args = parser.parse_args() 88 | return args 89 | 90 | 91 | # 定义banner 92 | def banner(): 93 | print(''' 94 | __ _ __ ___ 95 | / _| | ' \ (_-< 96 | \__|_ |_|_|_| /__/_ 97 | _|"""""|_|"""""|_|"""""| 98 | "`-0-0-'"`-0-0-'"`-0-0-' 99 | 100 | Author:有觉悟的菜鸡顾北''') 101 | 102 | 103 | # 保存到csv文件 104 | def save_csv(data): 105 | with open('result.csv', 'w', newline='') as f: 106 | writer = csv.writer(f) 107 | writer.writerow(['状态码', '网站名称', "指纹", "网站"]) 108 | try: 109 | 110 | for item in data: 111 | if item != None: 112 | writer.writerow(item) 113 | else: 114 | pass 115 | except Exception as e: 116 | 117 | print(e) 118 | 119 | 120 | if __name__ == '__main__': 121 | ss = readfilelist() 122 | # print(ss) 123 | --------------------------------------------------------------------------------