自己動手打造Github程式碼洩露監控工具之改進篇
0×00 為什麼
自從小弟上次釋出了《 ofollow,noindex">自己動手打造Github程式碼洩露監控工具 》一文後,承蒙各位客官老爺捧場,閱讀量已經好幾十萬了,Github也受到了一些大佬的關注。本著精益求精的黑客精神(其實是上次的工具版本存在許多不足),經過一段時間的執行後,確實工具存在一定精確性的問題,故有此文章。
0×01 怎麼改進
改進主要從以下幾個方面入手:
1.搜尋排序方式變更
上篇文章通過Github搜尋排序Best Match來爬去前面6頁來查詢洩露資訊, 優點在於 :可以匹配到最符合關鍵詞的專案,比如匹配域名為baidu.com的相關程式碼。然後在最匹配的關鍵詞中尋找payload,如password,username等; 缺點較為明顯 :主關鍵詞單一,雖然可以加入多個主關鍵詞,但是匹配結果卻不能收錄最新的記錄或程式碼。最為嚴重的問題是最新洩露的符合關鍵詞的敏感資訊可能無法獲取到。
2.搜尋方式變更
單一的域名關鍵詞搜尋內容有限,Github呈現的結果也非常多。另外Github的內部搜尋演算法的影響也會讓搜尋結果不盡人意。舉個栗子,搜尋baidu.com, Github呈現如下圖:
看圖可知,最匹配的程式碼結果其實可能沒有包含我們想要的諸如password, username, database等敏感資訊。有興趣的童鞋可以自行搜尋。細心的童鞋可能看到收錄的時間五花八門,有6月29的,7月6號的等等。現在是11月了啊,這結果根本不是最新的,不準確性和非及時性的問題體現出來了。即便是搜尋到幾十頁,上百頁,也可能沒有包含我們關注的資訊洩露程式碼,所以必須改進。
3.儲存方式和呈現效果變更
此前Github倉庫的連結爬去後都通過excel來儲存,Python處理這種IO還算效能不差。不過用excel來儲存資訊確實略顯陳舊,新版本則採用sqlite來儲存url和程式碼,這是儲存方式的變更。另外在呈現效果雖然還是通過郵件來呈現,但是加入了程式碼部分的展現,關鍵詞高亮顯示,方便監控者閱讀。具體效果如下:
接下來我們將針對提出的改進要點來修改監控工具~_~
0×02 具體改進方法
1.搜尋排序方式改進
這個要點的變更,其實變更程式碼比較少,只是變更搜尋程式碼的url即可:
https://github.com/search?o=desc&p=2&q=baidu.com&s=indexed&type=Code
通過變更排序方式,把最新收錄的排在前面,這樣就可以得到最新的程式碼收錄資訊。
對比先前版本url:
https://github.com/search?o=desc&q=baidu.com&s=&type=Code
2.搜尋方式改進
搜尋方式由單一關鍵詞更改為關鍵詞組合,例如,公司域名為:“example.com, 需要查詢的敏感關鍵詞為:password,那麼我們組合為:“example.com + password” 進行搜尋。新的改進版本不僅會收錄可能存在程式碼洩露倉庫的url,同時也會收錄簡要程式碼部分。然後在後續加入到郵件報警和資料庫的程式碼中進行example 和 password進行匹配,如果兩個關鍵詞都存在,則再比對資料庫中的基線裡面是否存在對應程式碼的url,如果存在則不加入基線中,反之,加入到基線中。我來看程式碼部分:
def hunter(gUser , gPass, keywords):#根據關鍵詞獲取想要查詢的內容
global codes
global tUrls
try:
#程式碼搜尋
s = login_github(gUser,gPass)
print('登陸成功,正在檢索洩露資訊.......')
sleep(1)
codes = []
tUrls = []
#新加入2條正則匹配,第一條匹配搜尋出來的程式碼部分;第二條則進行高亮顯示關鍵詞
pattern_code = re.compile( r'<div class="file-box blob-wrapper">(.*?)</div> ', re.S)
pattern_sub = re.compile( r'<em>', re.S)
for keyword in keywords:
for page in tqdm(range(1, 7)):
#更改搜尋排序方式的url,收錄可能存在洩漏的url還是使用xpath解析
search_code = ' https://github.com/search?o=desc&p= ' + str(page) + '&q=' + keyword +'&s=indexed&type=Code'
resp = s.get(search_code)
results_code = resp.text
dom_tree_code = etree.HTML(results_code)
#獲取存在資訊洩露的連結地址
Urls = dom_tree_code.xpath( '//div[@class="flex-auto min-width-0 col-10"]/a[2]/@href')
for url in Urls:
url = ' https://github.com ' + url
tUrls.append(url)
#獲取程式碼部分,先獲得整個包含洩露程式碼的最上層DIV物件,再把物件進行字元化,便於使用正則進行匹配洩露程式碼部分的div
results = dom_tree_code.xpath( '//div[@class="code-list-item col-12 py-4 code-list-item-public "]')
for div in results:
result = etree.tostring(div, pretty_print=True, method= "html")
code = str(result, encoding='utf-8')
#如果存在<div class="file-box blob-wrapper">此標籤則匹配洩露的關鍵程式碼部分,不存在則為空。
if '<div class="file-box blob-wrapper">' in code:
data = pattern_code.findall(code)
codes.append(pattern_sub.sub( '<em style="color:red">', data[0]))
else:
codes.append(' ')
return tUrls, codes
except Exception as e:
#如發生錯誤,則寫入檔案並且打印出來
error_Record(str(e), traceback. format_exc())
print(e)
正如程式碼中的註釋所講,獲取洩露地址的url還是採用xpath的解析。然而在洩露的程式碼部分,使用的是正則。
為什麼使用正則,第一,為了把包含洩露程式碼的table獲取下來以便傳送郵件(傳送郵件時只需要簡單加入css樣式即可);第二,這樣避免利用xpath去解析table下的行,這樣做稍顯麻煩。
其實在考慮這部分邏輯的時候遇到一個坑,那就是有些可能存在洩漏的倉庫,有url,卻沒有程式碼部分,沒有程式碼部分就不會存在’<div class=”file-box blob-wrapper”>’ 這個div,這樣獲取的url和程式碼就不能一一對應,會發現url和程式碼錯位了,根本就不準確。所以要先獲取 ‘<div class=”file-box blob-wrapper”>‘ 的父div即’<div class=”code-list-item col-12 py-4 code-list-item-public “>’,然後判斷是否存在程式碼部分,如果不存在則洩露程式碼的列表當前位置為空或空格,存在則搜尋程式碼洩露部分加入到列表中。這樣避免了有url沒有code的問題。
同時,在加入洩露程式碼的列表時,通過正則匹配進行css的替換把獲取的table標籤中的<em>標籤替換為<em sytle=”color:red”>。這樣以後傳送的郵件正文中關鍵詞則被標紅。
最後在異常處理部分加入了出現異常就寫入檔案,並且把trackback一併寫入檔案,以後排查錯誤時可以更準確方便。
3.儲存方式和呈現方式改進
儲存方式變更為資料庫儲存採用sqlite3,簡單易用,小型儲存需求可以滿足。建立了Baseline表,包含url和code兩個欄位。如圖所示:
此資料庫主要用於對比去重,基線建立,如資料庫中不存在洩露的url則加入其中,然後通過郵件發預警。反之則不傳送郵件預警。程式碼如下:
def insert_DB(url, code): try: conn = sqlite3.connect('hunter.db') cursor = conn.cursor() cursor.execute('CREATE TABLE IF NOT EXISTS Baseline (url varchar(1000) primary key, code varchar(10000))') cursor.execute('INSERT OR REPLACE INTO Baseline (url, code) values (?,?)', (url, code)) cursor.close conn.commit() conn.close() except Exception as e: print("資料庫操作失敗!\n") error_Record(str(e), traceback.format_exc()) print(e) def compare_DB_Url(url): try: con = sqlite3.connect('hunter.db') cur = con.cursor() cur.execute('SELECT url from Baseline where url = ?', (url,)) results = cur.fetchall() cur.close() con.commit() con.close() return results except Exception as e: error_Record(str(e), traceback.format_exc()) print(e) 呈現方式還是採用郵件預警,郵件格式採用html方便插入table並且設定css。郵件傳送程式碼如下: def send_mail(host, username, password, sender, receivers, message): def _format_addr(s): name,addr = parseaddr(s) return formataddr((Header(name,'utf-8').encode(),addr)) msg = MIMEText(message,'html', 'utf-8') subject = 'Github資訊洩露監控通知' msg['Subject']= Header(subject, 'utf-8').encode() msg['From']= _format_addr('Github資訊洩露監控<%s>'% sender) msg['To']= ','.join(receivers) try: smtp_obj = smtplib.SMTP(host,25) smtp_obj.login(username, password) smtp_obj.sendmail(sender, receivers, msg.as_string()) print('郵件傳送成功!') smtp_obj.close() except Exception as err: error_Record(str(err), traceback.format_exc()) print(err)
0×03 預警邏輯和基線建立
預警邏輯和基線建立程式碼都放在了主函式中,讀取配置檔案與之前Github資訊洩露監控工具無異。而在關鍵詞和payload讀取時則進行了改動,搜尋關鍵詞由原來的單一,變為組合型關鍵詞,即keyword + payload。搜尋程式碼中存在的關鍵詞和payload又將兩者拆分,這樣就保證了監控者關注的關鍵詞和payload 100%存在於Github新收錄的條目中,關鍵程式碼如下:
if __name__ == '__main__': config = configparser.ConfigParser() config.read('info.ini') g_User = config['Github']['user'] g_Pass = config['Github']['password'] host = config['EMAIL']['host'] m_User = config['EMAIL']['user'] m_Pass = config['EMAIL']['password'] m_sender = config['SENDER']['sender'] receivers = [] for k in config['RECEIVER']: receivers.append(config['RECEIVER'][k]) keywords = [] #組合關鍵詞,keyword + payload,兩者之間加入“+”號,符合Github搜尋語法 for keyword in config['KEYWORD']: for payload in config['PAYLOADS']: keywords.append(config['KEYWORD'][keyword]+ '+' + config['PAYLOADS'][payload]) message = 'Dear all<br><br>未發現任何新增敏感資訊!' tUrls, codes= hunter(g_User, g_Pass, keywords) target_codes = [] #第一次執行會查詢是否存在資料檔案,如果不存在則新建,存在則進行新增條目查詢 if os.path.exists('hunter.db'): print("存在資料庫檔案,進行新增資料查詢......") #拆分關鍵詞,在洩露的程式碼中查詢關鍵詞和payload.如果兩者都存在則進行下一步資料庫查詢 for keyword in keywords: payload = keyword.split('+') for i inrange(0, len(tUrls)): if (payload[0] in codes[i]) and (payload[1] in codes[i]): #如果資料庫中返回的值為空,則說明該條目在資料庫中不存在,那麼新增到target_codes裡面使用者傳送郵件,並且新增到資料庫中 if notcompare_DB_Url(tUrls[i]): target_codes.append('<br><br><br>'+ '連結:' +tUrls[i] + '<br><br>') target_codes.append('簡要程式碼如下:<br><div style="border:1px solid #bfd1eb;background:#f3faff">'+ codes[i] + '</div>') insert_DB(tUrls[i], codes[i]) else: print("未發現數據庫檔案,建立並建立基線......") for keyword in keywords: payload = keyword.split('+') for i inrange(0, len(tUrls)): #關鍵詞和payload同時存在則加入到target_codes,並寫入資料庫 if (payload[0] in codes[i]) and (payload[1] in codes[i]): target_codes.append('<br><br><br>'+ '連結:' +tUrls[i] + '<br><br>') target_codes.append('簡要程式碼如下:<br><div style="border:1px solid #bfd1eb;background:#f3faff">'+ codes[i] + '</div>') insert_DB(tUrls[i], codes[i]) #當target_codes有資料時,則進行郵件預警 if target_codes: warning = ''.join(target_codes) result = 'Dear all<br><br>發現資訊洩露! '+ '一共發現{}條'.format(int(len(target_codes)/2)) + warning send_mail(host, m_User, m_Pass, m_sender, receivers, result) else: send_mail(host, m_User, m_Pass, m_sender, receivers, message)
首先進行資料庫檔案查詢,如果是首次執行,那麼會在程式目錄下生成一個db檔案,名稱為hunter。然後進行keyword和payload查詢,當keyword和payload都存在於爬下來的洩露程式碼中,那麼則加入到target_codes中用於郵件預警,最後進行資料庫寫入操作建立基線;如果資料庫存在則說明基線存在,那麼則進行新增資料查詢。同理,當keyword和payload存在洩漏程式碼中,並且還必須滿足獲取的洩露地址url不存在於資料庫Baseline表中則加入到target_codes,最後寫入表中。在加入target_codes時還寫入了換行符,css樣式等,具體可參考上面程式碼。程式最後傳送預警郵件,如target_codes有資料,那麼進行預警。如果不存在資料則進行通知。
0×04 效果呈現
具體效果如下圖所示:
圖中被抹去的是關鍵詞,keyword和payload都以紅色顯示。根據自己需求進行修改即可。
0×05 總結
所謂人無完人,程式也是一樣,只有通過不斷改進,才能達到近乎完美得到自己想要的效果。我覺得最重要的是改進的過程,在這個過程會遇到各種問題,各種難題,我們要做的是去一個個克服,不能半途而廢。只有這樣才能不斷的提升自己,提高自己的各項技能水平。安全之路還長,吾將上下而求索!謝謝各位客官老爺耐心讀完!最新完整程式碼還是那個地址:
https://github.com/Hell0W0rld0/Github-Hunter
自己動手打造系列下期預告:自己動手打造自動化安全掃描平臺
*本文作者:ztencmcp,轉載請註明來自FreeBuf.COM