什么是cms
CMS是Content Management System的缩写,意为"内容管理系统",这是百度百科的解释,意思是相当于网站的建站模板,整个网站架构已经集成好了,只需要你部署和安装,便可以搭起一个网站,这样虽然方便了开发人员建设网站,但也带来新的安全问题,如果cms本身有安全问题如一些高危漏洞,那么使用这个cms的所有网站都会存在这种安全漏洞,所以我们在进行渗透测试的时候,如果可以探测出网站使用的框架,那么我们便可以寻找这个框架的漏洞,从而成功拿下这个网站,所以在渗透过程中,探测网站的cms是一件很重要的事,网上有开源的常见的cms的字典库,我们可以手写一个cms识别程序来对cms进行识别,方便我们进行渗透。
常见的指纹识别方式
1、特定文件的MD5
一些网站的特定图片文件、js文件、CSS等静态文件,如favicon.ico、css、logo.ico、js等文件一般不会修改,通过爬虫对这些文件进行抓取并比对md5值,如果和规则库中的Md5一致则说明是同一CMS。这种方式速度比较快,误报率相对低一些,但也不排除有些二次开发的CMS会修改这些文件。
2、正常页面或错误网页中包含的关键字
先访问首页或特定页面如robots.txt等,通过正则的方式去匹配某些关键字,如Powered by Discuz、dedecms等。
或者可以构造错误页面,根据报错信息来判断使用的CMS或者中间件信息,比较常见的如tomcat的报错页面。
3、请求头信息的关键字匹配
根据网站response返回头信息进行关键字匹配,whatweb和Wappalyzer就是通过banner信息来快速识别指纹,之前fofa的web指纹库很多都是使用的这种方法,效率非常高,基本请求一次就可以,但搜集这些规则可能会耗时很长。而且这些banner信息有些很容易被改掉。
根据response header一般有以下几种识别方式:
- 查看http响应报头的X-Powered-By字段来识别;
- 根据Cookies来进行判断,比如一些waf会在返回头中包含一些信息,如360wzws、Safedog、yunsuo等;
- 根据header中的Server信息来判断,如DVRDVS-Webs、yunjiasu-nginx、Mod_Security、nginx-wallarm等;
- 根据WWW-Authenticate进行判断,一些路由交换设备可能存在这个字段,如NETCORE、huawei、h3c等设备。
4、部分URL中包含的关键字,比如wp-includes、dede等URL关键特征
通过规则库去探测是否有相应目录,或者根据爬虫结果对链接url进行分析,或者对robots.txt文件中目录进行检测等等方式,通过url地址来判别是否使用了某CMS,比如wordpress默认存在wp-includes和wp-admin目录,织梦默认管理后台为dede目录,solr平台可能使用/solr目录,weblogic可能使用wls-wsat目录等。
5、开发语言的识别
web开发语言一般常见的有PHP、jsp、aspx、asp等,常见的识别方式有:
- 通过爬虫获取动态链接进行直接判断是比较简便的方法。
asp判别规则如下<a[^>]*?href=(‘|”)[^http][^>]*?.asp(?|#|1),其他语言可替换相应asp即可。
- 通过X-Powered-By进行识别
比较常见的有X-Powered-By: ASP.NET或者X-Powered-By: PHP/7.1.8
- 通过Set-Cookie进行识别
这种方法比较常见也很快捷,比如Set-Cookie中包含PHPSSIONID说明是php、包含JSESSIONID说明是java、包含ASP.NET_SessionId说明是aspx等。
下面是我在github上找到的一个大佬写的cms脚本识别工具(自己写的就不放了,惨不忍睹),大佬写的很好,可以当做生产力工具来使用了。
import json#字典为json文件格式 import threading import requests import hashlib#用于md5加密 from concurrent.futures import ThreadPoolExecutor, as_completed, FIRST_EXCEPTION, wait, ALL_COMPLETED from optparse import OptionParser #查找cms静态文件,并计算哈希值,获取静态文件的url相对路径,根据此生成cms特征集 threadingLock = threading.Lock() show_count = 0 SCAN_COMPLATED = False def md5encode(text):#将关键字md5加密 m = hashlib.md5() m.update(text.encode("utf-8")) return m.hexdigest() def check_file_is_ok(url, path): """ head 请求方式去判断文件是否存在, 减少正文响应时间 :param url: :param path: :return: """ target = url + path#拼接url和路径 r = requests.head(target) if r.status_code == 200:#判断页面状态码 return True return False def get_request_md5(url, path, pattern): """ 通过请求路径获取内容的md5 :param url: :param path: :param pattern: :return: """ target = url + path r = requests.get(target) r_md5 = md5encode(r.text) if pattern == r_md5: return True return False def load_cms_fingers(fingers): """ 加载CMS指纹 :return: """ with open(fingers) as f: data = json.load(f) print("Update Time: {}".format(data.get("update_time"))) print("CMS Fingers Count: {}".format(len(data['data']))) return data['data'] def read_url_file_to_list(filename): """ 读 URL 文件为列表 :param filename: :return: """ with open(filename) as f: return [x.strip() for x in f.readlines()] def check_thread(item): global show_count#在函数中调用全局变量 global SCAN_COMPLATED url, finger = item path = finger.get("path") path = path if path[0] == "/" else "/" + path threadingLock.acquire() show_count += 1 if not SCAN_COMPLATED: print(' ', "扫描进度 {}/{}".format(show_count, fingers_count), end='', flush=True) threadingLock.release() if check_file_is_ok(url, path): match_pattern = finger.get("match_pattern") result = get_request_md5(url, path, match_pattern) if result: threadingLock.acquire() if not SCAN_COMPLATED: print(" Hint CMS名称: {}".format(finger.get("cms"))) print("Hint 指纹文件: {}".format(finger.get("path"))) print("Hint Md5: {} ".format(finger.get("match_pattern"))) SCAN_COMPLATED = True threadingLock.release() raise Exception("任务结束") threadingLock.release() if __name__ == '__main__': usage = "%prog -u "http://xxxx.com" -t threads_number" parser = OptionParser(usage=usage) print("指纹识别------2.0") parser.add_option("-u", "--url", dest="url", help="目标URL") parser.add_option("-f", "--file", dest="file", help="url文件", default=None) parser.add_option("-s", "--fingers", dest="fingers", help="指定指纹文件", default="fingers_simple.json") parser.add_option("-t", "--threads", dest="threads", type="int", default=10, help="线程大小, 默认为 10") options, args = parser.parse_args() if not options.url and not options.file: parser.print_help() exit(0) fingers = load_cms_fingers(options.fingers) if options.file: urls = read_url_file_to_list(options.file) else: urls = [options.url] for url in urls: SCAN_COMPLATED = False show_count = 0 print(" 扫描目标: {}".format(url)) fingers_count = len(fingers) executor = ThreadPoolExecutor(max_workers=options.threads) tasks = [executor.submit(check_thread, ((url, finger))) for finger in fingers] wait(tasks, return_when=FIRST_EXCEPTION) for task in reversed(tasks): task.cancel() wait(tasks, return_when=ALL_COMPLETED)
大佬的脚本是将cms的md5特征值以json格式存储为一个字典,调用字典对网站进行检测,同时还加入了验证机制,验证文件是否存在,减少响应时间等。大家可以学习下。
很多时候我们习惯用云悉等在线工具对cms进行识别,手写的这种脚本可能因为识别速度和精度问题比不上在线的工具强大,但是我们还是需要尝试手写一些工具,对于工具,我们不仅要会用,还要深挖其原理,尝试自己写,不能停留在脚本小子,等到技术积累到一定程度,我们手写的工具也可以成为生产力。