成功識(shí)別日志中的數(shù)據(jù)泄漏漏洞并對(duì)其進(jìn)行緩解
我會(huì)在本文介紹我是如何與r2c的另一位開(kāi)發(fā)人員成功識(shí)別日志中的數(shù)據(jù)泄漏,從而修復(fù)了該漏洞并徹底杜絕其今后的再發(fā)生,整個(gè)過(guò)程只需幾個(gè)小時(shí)就可以完成了。
作為一名開(kāi)發(fā)人員和工程經(jīng)理,我一直癡迷于尋找一種可以不需要安全團(tuán)隊(duì)完全參與,即可快速解決整個(gè)涉及組織安全漏洞的方法。
為什么要這么做呢?好處有很多:
- 可以快速解決組織出現(xiàn)的安全漏洞。在實(shí)踐過(guò)程中,該方法可以大大加快安全防御的速度,以至于我們可以在識(shí)別出漏洞后的幾分鐘內(nèi)建立起安全的防護(hù)措施,如果是走組織流程,則安全漏洞則會(huì)持續(xù)數(shù)天或數(shù)周。
- 當(dāng)開(kāi)發(fā)人員可以輕松地自行解決安全漏洞時(shí),它可以使安全團(tuán)隊(duì)騰出精力來(lái)專(zhuān)注于整個(gè)組織的“全局”安全性。我希望安全工程師考慮如何選擇框架、設(shè)置工具、幫助實(shí)現(xiàn)安全體系結(jié)構(gòu),以及構(gòu)建深度防御,而不是找到我在本文所述的XSS漏洞。
我將以上過(guò)程稱(chēng)為“self-service DevSec。
接下來(lái),我將介紹我們?cè)谌粘i_(kāi)發(fā)工作過(guò)程中遇到的一個(gè)安全漏洞。我將討論我們?nèi)绾伟l(fā)現(xiàn)此漏洞的,以及如何在短短幾個(gè)小時(shí)內(nèi)修復(fù)整個(gè)安全漏洞,并使用Semgrep防止該漏洞再次發(fā)生。Semgrep是一個(gè)開(kāi)源工具,用于使用熟悉的語(yǔ)法進(jìn)行輕量級(jí)靜態(tài)分析。
上個(gè)月,我正在與r2c的另一位工程師Clara McCreery一起調(diào)試Flask Web應(yīng)用驗(yàn)證流程。就像許多工程師面臨著令人困惑的調(diào)試問(wèn)題一樣,我們的第一步就是將Web應(yīng)用程序放入調(diào)試日志記錄。
具體來(lái)說(shuō),我們想知道數(shù)據(jù)庫(kù)操作的情況,因此我們將ORM(在本例中,我們使用SQLAlchemy)設(shè)置為INFO級(jí)別的日志記錄,方法如下:
- logging.getLogger("sqlalchemy.engine.base.Engine").setLevel(logging.INFO)
這會(huì)將SQLAlchemy配置為記錄所有SQL語(yǔ)句以及傳遞的參數(shù),讓我們看一下我們看到的一些輸出結(jié)果:
- INFO:werkzeug:127.0.0.1 - - [25/Sep/2020 11:50:01] "POST /api/auth/authenticate HTTP/1.1" 200 -
- INFO:sqlalchemy.engine.base.Engine:BEGIN (implicit)
- INFO:sqlalchemy.engine.base.Engine:SELECT token.id AS token_id, token.token AS token_token, token.name AS token_name
- FROM token
- WHERE token.token = %(token_1)s
- LIMIT %(param_1)sINFO:sqlalchemy.engine.base.Engine:{'token_1': $2a$10$KVsyW1jjKn.pvkVi3w9Rn.1mwnZFd7F2SFveGDG8flIhbe.MoJH4G, 'param_1': 1}
我們絕對(duì)不應(yīng)該記錄令牌,即使已安全地對(duì)其進(jìn)行哈希處理。在此示例中,處于講解的目的,實(shí)際令牌值已更改。
首先要制定一個(gè)計(jì)劃
至此,我們已經(jīng)確定了一個(gè)安全漏洞,并且希望在保留檢查日志能力的同時(shí)修復(fù)此漏洞。具體步驟如下:
- 緩解當(dāng)前的安全漏洞;
- 尋找一個(gè)永久的解決方案,以備不時(shí)之需。永久的解決方案意味著對(duì)我們的系統(tǒng)進(jìn)行深層次的改變。理想情況下,該解決方案是在整個(gè)組織中自動(dòng)化和無(wú)縫的。
- 添加一種機(jī)制來(lái)強(qiáng)制我們的解決方案在整個(gè)組織范圍內(nèi)使用。
接下來(lái),我將指導(dǎo)你完成每個(gè)步驟。需要注意的是,我們能夠在幾個(gè)小時(shí)內(nèi)完成整個(gè)流程,而無(wú)需與安全團(tuán)隊(duì)合作。
緩解當(dāng)前的安全漏洞
這里的緩解措施非常簡(jiǎn)單,因?yàn)槲覀円呀?jīng)知道了漏洞的根本原因,為此可以快速還原日志記錄的更改過(guò)程。然后,我們可以對(duì)日志進(jìn)行快速審核,以確保僅泄漏了開(kāi)發(fā)測(cè)試令牌。
永久解決方案
那我們?nèi)绾畏乐筍QLAlchemy記錄敏感數(shù)據(jù)?
第一步是閱讀文檔??焖偎阉?ldquo;引擎日志中的sqlalchemy隱藏參數(shù)”將我們鏈接到SQLAlchemy Engine文檔。稍后進(jìn)行詳細(xì)閱讀,這樣我們就發(fā)現(xiàn)了hide_parameters標(biāo)志,該標(biāo)志防止日志記錄框架在日志或異常中發(fā)出任何參數(shù)。
雖然這肯定可以防止發(fā)現(xiàn)的安全漏洞,但對(duì)我們來(lái)說(shuō)信息量太小了,因?yàn)槲覀兿胫览鐢?shù)據(jù)庫(kù)ID等信息,以便進(jìn)行調(diào)試。
真正的解決方案
然后,我們檢查了相關(guān)的SQLAlchemy源代碼,相關(guān)代碼在sqlalchemy / engine / base.py中:

