什么是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('\r', "掃描進度 {}/{}".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("\nHint CMS名稱: {}".format(finger.get("cms"))) print("Hint 指紋文件: {}".format(finger.get("path"))) print("Hint Md5: {}\n".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進行識別,手寫的這種腳本可能因為識別速度和精度問題比不上在線的工具強大,但是我們還是需要嘗試手寫一些工具,對於工具,我們不僅要會用,還要深挖其原理,嘗試自己寫,不能停留在腳本小子,等到技術積累到一定程度,我們手寫的工具也可以成為生產力。