sql_util._repr_params依次運(yùn)行:

通過(guò)研究trunc,我們發(fā)現(xiàn)它通過(guò)將參數(shù)的repr截?cái)酁樽畲笞址麛?shù)來(lái)轉(zhuǎn)換參數(shù)值,這意味著我們應(yīng)該重寫(xiě)參數(shù)對(duì)象的repr方法以防止敏感日志記錄。
此時(shí),我們像優(yōu)秀的工程師一樣,使用了一條懶惰的策略,因?yàn)槲野l(fā)現(xiàn)的這個(gè)GitHub漏洞,Mike Bayer已經(jīng)發(fā)布了一個(gè)很好的解決方案,所以我就進(jìn)行了一些復(fù)制,關(guān)鍵代碼如下:

這段代碼的作用是什么?你可以發(fā)現(xiàn)它用新的ObfuscatedString.Repr參數(shù)替換了我們?cè)瓉?lái)的str參數(shù)。登錄時(shí)或發(fā)出異常消息時(shí),該字符串將替換為我們的********。由于參數(shù)仍然被綁定為原始字符串(通過(guò)impl = types.String),因此仍然插入和從數(shù)據(jù)庫(kù)中選擇正確的值。
要使用這個(gè)新的字段類(lèi)型,我們?cè)O(shè)置令牌的字段類(lèi)型如下:

然后,我們重新啟用INFO日志記錄,并檢查我們是否正確混淆了文本:
- INFO:werkzeug:127.0.0.1 - - [25/Sep/2020 13:48:55] "GET /api/agent/deployments/1/policies HTTP/1.1" 200 -
- INFO:sqlalchemy.engine.base.Engine:BEGIN (implicit)
- INFO:sqlalchemy.engine.base.Engine:SELECT token.id AS token_id, token.token AS token_token, token.name AS token_name
- FROM token
- WHERE token.token = %(token_1)s
- LIMIT %(param_1)s
- INFO:sqlalchemy.engine.base.Engine:{'token_1': ********, 'param_1': 1}
為了完整起見(jiàn),我們還在開(kāi)發(fā)數(shù)據(jù)庫(kù)控制臺(tái)中驗(yàn)證了是否存儲(chǔ)和檢索了正確的值。
執(zhí)行過(guò)程
應(yīng)該說(shuō),我們已經(jīng)暫時(shí)解決了安全漏洞,以便可以重新調(diào)試原始的身份驗(yàn)證漏洞。但要徹底修復(fù)整個(gè)漏洞。我們將如何做?
以下有一些想法,我相信我們都曾經(jīng)遇到過(guò):
- 在安全審查中阻止對(duì)SQLAlchemy模型的所有提交。
- 為所有開(kāi)發(fā)人員舉辦年度安全培訓(xùn),包括記錄敏感數(shù)據(jù)的漏洞。
- 每周審核日志。
- 向你的SAST供應(yīng)商提出漏洞,要求他們添加檢查以捕獲敏感記錄的數(shù)據(jù)。
如果要從這篇博客文章中得出一個(gè)中心結(jié)論的話,那就是:這些都不是理想的解決方案,原因如下:
- 阻止提交會(huì)在開(kāi)發(fā)過(guò)程中引入不必要的拖延,降低開(kāi)發(fā)速度,并會(huì)分散安全團(tuán)隊(duì)的注意力。
- 安全培訓(xùn)是安全計(jì)劃的重要組成部分,也是讓開(kāi)發(fā)人員意識(shí)到不斷發(fā)展的安全威脅的必要條件,但是人類(lèi)的記憶力很差,我們可能會(huì)忘記幾個(gè)月甚至幾天前聽(tīng)到的事情。
- 定期審核(例如阻止提交)會(huì)給幾乎肯定是超負(fù)荷的安全團(tuán)隊(duì)帶來(lái)沉重的工作量;
- 你的SAST提供商當(dāng)然會(huì)歡迎你的建議,但是你會(huì)依賴他們的軟件發(fā)布周期,并且可能幾個(gè)月都看不到可用的檢查。此外,如果你的漏洞是特定于某個(gè)領(lǐng)域的,則實(shí)施廣泛地檢查甚至沒(méi)有意義。
幸運(yùn)的是,Semgrep為我們提供了一個(gè)簡(jiǎn)單的解決方案:在代碼中定義一個(gè)不變量,并在每次CI運(yùn)行時(shí)使用Semgrep掃描對(duì)其進(jìn)行強(qiáng)制執(zhí)行。
在r2c中,我們使用GitHub操作在每個(gè)合并請(qǐng)求上運(yùn)行Semgrep。我們使用由Semgrep .dev管理的管理策略、規(guī)則字段表和通知設(shè)置來(lái)定義Semgrep應(yīng)該運(yùn)行哪些檢查。
為了保證我們的代碼不會(huì)再出現(xiàn)問(wèn)題,我訪問(wèn)了semgrep.dev/editor并編寫(xiě)了一個(gè)快速規(guī)則來(lái)檢測(cè)潛在的不安全日志SQLAlchemy字段。
這是Semgrep的YAML定義語(yǔ)言中的規(guī)則定義:

這個(gè)規(guī)則有什么作用?詳細(xì)解釋如下:
- id:我們?yōu)橐?guī)則提供了一個(gè)簡(jiǎn)潔的描述性ID,以便任何在編輯器或CI輸出中看到它的開(kāi)發(fā)人員都可以輕松參考。
- patterns:這由兩部分組成:
- pattern:此表達(dá)式告訴Semgrep如何在我們的代碼庫(kù)(在此示例中,我們的SQLAlchemy實(shí)例稱(chēng)為db)中查找具有String字段類(lèi)型的任何SQLAlchemy ORM字段定義,它還將字段名稱(chēng)綁定到名為COLUMN的元變量。
- metavvariable -regex:這個(gè)表達(dá)式告訴Semgrep只有在字段metavariable包含單詞片段(如token、email、key或secret)時(shí)才報(bào)告匹配。正則表達(dá)式包含了很多細(xì)節(jié)聲明,以防止我們匹配不相關(guān)的單詞,如keyboard。
- message:當(dāng)Semgrep匹配我們的模式時(shí),我們希望確保我們解釋檢測(cè)到的漏洞是什么,為什么它是一個(gè)漏洞,以及如何修復(fù)它。這些信息將有助于開(kāi)發(fā)人員獨(dú)立解決漏洞,而不會(huì)造成混亂或不必要的誤讀。
- severity:你可以自定義你領(lǐng)域中任何漏洞的嚴(yán)重程度。
然后快速地按下“部署到策略”按鈕,就可以保證所有的web應(yīng)用程序都得到了保護(hù)。
通過(guò)我們的VS Code擴(kuò)展將Semgrep集成到編程工作流中的開(kāi)發(fā)人員也會(huì)開(kāi)始在他們的IDE中產(chǎn)生效果。
請(qǐng)注意,此解決方案是有意迭代的:我們可能會(huì)發(fā)現(xiàn)更多字段名稱(chēng)被標(biāo)識(shí)為敏感字段,或者還希望包含db.Text類(lèi)型。幸運(yùn)的是,這是一個(gè)快速修訂,并根據(jù)需要重新部署。
總結(jié)
在這篇文章中,我演示了你作為一名開(kāi)發(fā)人員或管理人員如何使用輕量級(jí)靜態(tài)分析(如Semgrep)來(lái)幫助在代碼中強(qiáng)制執(zhí)行不變量。
在r2c中,我們習(xí)慣性地使用Semgrep來(lái)防止自己重復(fù)犯錯(cuò)誤:意外地使調(diào)試器處于提交狀態(tài)?有一條規(guī)則可以防止這種情況發(fā)生。當(dāng)我們發(fā)現(xiàn)導(dǎo)入某個(gè)庫(kù)會(huì)減慢程序的初始化速度時(shí),我們編寫(xiě)了一條規(guī)則來(lái)確保它被延遲加載。
本文翻譯自:https://r2c.dev/blog/2020/fixing-leaky-logs-how-to-find-a-bug-and-ensure-it-never-returns/