網(wǎng)站做關(guān)鍵詞搜索要好多錢潮州seo建站
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
MIT 6.858 計算機系統(tǒng)安全筆記 2014 秋季
2014 年由Nickolai Zeldovich 教授和James Mickens 教授教授授課的 6.858 講座筆記。這些講座筆記略有修改,與 6.858 課程網(wǎng)站上發(fā)布的內(nèi)容略有不同。
-
第1講:介紹:什么是安全,意義何在,沒有完美的安全,策略,威脅模型,假設(shè),機制,緩沖區(qū)溢出
-
第2講:控制劫持攻擊:緩沖區(qū)溢出,棧金絲雀,邊界檢查,電子圍欄,胖指針,影子數(shù)據(jù)結(jié)構(gòu),Jones & Kelly,松松邊界檢查
-
第3講:更多松松邊界和返回導(dǎo)向編程:邊界檢查的成本,不可執(zhí)行內(nèi)存,地址空間布局隨機化(ASLR),返回導(dǎo)向編程(ROP),堆棧讀取,盲目 ROP,小工具
-
第4講:OKWS:特權(quán)分離,Linux 自主訪問控制(DAC),UIDs,GIDs,setuid/setgid,文件描述符,進程,Apache web 服務(wù)器,chroot 監(jiān)獄,遠程過程調(diào)用(RPC)
-
第5講:滲透測試 嘉賓講座 由 Paul Youn,iSEC Partners
-
第6講:Capsicum:混淆副手問題,環(huán)境權(quán)限,能力,沙盒,自主訪問控制(DAC),強制訪問控制(MAC),Capsicum
-
第7講:Native Client (NaCl):沙盒化 x86 本機代碼,軟件故障隔離,可靠的反匯編,x86 分段
-
第8講:Web 安全,第一部分:現(xiàn)代網(wǎng)絡(luò)瀏覽器,同源策略,框架,DOM 節(jié)點,cookies,跨站請求偽造(CSRF)攻擊,DNS 重綁定攻擊,瀏覽器插件
-
第9講:Web 安全,第二部分:跨站腳本(XSS)攻擊,XSS 防御,SQL 注入攻擊,Django,會話管理,cookies,HTML5 本地存儲,HTTP 協(xié)議的歧義,隱蔽通道
-
第10講:符號執(zhí)行 嘉賓講座 由 Armando Solar-Lezama 教授,MIT CSAIL
-
第11講:Ur/Web 嘉賓講座 由 Adam Chlipala 教授,MIT,CSAIL
-
第12講:TCP/IP 安全:威脅模型,序列號和攻擊,連接劫持攻擊,SYN 洪水攻擊,帶寬放大攻擊,路由
-
第13講:Kerberos:Kerberos 架構(gòu)和信任模型,票證,認證者,票證授予服務(wù)器,更改密碼,復(fù)制,網(wǎng)絡(luò)攻擊,前向保密性
-
第14講:ForceHTTPS:證書,HTTPS,在線證書狀態(tài)協(xié)議(OCSP),ForceHTTPS
-
第15講:醫(yī)療軟件 嘉賓講座 由 Kevin Fu 教授,密歇根大學(xué)
-
第16講:時序攻擊:側(cè)信道攻擊,RSA 加密,RSA 實現(xiàn),模指數(shù)運算,賽里斯剩余定理(CRT),重復(fù)平方,蒙哥馬利表示,卡拉茨巴乘法,RSA 蒙蔽,其他時序攻擊
-
第17講:用戶認證:你擁有什么,你知道什么,你是誰,密碼,挑戰(zhàn)-響應(yīng),可用性,部署性,安全性,生物特征,多因素認證(MFA),萬事達卡的 CAP 讀卡器
-
第18講:私密瀏覽:私密瀏覽模式,本地和網(wǎng)絡(luò)攻擊者,虛擬機級隱私,操作系統(tǒng)級隱私,瀏覽器實現(xiàn)了什么,瀏覽器擴展
-
第19講:Tor 客座講座,由 Nick Mathewson 主講,Tor 項目
- 2012 年的 6.858 課程筆記關(guān)于匿名通信:洋蔥路由,Tor 設(shè)計,Tor 電路,Tor 流,Tor 隱藏服務(wù),阻止 Tor,用餐密碼學(xué)家網(wǎng)絡(luò)(DC-nets)
-
第20講:手機安全:Android 應(yīng)用程序,活動,服務(wù),內(nèi)容提供者,廣播接收器,意圖,權(quán)限,標簽,參考監(jiān)視器,廣播意圖
-
第21講:信息流跟蹤:TaintDroid,Android 數(shù)據(jù)泄漏,信息流控制,污點跟蹤,污點標志,隱式流,x86 污點跟蹤,TightLip
-
第22講:麻省理工學(xué)院信息服務(wù)與技術(shù)部 客座講座,由 Mark Silis 和 David LaPorte 主講,麻省理工學(xué)院信息服務(wù)與技術(shù)部
-
第23講:安全經(jīng)濟學(xué):網(wǎng)絡(luò)攻擊的經(jīng)濟學(xué),垃圾郵件價值鏈,廣告,點擊支持,實現(xiàn),CAPTCHA,僵尸網(wǎng)絡(luò),支付協(xié)議,道德
2015 年的新筆記
- 第8講:英特爾軟件保護擴展(SGX):隔離,Iago 攻擊,飛地,證明,Haven
論文
我們閱讀的論文列表(papers/):
-
松松垮垮的邊界檢查
-
盲目黑客
-
OKWS
-
困惑的代理(或者為什么可能發(fā)明了能力)
-
辣椒 (capabilities)
-
本地客戶端(沙箱化 x86 代碼)
-
OWASP 前十名,最關(guān)鍵的 Web 應(yīng)用程序安全風(fēng)險
-
KLEE(符號執(zhí)行)
-
Ur/Web(面向 Web 的函數(shù)式編程)
-
回顧“TCP/IP 協(xié)議套件中的安全問題”
-
凱伯羅斯:用于開放網(wǎng)絡(luò)系統(tǒng)的認證服務(wù)
-
ForceHTTPs
-
值得信賴的醫(yī)療設(shè)備軟件
-
遠程時序攻擊是可行的
-
替代密碼的探索
-
私密瀏覽模式
-
Tor:第二代洋蔥路由器
-
了解 Android 安全
-
TaintDroid:一種用于智能手機實時隱私監(jiān)控的信息流跟蹤系統(tǒng)
-
點擊軌跡:垃圾郵件價值鏈的端到端分析
“新” 論文
- Iago Attacks: 為什么系統(tǒng)調(diào)用 API 是一個糟糕的不受信任的 RPC 接口
其他論文
- 賽里斯防火墻安全政策
介紹
**注意:**這些講座筆記略有修改,來源于 2014 年 6.858 課程網(wǎng)站上發(fā)布的內(nèi)容課程網(wǎng)站。
什么是安全性?
-
在對手存在的情況下實現(xiàn)某個目標。
-
許多系統(tǒng)連接到互聯(lián)網(wǎng),而互聯(lián)網(wǎng)上存在對手。
-
因此,許多系統(tǒng)的設(shè)計可能需要考慮安全性。
-
即,在有對手的情況下系統(tǒng)是否能正常工作?
-
-
-
高層次的安全性思考計劃:
-
**政策:**您想要實現(xiàn)的目標。
-
例如,只有艾麗絲應(yīng)該讀取文件
F
。 -
*常見目標:*保密性,完整性,可用性。
-
-
**威脅模型:**對攻擊者可能做什么的假設(shè)。
-
例如可以猜測密碼,但無法物理抓取文件服務(wù)器。
-
最好假設(shè)攻擊者可以做某事。
-
-
**機制:**系統(tǒng)提供的旋鈕,幫助維護政策。
- 例如,用戶帳戶,密碼,文件權(quán)限,加密。
-
**最終目標:**在威脅模型內(nèi)部,對手無法違反政策。
- 請注意,目標與機制無關(guān)。
-
-
為什么安全性很難?這是一個負面目標。
-
對比:檢查是否實現(xiàn)了積極目標很容易,例如,艾麗絲實際上可以讀取文件
F
。更難的是檢查沒有可能的方式讓艾麗絲讀取文件F
。- 你甚至如何開始列舉艾麗絲可能讀取文件的所有可能方式?艾麗絲可以利用多少層次的漏洞來獲取文件
F
的訪問權(quán)限。
- 你甚至如何開始列舉艾麗絲可能讀取文件的所有可能方式?艾麗絲可以利用多少層次的漏洞來獲取文件
-
需要保證政策,假設(shè)威脅模型。
-
很難想象攻擊者可能如何入侵的所有可能方式。
-
現(xiàn)實的威脅模型是開放式的(幾乎是負面模型)。
-
最弱的環(huán)節(jié)很重要。
-
迭代過程:設(shè)計,根據(jù)需要更新威脅模型等。
-
如果我們無法實現(xiàn)完美的安全性,那有什么意義呢?
-
在這門課程中,我們將推動每個系統(tǒng)的邊界,看看它何時會崩潰。
-
每個系統(tǒng)可能都會有一些導(dǎo)致妥協(xié)的破壞點。
-
這并不一定意味著系統(tǒng)沒有用:這取決于上下文。
-
重要的是了解系統(tǒng)能做什么,以及系統(tǒng)不能做什么。
-
-
實際上,必須管理安全風(fēng)險與收益。
-
更安全的系統(tǒng)意味著某些妥協(xié)的風(fēng)險(或后果)更小。
-
不安全的系統(tǒng)可能需要手動審計以檢查攻擊等。
-
攻擊成本越高,將有更多的對手被阻止。
-
-
更好的安全性通常使新功能變得實用和安全。
-
假設(shè)你想在系統(tǒng)上運行某些應(yīng)用程序。
-
大公司有時會禁止用戶在其臺式機上安裝未經(jīng)批準的軟件,部分原因是出于安全考慮。
-
瀏覽器中的 Javascript 是隔離的,這使得運行新代碼/應(yīng)用程序而無需手動檢查/批準變得可以接受(或虛擬機,或本地客戶端,或更好的操作系統(tǒng)隔離機制)。
-
同樣,VPN 使得減輕允許員工從互聯(lián)網(wǎng)的任何地方連接到公司網(wǎng)絡(luò)的風(fēng)險變得實際可行。
-
出現(xiàn)問題的原因之一:政策問題
-
例子: 薩拉·佩林的電子郵件賬戶。
-
雅虎電子郵件賬戶有用戶名、密碼和安全問題。
-
用戶可以通過提供用戶名和密碼登錄。
-
如果用戶忘記密碼,可以通過回答安全問題來重置。
-
安全問題有時比密碼更容易猜到。
-
一些對手猜到了薩拉·佩林的高中、生日等信息。
-
政策總結(jié)為:可以使用密碼或安全問題登錄。
- (無法強制執(zhí)行“只有用戶忘記密碼,然后…”)
-
-
例子: 馬特·霍南在亞馬遜、蘋果、谷歌等處的賬戶
-
Gmail 密碼重置:向備用電子郵件地址發(fā)送驗證鏈接。
-
谷歌貼心地打印了備用電子郵件地址的一部分。
-
馬特·霍南的備用地址是他的蘋果
@me.com
賬戶。
-
-
蘋果密碼重置:需要賬單地址,信用卡的最后 4 位數(shù)字。
- 地址可能很容易獲取,但如何獲取用戶信用卡號的 4 位數(shù)字?
-
亞馬遜:可以向賬戶添加信用卡,無需密碼。
-
亞馬遜密碼重置:提供用戶的任意一張信用卡號。
- 哈哈。
-
亞馬遜:不會打印信用卡號… 但會打印最后 4 位數(shù)字!
-
-
例子: Twitter 的
@N
賬戶劫持。- 對于合法用戶來說,有時很難證明他們擁有一個賬戶!
-
如何解決?
-
仔細思考政策聲明的含義。
-
一些政策檢查工具可以幫助,但需要一種指定不良內(nèi)容的方法。
-
在分布式系統(tǒng)中很困難:不知道每個人在做什么。
-
出了什么問題 #2:威脅模型/假設(shè)問題
-
例子: 未考慮人為因素。
-
釣魚攻擊。
-
用戶收到一封要求續(xù)訂電子郵件賬戶、轉(zhuǎn)賬或…的電子郵件。
-
技術(shù)支持接到一個聲音很像用戶的電話要求重置密碼。
- “橡皮管解密”
-
-
例子: 計算假設(shè)隨時間變化。
-
麻省理工學(xué)院的 Kerberos 系統(tǒng)使用 56 位 DES 密鑰,自 20 世紀 80 年代中期以來。
-
當(dāng)時,似乎可以假設(shè)對手無法檢查所有 2?? 個密鑰。
-
不再合理:現(xiàn)在大約需要 100 美元。
- 幾年前,6.858 期末項目表明可以在一天內(nèi)獲取任何密鑰。
-
-
例子: 所有 SSL 證書 CA 都是完全受信任的。
-
要連接到啟用 SSL 的網(wǎng)站,Web 瀏覽器會驗證證書。
-
證書是服務(wù)器主機名和加密密鑰的組合,
- 由某些受信任的證書頒發(fā)機構(gòu)(CA)簽名。
-
瀏覽器信任的證書頒發(fā)機構(gòu)(CA)長列表(數(shù)百個)。
-
如果任何 CA 受到損害,對手可以攔截 SSL 連接。
- 為任何服務(wù)器主機名創(chuàng)建一個“偽造”證書。
-
2011 年,兩個 CA 受到損害,為許多域(谷歌、雅虎、Tor 等)簽發(fā)了偽造證書,顯然在伊朗使用。
-
en.wikipedia.org/wiki/DigiNotar
-
en.wikipedia.org/wiki/Comodo_Group
-
-
2012 年,一家 CA 無意中簽發(fā)了一個適用于任何域的根證書。
www.h-online.com/security/news/item/Trustwave-issued-a-man-in-the-middle-certificate-1429982.html
-
-
*例子:*假設(shè)你的硬件是可信的。
-
如果 NSA 是你的對手,結(jié)果可能并不是一個好的假設(shè)。
www.schneier.com/blog/archives/2013/12/more_about_the.html
-
-
*例子:*假設(shè)密碼學(xué)中有良好的隨機性。
-
需要高質(zhì)量的隨機性來生成無法被猜測的密鑰。
-
問題:嵌入式設(shè)備、虛擬機可能沒有太多的隨機性。
-
結(jié)果,許多密鑰相似或容易被猜測攻擊。
- 挖掘你的 P 和 Q:檢測網(wǎng)絡(luò)設(shè)備中普遍存在的弱密鑰
-
-
*例子:*顛覆軍事操作系統(tǒng)安全。
-
在 80 年代,軍方鼓勵研究安全操作系統(tǒng)。
-
操作系統(tǒng)被破壞的一個意想不到的方式:
- 對手獲取了開發(fā)系統(tǒng)的訪問權(quán)限,修改了操作系統(tǒng)代碼。
-
-
*例子:*顛覆防火墻。
-
對手可以連接到防火墻后面的不安全無線網(wǎng)絡(luò)。
-
對手可以欺騙防火墻后面的用戶來禁用防火墻。
-
可能只需點擊鏈接
http://firewall/?action=disable
就足夠了。 -
或者也許在 CNN.com 購買廣告,指向那個 URL(有效)?
-
-
-
*例子:*斷開與互聯(lián)網(wǎng)連接的機器安全嗎?
- Stuxnet 蠕蟲通過 USB 驅(qū)動器上的特制文件傳播。
-
如何解決?
-
更明確的威脅模型,以了解可能存在的弱點。
-
更簡單、更通用的威脅模型。
-
更好的設(shè)計可能會消除/減少對某些假設(shè)的依賴。
-
例如,不依賴完全信任 CA 的替代信任模型。
-
例如,不容易受到釣魚攻擊的身份驗證機制。
-
-
問題出在哪里 #3:機制問題–漏洞
-
安全機制中的漏洞(例如,操作系統(tǒng)內(nèi)核)會導(dǎo)致漏洞。
-
如果應(yīng)用程序正在執(zhí)行安全性,應(yīng)用程序級別的錯誤會導(dǎo)致漏洞。
-
例子:蘋果的 iCloud 密碼猜測速率限制。
-
人們經(jīng)常選擇弱密碼;通常可以在幾次嘗試(1K-1M)后猜中。
-
大多數(shù)服務(wù),包括蘋果的 iCloud,會對登錄嘗試進行速率限制。
-
蘋果的 iCloud 服務(wù)有許多 API。
-
一個 API(“查找我的 iPhone”服務(wù))忘記實現(xiàn)速率限制。
-
對手可以多次嘗試猜測密碼。
- 可能和他們發(fā)送數(shù)據(jù)包的速度一樣快:
>> M/day.
- 可能和他們發(fā)送數(shù)據(jù)包的速度一樣快:
-
-
例子:花旗集團信用卡網(wǎng)站缺失訪問控制檢查。
-
花旗銀行允許信用卡用戶在線訪問他們的賬戶。
-
登錄頁面要求輸入用戶名和密碼。
-
如果用戶名和密碼正確,將重定向到賬戶信息頁面。
-
賬戶信息頁面的 URL 包含一些數(shù)字。
-
結(jié)果這些數(shù)字與用戶的賬號相關(guān)。
-
更糟糕的是,服務(wù)器沒有檢查您是否已登錄到該帳戶。
-
對手嘗試不同的數(shù)字,獲取不同人的賬戶信息。
-
可能是錯誤的威脅模型:與現(xiàn)實世界不匹配?
-
如果對手通過瀏覽器瀏覽網(wǎng)站,系統(tǒng)是安全的。
-
如果對手自己合成新的 URL,系統(tǒng)就不安全了。
-
-
很難說開發(fā)人員是否有錯誤的威脅模型,或者有錯誤的機制。
-
-
例子: 安卓的 Java
SecureRandom
弱點導(dǎo)致比特幣被盜。-
擁有所有者私鑰的任何人都可以花費比特幣。
-
許多安卓上的比特幣錢包應(yīng)用使用了 Java 的
SecureRandom
API。 -
結(jié)果系統(tǒng)有時忘記了給 PRNG 種子!
-
結(jié)果,一些比特幣密鑰很容易被猜到。
-
對手搜索可猜測的密鑰,花費任何相應(yīng)的比特幣。
-
-
例子: 沙箱中的漏洞(NaCl,Javascript,Java 運行時)。
- 允許對手逃離隔離,執(zhí)行他們本不應(yīng)執(zhí)行的操作。
-
例子: Moxie 的 SSL 證書名稱檢查漏洞
- 空字節(jié)與長度編碼。
-
例子: 緩沖區(qū)溢出(見下文)。
案例研究:緩沖區(qū)溢出
-
考慮一個網(wǎng)絡(luò)服務(wù)器。
-
通常情況下,網(wǎng)絡(luò)服務(wù)器的代碼負責(zé)安全性。
-
例如,檢查可以訪問哪些 URL,檢查 SSL 客戶端證書,…
-
因此,服務(wù)器代碼中的漏洞可能導(dǎo)致安全妥協(xié)。
-
-
威脅模型是什么,策略是什么?
-
假設(shè)對手可以連接到網(wǎng)絡(luò)服務(wù)器,提供任何輸入。
-
策略有點模糊:只執(zhí)行程序員意圖的操作?
-
例如,不希望對手竊取數(shù)據(jù),繞過檢查,安裝后門。
-
-
考慮來自某個網(wǎng)絡(luò)服務(wù)器的簡化示例代碼:
webserver.c:
int read_req(void) {char buf[128];int i;gets(buf);i = atoi(buf);return i;}
-
編譯器在內(nèi)存布局方面生成了什么?
-
x86 棧:
-
棧向下增長。
-
%esp
指向棧上最后(最底部)有效的內(nèi)容。 -
%ebp
指向調(diào)用者的%esp
值。
-
-
read_req() stack layout:
+------------------+entry %ebp ----> | .. prev frame .. || || |+------------------+entry %esp ----> | return address |+------------------+new %ebp ------> | saved %ebp |+------------------+| buf[127] || ... || buf[0] |+------------------+| i |new %esp ------> +------------------+| ... |+------------------+
-
調(diào)用者的代碼(比如,
main()
):- 調(diào)用
read_req()
- 調(diào)用
read_req()
的匯編代碼:
push %ebpmov %esp -> %ebpsub 168, %esp # stack vars, etc...mov %ebp -> %esppop %ebpret
-
對手如何利用這段代碼?
-
提供長輸入,覆蓋超出緩沖區(qū)的棧上數(shù)據(jù)。
-
有趣的數(shù)據(jù):返回地址,被
ret
使用。 -
可以將返回地址設(shè)置為緩沖區(qū)本身,在其中包含一些代碼。
-
-
對手如何知道緩沖區(qū)的地址?
-
如果一臺機器的內(nèi)存是另一臺的兩倍會發(fā)生什么?
-
幸運的是對手,虛擬內(nèi)存使事情更加確定。
-
對于給定的操作系統(tǒng)和程序,地址通常是相同的。
-
-
如果棧向上增長,而不是向下,會發(fā)生什么?
- 查看
gets()
的棧幀。
- 查看
-
一旦對手執(zhí)行代碼,他們可以做什么?
-
使用進程的任何權(quán)限。
-
經(jīng)常利用溢出來更容易地進入系統(tǒng)。
- 最初在 Unix 上,運行 shell
/bin/sh
(因此稱為“shell 代碼”)。
- 最初在 Unix 上,運行 shell
-
如果進程以
root
或Administrator
運行,可以做任何事情。 -
即使不是,仍然可以發(fā)送垃圾郵件,讀取文件(Web 服務(wù)器,數(shù)據(jù)庫),…
-
可以攻擊防火墻后面的其他機器。
-
-
為什么程序員會寫出這樣的代碼?
-
舊代碼,未暴露在互聯(lián)網(wǎng)上。
-
程序員沒有考慮安全性。
-
許多標準函數(shù)曾經(jīng)是不安全的(
strcpy
,gets
,sprintf
)。 -
即使是安全版本也有陷阱(
strncpy
不會在末尾加上空字符)。
-
-
更一般地說,任何內(nèi)存錯誤都可能轉(zhuǎn)化為漏洞。
-
在釋放后繼續(xù)使用內(nèi)存(釋放后使用)。
-
如果寫入,覆蓋新的數(shù)據(jù)結(jié)構(gòu),例如函數(shù)指針。
-
如果讀取,可能會調(diào)用一個已損壞的函數(shù)指針。
-
-
兩次釋放相同的內(nèi)存(雙重釋放)。
- 可能會導(dǎo)致
malloc()
之后再次返回相同的內(nèi)存。
- 可能會導(dǎo)致
-
將棧指針遞減到棧的末尾之外,進入其他內(nèi)存。
- 利用運行在 Linux 上的 Xorg 服務(wù)器中的大內(nèi)存管理漏洞
-
一個字節(jié)的錯誤寫入可能導(dǎo)致受損。
- glibc __gconv_translit_find() 漏洞
-
甚至可能不需要覆蓋返回地址或函數(shù)指針。
-
可以足以讀取敏感數(shù)據(jù),如加密密鑰。
-
可以通過改變一些位來滿足需求(例如
int isLoggedIn
,int isRoot
)。
-
-
如何避免機制問題?
-
減少安全關(guān)鍵代碼的數(shù)量。
-
不要依賴整個應(yīng)用程序來執(zhí)行安全性。
-
將在實驗 2 中進行。
-
-
避免安全關(guān)鍵代碼中的錯誤。
-
例如,不要使用
gets()
,而是使用fgets()
可以限制緩沖區(qū)長度。 -
使用常見、經(jīng)過充分測試的安全機制(“機制的經(jīng)濟性”)。
-
審計這些常見的安全機制(有很多動力這樣做)。
-
避免開發(fā)可能存在錯誤的新的一次性機制。
-
良好的機制支持多種用途、策略(更有動力進行審計)。
-
-
常見機制的示例:
-
操作系統(tǒng)級別的訪問控制(但是,通??梢愿?#xff09;。
-
網(wǎng)絡(luò)防火墻(但是,通??梢愿?#xff09;。
-
加密,加密協(xié)議。
-
緩沖區(qū)溢出,松散的邊界
注意: 這些講座筆記略有修改,來自 2014 年 6.858 課程網(wǎng)站。
緩沖區(qū)溢出攻擊回顧
在上一講中,我們看了執(zhí)行緩沖區(qū)溢出攻擊的基礎(chǔ)知識。該攻擊利用了幾個觀察結(jié)果:
-
系統(tǒng)軟件通常用 C 編寫(操作系統(tǒng)、文件系統(tǒng)、數(shù)據(jù)庫、編譯器、網(wǎng)絡(luò)服務(wù)器、命令外殼和控制臺實用程序)
-
C 本質(zhì)上是高級匯編語言,所以…
-
暴露原始指針到內(nèi)存
-
不對數(shù)組執(zhí)行邊界檢查(因為硬件不這樣做,而 C 希望讓你盡可能接近硬件)
-
-
攻擊還利用了關(guān)于 x86 代碼如何工作的架構(gòu)知識:
-
棧增長的方向
-
棧變量的布局(尤其是數(shù)組和函數(shù)的返回地址)
-
read_req.c:
void read_req() {char buf[128];int i;gets(buf);//. . . do stuff w/buf . . .}
編譯器在內(nèi)存布局方面生成了什么?x86 ??雌饋硐襁@樣:
%esp points to the last (bottom-most) valid thing onthe stack.%ebp points to the caller's %esp value.+------------------+entry %ebp ----> | .. prev frame .. || | || | | stack grows down+------------------+ |entry %esp ----> | return address | v+------------------+new %ebp ------> | saved %ebp |+------------------+| buf[127] || ... || buf[0] |+------------------+new %esp ------> | i |+------------------+
對手如何利用這段代碼?
-
提供長輸入,覆蓋緩沖區(qū)后的棧數(shù)據(jù)。
-
關(guān)鍵觀察 1: 攻擊者可以覆蓋返回地址,使程序跳轉(zhuǎn)到攻擊者選擇的位置!
-
關(guān)鍵觀察 2: 攻擊者可以將返回地址設(shè)置為緩沖區(qū)本身,在其中包含一些 x86 代碼!
攻擊者在執(zhí)行代碼后可以做什么?
-
利用進程的任何權(quán)限!如果進程以 root 或管理員身份運行,它可以在系統(tǒng)上做任何想做的事情。即使進程不以 root 身份運行,它也可以發(fā)送垃圾郵件、讀取文件,有趣的是,攻擊或破壞防火墻后面的其他機器。
-
嗯,但為什么操作系統(tǒng)沒有注意到緩沖區(qū)已經(jīng)溢出?
-
就操作系統(tǒng)而言,沒有發(fā)生任何奇怪的事情!請記住,粗略地說,操作系統(tǒng)只在 Web 服務(wù)器進行 IO 或 IPC 時才被調(diào)用。除此之外,操作系統(tǒng)基本上只是坐下來讓程序執(zhí)行,依靠硬件頁表防止進程篡改彼此的內(nèi)存。然而,頁表保護無法防止進程“針對自身”發(fā)起的緩沖區(qū)溢出,因為溢出的緩沖區(qū)、返回地址和所有相關(guān)內(nèi)容都在進程的有效地址空間內(nèi)。
-
在本講座的后面,我們將討論操作系統(tǒng)可以采取的措施使緩沖區(qū)溢出更加困難。
-
修復(fù)緩沖區(qū)溢出
方法 #1: 避免 C 代碼中的錯誤。
-
難以或不可能實現(xiàn)。
-
程序員應(yīng)仔細檢查緩沖區(qū)、字符串、數(shù)組等的大小。特別是,程序員應(yīng)使用考慮到緩沖區(qū)大小的標準庫函數(shù)(
strncpy()
而不是strcpy()
,fgets()
而不是gets()
等)。 -
現(xiàn)代版本的
gcc
和 Visual Studio 在程序使用不安全函數(shù)(如 gets())時會發(fā)出警告。一般來說,你不應(yīng)該忽略編譯器警告。 將警告視為錯誤! -
好處: 首先避免問題!
-
壞處: 很難確保代碼是無錯誤的,特別是如果代碼庫很大。此外,應(yīng)用程序本身可能定義不使用
fgets()
或strcpy()
作為基本操作的緩沖區(qū)操作函數(shù)。
方法 2: 構(gòu)建工具來幫助程序員找到錯誤。
- 例如,我們可以使用靜態(tài)分析在編譯之前找到源代碼中的問題。想象一下,如果你有這樣一個函數(shù):
foo.c:
void foo(int *p) {int offset;int *z = p + offset;if(offset > 7){bar(offset);}}
-
通過靜態(tài)分析控制流,我們可以知道 offset 在未初始化的情況下被使用。
-
if
語句還限制了我們可能傳播到 bar 的偏移量。 -
我們將在后續(xù)講座中更多地討論靜態(tài)分析。
-
提供隨機輸入的“模糊器”可以有效地發(fā)現(xiàn)錯誤。請注意,模糊化可以與靜態(tài)分析結(jié)合以最大化代碼覆蓋率!
-
壞處: 很難證明完全沒有錯誤,尤其是對于像 C 這樣的不安全代碼。
-
好處: 即使是部分分析也是有用的,因為程序應(yīng)該變得更少有錯誤。例如,松散的邊界檢查可能無法捕捉所有內(nèi)存錯誤,但它可以檢測到許多重要類型。
方法 3: 使用內(nèi)存安全語言(JavaScript,C#,Python)。
-
好處: 通過不向程序員暴露原始內(nèi)存地址,并通過自動處理垃圾回收來防止內(nèi)存損壞錯誤。
-
壞處: 低級運行時代碼確實使用原始內(nèi)存地址。因此,運行時核心仍然需要正確。例如,堆噴射攻擊:
-
NOZZLE:防御堆噴射代碼注入攻擊
-
撰寫利用教程第 11 部分:堆噴射揭秘
-
-
壞處: 仍然有很多使用不安全語言(FORTRAN 和 COBOL)的遺留代碼。
-
壞處: 也許你需要訪問低級硬件功能(例如,你正在編寫設(shè)備驅(qū)動程序)
-
壞處: 性能比調(diào)優(yōu)良好的 C 應(yīng)用程序差?
-
過去是一個更大的問題,但硬件和高級語言變得更好了。
-
JIT 編譯萬歲!
-
asm.js的性能接近本機 C++性能的 2 倍!
-
使用謹慎的編碼來避免關(guān)鍵路徑中的垃圾回收抖動。
-
也許你是一個不懂得如何選擇合適工具的壞人/ 語言沙文主義者。如果你的任務(wù)是 I/O 綁定的,原始計算速度就不那么重要了。另外,不要成為那個用 C 語言編寫文本處理程序的笨蛋。
-
-
上述 3 種方法都是有效且廣泛使用的,但在實踐中緩沖區(qū)溢出仍然是一個問題。
-
大量/復(fù)雜的用 C 語言編寫的遺留代碼非常普遍。
-
即使是用 C/C++編寫的新代碼也可能存在內(nèi)存錯誤。
盡管存在有缺陷的代碼,我們?nèi)绾螠p輕緩沖區(qū)溢出?
-
在“傳統(tǒng)”緩沖區(qū)溢出中發(fā)生了兩件事:
-
對手控制執(zhí)行(程序計數(shù)器)。
-
對手執(zhí)行一些惡意代碼。
-
-
這兩個步驟存在哪些困難?
-
需要覆蓋一個代碼指針(稍后被調(diào)用)。常見目標是使用堆棧上的緩沖區(qū)的返回地址。在實踐中,任何內(nèi)存錯誤都可能起作用。函數(shù)指針,C++ vtables,異常處理程序等。
-
需要一些有趣的代碼在進程的內(nèi)存中。這通常比#1 更容易,因為:
-
在緩沖區(qū)中放置代碼很容易,因此
-
進程中已經(jīng)包含了許多可能被利用的代碼。
-
然而,攻擊者需要將這段代碼放在可預(yù)測的位置,以便攻擊者可以將代碼指針設(shè)置為指向惡意代碼!
-
-
緩解方法 1:金絲雀(例如,StackGuard,gcc 的 SSP)
-
理念:覆蓋代碼指針是可以接受的,只要我們在調(diào)用之前捕捉到它。
-
較早的一個系統(tǒng):StackGuard
-
在進入時在堆棧上放置一個金絲雀,在返回前檢查金絲雀值。
-
通常需要源代碼;編譯器插入金絲雀檢查。
-
Q: 堆棧圖中的金絲雀在哪里? A: 金絲雀必須放在堆棧上返回地址的“前面”,這樣任何溢出重寫返回地址也將重寫金絲雀。
-
堆棧布局:
| |+------------------+entry %esp ----> | return address | ^+------------------+ |new %ebp ------> | saved %ebp | |+------------------+ || CANARY | | Overflow goes+------------------+ | this way.| buf[127] | || ... | || buf[0] | |+------------------+| |
-
Q: 假設(shè)編譯器總是將金絲雀設(shè)為 4 個字節(jié)的
'a'
字符。這有什么問題嗎? -
A: 對手可以在緩沖區(qū)溢出中包含適當(dāng)?shù)慕鸾z雀值!
-
因此,金絲雀必須要么難以猜測,要么可以容易猜測但仍然能夠抵御緩沖區(qū)溢出。以下是這些方法的示例。
-
“終結(jié)符金絲雀”:四個字節(jié)(0,CR,LF,-1)
-
理念:許多 C 函數(shù)將這些字符視為終結(jié)符(例如,
gets()
,sprintf()
)。因此,如果金絲雀與這些終結(jié)符之一匹配,那么進一步的寫入將不會發(fā)生。 -
在程序初始化時生成隨機金絲雀:今天更常見(但是,你需要良好的隨機性!)。
-
堆棧金絲雀不會捕捉到哪些類型的漏洞?
-
在金絲雀之前覆蓋函數(shù)指針變量。
-
攻擊者可以覆蓋數(shù)據(jù)指針,然后利用它進行任意內(nèi)存寫入。
數(shù)據(jù)指針示例:
int *ptr = ...;char buf[128];gets(buf); // Buffer is overflowed, and overwrites ptr.*ptr = 5; // Writes to an attacker-controlled address!// Canaries can't stop this kind of thing.
-
堆對象溢出(函數(shù)指針,C++ vtables)。
-
malloc
/free
溢出
malloc 示例:
int main(int argc, char **argv) {char *p, *q;p = malloc(1024);q = malloc(1024);if(argc >= 2)strcpy(p, argv[1]);free(q);free(p);return 0;}
-
假設(shè)屬于
p
和q
的兩個內(nèi)存塊在內(nèi)存中是相鄰/附近的。 -
假設(shè)
malloc
和free
表示內(nèi)存塊如下:
malloc 內(nèi)存塊:
+----------------+ | | | App data | | | Allocated memory block+----------------+ | size | +----------------+ +----------------+| size |+----------------+| ...empty... |+----------------+ | bkwd ptr | +----------------+ | fwd ptr | Free memory block+----------------+ | size | +----------------+
-
因此,在
p
的緩沖區(qū)溢出將覆蓋q
內(nèi)存塊中的大小值!為什么這是一個問題?-
當(dāng)
free()
合并兩個相鄰的空閑塊時,需要操作bkwd
和fwd
指針… -
…并且指針計算使用大小來確定空閑內(nèi)存塊結(jié)構(gòu)的位置!
-
free()
內(nèi)部:
p = get_free_block_struct(size);bck = p->bk;fwd = p->fd;fwd->bk = bck; // Writes memory!bck->fd = fwd; // Writes memory!
-
空閑內(nèi)存塊表示為 C
struct
;通過破壞大小值,攻擊者可以強制free()
在位于攻擊者控制的內(nèi)存中的偽造struct
上操作,并具有攻擊者控制的值用于前向和后向指針。 -
如果攻擊者知道
free()
如何更新指針,他可以使用該更新代碼將任意值寫入任意位置。例如,攻擊者可以覆蓋返回地址。-
實際細節(jié)更加復(fù)雜;如果你對血腥細節(jié)感興趣,請訪問這里
-
高層次的觀點是,棧金絲雀不會阻止這種攻擊,因為攻擊者正在“越過”金絲雀并直接寫入返回地址!
-
緩解方法 2: 邊界檢查
-
總體目標: 通過檢查指針是否在范圍內(nèi)來防止指針誤用。
-
挑戰(zhàn): 在 C 語言中,很難區(qū)分有效指針和無效指針。例如,假設(shè)一個程序分配了一個字符數(shù)組…
char x[1024];
-
… 以及該數(shù)組中的某個位置的指針,例如,
char *y = &x[107];
-
增加
y
以訪問后續(xù)元素是否可以?-
如果
x
代表一個字符串緩沖區(qū),也許是。 -
如果
x
代表一個網(wǎng)絡(luò)消息,也許不。 -
如果程序使用聯(lián)合體,情況會變得更加復(fù)雜。
-
聯(lián)合體示例:
union u{int i;struct s{int j;int k;};};int *ptr = &(u.s.k); // Does this point to valid data?
-
問題 是,在 C 語言中,指針不會編碼關(guān)于該指針的預(yù)期使用語義的信息。
-
因此,很多工具不會嘗試猜測這些語義。相反,這些工具的目標并不像“完全正確”的指針語義那樣高遠:這些工具只是強制執(zhí)行堆對象和棧對象的內(nèi)存邊界。在高層次上,這是目標:
- 對于從
p
派生出的指針p'
,p'
只能被解引用以訪問屬于p
的有效內(nèi)存區(qū)域。
- 對于從
-
強制執(zhí)行內(nèi)存邊界是一個比強制“完全正確”指針語義更弱的目標。程序仍然可以通過惡意方式踐踏其內(nèi)存而自掘墳?zāi)?#xff08;例如,在聯(lián)合體示例中,應(yīng)用程序可能會寫入指針,盡管未定義)。
-
然而,邊界檢查仍然很有用,因為它可以防止任意內(nèi)存覆寫。程序只能踐踏其內(nèi)存,如果該內(nèi)存實際上已分配!這在 C 語言世界中被認為是進步。
-
邊界檢查的一個缺點是通常需要對編譯器進行更改,并且必須使用新編譯器重新編譯程序。如果只能訪問二進制文件,則這是一個問題。
有哪些實現(xiàn)邊界檢查的方法?
邊界檢查方法 #1: 電子圍欄
-
這是一個舊方法,其優(yōu)點在于簡單。
-
思路: 將每個堆對象與一個守衛(wèi)頁對齊,并使用頁表確保對守衛(wèi)頁的訪問導(dǎo)致故障。
電子圍欄:
+---------+| Guard || | ^+---------+ | Overflows cause a page exception| Heap | || obj | |+---------+
-
這是一種方便的調(diào)試技術(shù),因為堆溢出會立即導(dǎo)致崩潰,而不是悄無聲息地破壞堆并在未來某個不確定的時間導(dǎo)致失敗。
-
重要優(yōu)勢:無需源代碼即可運行:無需更改編譯器或重新編譯程序!
- 你確實需要重新鏈接它們,以便它們使用實現(xiàn)電子圍欄的新版本的
malloc
。
- 你確實需要重新鏈接它們,以便它們使用實現(xiàn)電子圍欄的新版本的
-
主要缺點:巨大的開銷!每頁只有一個對象,并且您有一個未用于“真實”數(shù)據(jù)的虛擬頁面的開銷。
-
摘要:電子圍欄可以作為調(diào)試技術(shù)很有用,并且可以防止堆對象的一些緩沖區(qū)溢出。然而,電子圍欄無法保護堆棧,并且內(nèi)存開銷太高,無法在生產(chǎn)系統(tǒng)中使用。
**邊界檢查方法#2:**胖指針
- **想法:**修改指針表示以包含邊界信息?,F(xiàn)在,指針包括關(guān)于生存在該內(nèi)存區(qū)域中的對象的邊界信息。
示例:
Regular 32-bit pointer +-----------------+| 4-byte address |+-----------------+Fat pointer (96 bits)+-----------------+----------------+---------------------+| 4-byte obj_base | 4-byte obj_end | 4-byte curr_address |+-----------------+----------------+---------------------+
- 您需要修改編譯器并重新編譯程序以使用胖指針。編譯器生成的代碼會在它解引用地址超出自己的
base ... end
范圍的指針時中止程序。
示例:
int *ptr = malloc(sizeof(int) * 2);while(1){*ptr = 42; <----------|ptr++; |} |______________________________||This line checks the current address of the pointer andensures that it's in-bounds. Thus, this line will failduring the third iteration of the loop.
-
**問題#1:**檢查所有指針解引用可能很昂貴。C 社區(qū)討厭昂貴的東西,因為 C 就是關(guān)于速度速度速度。
-
**問題#2:**胖指針與許多現(xiàn)有軟件不兼容。
-
你不能將胖指針傳遞給未修改的庫。
-
你不能在固定大小的數(shù)據(jù)結(jié)構(gòu)中使用胖指針。例如,
sizeof(that_struct)
將會改變! -
對胖指針的更新不是原子的,因為它們跨越多個字。一些程序假設(shè)指針寫入是原子的。
-
邊界檢查方法#3:影子數(shù)據(jù)結(jié)構(gòu)
**想法:**使用影子數(shù)據(jù)結(jié)構(gòu)來跟蹤邊界信息(Jones and Kelly,Baggy bounds)。
-
對于每個分配的對象,存儲對象的大小。例如:
-
記錄傳遞給 malloc 的值:
char *p = malloc(mem_size);
-
對于靜態(tài)變量,值由編譯器確定:
char p[256];
-
對于每個指針,我們需要對兩個操作進行干預(yù):
-
指針算術(shù):
char *q = p + 256;
-
指針解引用:
char ch = *q;
-
-
Q: 為什么我們需要對解引用進行干預(yù)?不能只進行算術(shù)嗎?
-
A: 無效指針并不總是一個錯誤!例如,數(shù)組最后一個元素之外的一個元素的指針可能被用作循環(huán)中的停止測試。應(yīng)用程序還可以執(zhí)行一些愚蠢的操作,如:
-
模擬從 1 開始的數(shù)組
-
計算 p+(a-b)為(p+a)-b
-
生成稍后檢查有效性的 OOB 指針
-
-
因此,僅僅創(chuàng)建無效指針不應(yīng)該導(dǎo)致程序失敗。
-
Q: 為什么我們需要對算術(shù)進行干預(yù)?不能只進行解引用嗎?
-
A: 干預(yù)算術(shù)是允許我們跟蹤指針的來源并設(shè)置 OOB 位的原因。沒有 OOB,我們將無法確定派生指針何時超出其基本對象的邊界。
-
-
挑戰(zhàn) 1: 我們?nèi)绾握业匠R?guī)指針(即在邊界內(nèi)的指針)的邊界信息?
-
天真: 使用哈希表或區(qū)間樹將地址映射到邊界。
-
好: 空間高效(僅存儲正在使用的指針的信息,而不是所有可能的地址)。
-
不好: 查找慢(每次查找多次內(nèi)存訪問)。
-
-
天真: 使用數(shù)組存儲每個內(nèi)存地址的邊界信息。
-
好: 快速!
-
不好: 內(nèi)存開銷很高。
-
-
-
挑戰(zhàn) 2: 我們?nèi)绾螐娭圃浇缰羔樈庖檬?#xff1f;
-
天真: 對每個指針解引用進行檢測。
-
好: 哦,它有效。
-
不好: 昂貴。我們必須為每次解引用執(zhí)行額外的代碼!
-
-
Baggy bounds 方法:5 個技巧
-
Trick 1: 將每個分配向上舍入為 2 的冪,并將分配的起始對齊到該 2 的冪。
-
Trick 2: 將每個范圍限制表示為
log_2(alloc_size)
。- 對于 32 位指針,只需要 5 位來表示可能的范圍:我們只能分配 32 種不同大小的對象:
21 = 2 字節(jié),22 = 4 字節(jié)...,231 字節(jié)或 232 字節(jié)
,并且我們存儲分配大小的以 2 為底的對數(shù),這是一個介于 1 和 32 之間的數(shù)字,因此我們只需要 5 位來表示它。
- 對于 32 位指針,只需要 5 位來表示可能的范圍:我們只能分配 32 種不同大小的對象:
-
Trick 3: 在線性數(shù)組中存儲限制信息:每個條目一個字節(jié)的快速查找。此外,我們可以使用虛擬內(nèi)存按需分配數(shù)組!
-
Trick 4: 以插槽粒度(例如,16 字節(jié))分配內(nèi)存:更少的數(shù)組條目。
-
這意味著最小分配大小為 16 字節(jié)
-
…而且,由于指針將對齊到其分配大小邊界,這意味著指針的最后 4 位都是零
-
示例:
slot_size = 16p = malloc(16); --> table[p/slot_size] = 4;p = malloc(32); --> table[p/slot_size] = 5;\-> table[(p/slot_size) + 1] = 5;
-
Trick 4 (續(xù)):
- 現(xiàn)在,給定一個已知的好指針
p
,和一個派生指針p'
,我們可以通過檢查這兩個指針的地址位中是否有相同的前綴,并且它們只在它們的e
個最低有效位上有所不同,其中e
等于分配大小的對數(shù),來測試p'
是否有效。
- 現(xiàn)在,給定一個已知的好指針
示例:
C code------ p' = p + i;Bounds check------------size = 1 << table[p >> log_of_slot_size];base = p & ~(size - 1);(p' >= base) && ((p' - base) < size)Optimized bounds check----------------------(p^p') >> table[p >> log_of_slot_size] == 0
- Trick 5: 使用虛擬內(nèi)存系統(tǒng)來防止越界解引用:在 OOB 指針中設(shè)置最高有效位,然后將地址空間上半部分的頁面標記為不可訪問。這樣,我們就不必對指針解引用進行檢測以防止錯誤的內(nèi)存訪問!
示例代碼(假設(shè)slot_size=16
)
char *p = malloc(44); //Note that the nearest power of 2 (i.e.,//64 bytes) are allocated. So, there are//64/(slot_size) = 4 bounds table entries//that are set to log_2(64) = 6.char *q = p + 60; //This access is ok: It's past p's object//size of 44, but still within the baggy//bounds of 64.char *r = q + 16; //r is now at an offset of 60+16=76 from//p. This means that r is (76-64)=12 bytes//beyond the end of p. This is more than//half a slot away, so baggy bounds will//raise an error.char *s = q + 8; //s is now at an offset of 60+8=68 from p.//So, s is only 4 bytes beyond the baggy//bounds, which is les than half a slot//away. No error is raised, but the OOB//high-order bit is set in s, so that s//cannot be dereferenced.char *t = s - 32; //t is now back inside the bounds, so//the OOB bit is cleared.
對于 OOB 指針,高位被設(shè)置(如果 OOB 在半個插槽內(nèi))。- 通常,操作系統(tǒng)內(nèi)核位于上半部分,通過分頁硬件保護自身。- 問: 為什么越界是半個插槽?
那么作業(yè)問題的答案是什么?
char *p = malloc(256);char *q = p + 256;char ch = *q; // Does this raise an exception?// Hint: How big is the baggy bound for p?
Baggy bounds 論文勘誤
Baggy bounds 論文中的一些錯誤:
-
圖 3,顯式邊界檢查應(yīng)該生成如下大小:
size = 1 << table[p >> log_of_slot_size]
-
圖 3,優(yōu)化的邊界檢查應(yīng)該是:
(p^p') >> table[p >> log_of_slot_size] == 0
-
圖 5 和 18,指針算術(shù)代碼應(yīng)該是以下之一:
-
char *p = &buf[i];
-
char *p = buf + i;
-
Baggy bounds(續(xù))
注意: 這些講座筆記是從 2014 年 6.858 課程網(wǎng)站上發(fā)布的筆記中稍作修改而來。
示例代碼:(假設(shè)slot_size = 16
)
char *p = malloc(44); // Note that the nearest power of 2 (i.e.,// 64 bytes) are allocated. So, there are// 64/(slot_size) = 4 bounds table entries// that are set to log_2(64) = 6.char *q = p + 60; // This access is ok: It's past p's object// size of 44, but still within the baggy// bounds of 64.char *r = q + 16; // ERROR: r is now at an offset of 60+16=76// from p. This means that r is (76-64)=12// beyond the end of p. This is more than// half a slot away, so baggy bounds will// raise an error.char *s = q + 8; // s is now at an offset of 60+8=68 from p.// So, s is only 4 bytes beyond the baggy// bounds, which is less than half a slot// away. No error is raised, but the OOB// high-order bit is set in s, so that s// cannot be derefernced.char *t = s - 32; // t is now back inside the bounds, so// the OOB bit is cleared.
對于越界指針,高位被設(shè)置(如果在半個插槽內(nèi)越界)。
-
通常,操作系統(tǒng)內(nèi)核位于上半部分,通過分頁硬件保護自身。
-
Q: 為什么要為越界分配半個插槽?
那么作業(yè)問題的答案是什么?
char *p = malloc(255);char *q = p + 256;char ch = *q; // Does this raise an exception?// Hint: How big is the baggy bound for p?
寬松邊界檢查是否必須檢測每個內(nèi)存地址計算和訪問?
- 不,靜態(tài)分析可以證明某些地址始終是安全的。但是,某些地址計算是“不安全”的,因為無法靜態(tài)確定其值的邊界。這些不安全的變量需要檢查。
處理函數(shù)調(diào)用參數(shù)有點棘手,因為 x86 調(diào)用約定是固定的,即硬件期望棧上的某些內(nèi)容放在特定位置。
-
但是,我們可以將不安全的參數(shù)復(fù)制到一個單獨的區(qū)域,并確保復(fù)制的參數(shù)對齊和受保護。
-
Q: 我們是否必須在函數(shù)返回時用復(fù)制的值覆蓋原始參數(shù)?
-
A: 不,因為在 C 語言中一切都是按值傳遞的!
寬松邊界檢查如何確保與現(xiàn)有庫的二進制兼容性?
特別是,寬松邊界代碼如何與由未經(jīng)檢測的代碼分配的內(nèi)存指針交互?
-
解決方案: 邊界表中的每個條目都初始化為值 31,這意味著相應(yīng)的指針具有 231 的內(nèi)存邊界(這是所有可尋址內(nèi)存)。
-
在經(jīng)過檢測的代碼中進行內(nèi)存分配時,邊界條目如前所述設(shè)置,并在釋放內(nèi)存時重置為 31。
-
分配給未經(jīng)檢測的代碼的內(nèi)存永遠不會改變邊界表條目的默認值 31;因此,當(dāng)經(jīng)過檢測的代碼與這些指針交互時,邊界錯誤永遠不會發(fā)生
-
例子:
Contiguous range ofmemory used for theheap+-------------------+| || || Heap allocated by || uninstrumented |---+| code | \ Bounds table| | \+-------------------+ \ +-----------+| | +->| || | | Always 31 || Heap allocated by | | || instrumented code | +-----------+| | | Set using || |--------->| baggy bnds|+-------------------+ +-----------+
-
這一切意味著什么?
-
無法檢測在未經(jīng)檢測的代碼中生成的越界指針。
-
無法檢測傳遞給庫的越界指針何時再次進入邊界內(nèi)。
-
Q: 為什么?
-
A: 因為未經(jīng)檢測的代碼中沒有指針檢查可以清除高位越界位!
-
Q: 為什么要檢測
strcpy()
和memcpy()
? -
A: 否則,這些函數(shù)就是未經(jīng)檢測的代碼,并且會遇到我們剛剛討論過的相同問題。例如,現(xiàn)成的
strcpy()
不能確保目標有足夠的空間來存儲源!
-
-
寬松位如何利用 64 位地址空間?
可以擺脫存儲邊界信息的表,并將其放入指針中。
Regular pointer +---------------+-------+------------------------+| zero | size | supported addr space |+---------------+-------+------------------------+21 5 38OOB pointer+--------+------+-------+------------------------+| offset | size | zero | supported addr space |+--------+------+-------+------------------------+13 5 8 38
這類似于一個胖指針,但具有以下優(yōu)點……
-
標記指針與常規(guī)指針大小相同
-
對它們的寫入是原子的
……以便不破壞程序員的期望,并且數(shù)據(jù)布局保持不變。
還要注意,使用標記指針,我們現(xiàn)在可以跟蹤遠離基本指針多得多的越界指針。這是因為現(xiàn)在我們可以使用偏移量標記指針,指示它們距離基本指針有多遠。在 32 位世界中,如果沒有額外的數(shù)據(jù)結(jié)構(gòu),我們無法跟蹤越界偏移量!
在 baggy bounds 系統(tǒng)中仍然可以發(fā)動緩沖區(qū)溢出攻擊嗎?
是的,因為這個世界充滿了悲傷。
-
可能會利用未經(jīng)檢測的庫中的漏洞。
-
可能會利用時間漏洞(使用后釋放)。
-
混合緩沖區(qū)和代碼指針
例子:
struct {char buf[256];void (*f) (void);} my_type;
請注意*f
不是分配的類型,因此在調(diào)用期間與其解引用相關(guān)聯(lián)的邊界檢查不存在。因此,如果s.buf
溢出(例如,由未經(jīng)檢測的庫中的錯誤引起),并且s.f
被損壞,那么對f
的調(diào)用不會導(dǎo)致邊界錯誤!
重新排列 f 和 buf 會有幫助嗎?
-
可能會破壞依賴結(jié)構(gòu)布局的應(yīng)用程序。
-
如果這是一個(
struct my_type
)數(shù)組,可能不會有幫助。
一般來說,邊界檢查的成本是什么?
-
邊界信息的空間開銷(胖指針或 baggy bounds 表)。
-
Baggy bounds 還會為 buddy 分配器使用的額外填充內(nèi)存增加空間開銷(盡管所有流行的動態(tài)內(nèi)存分配算法都會有一定程度的開銷)。
-
指針算術(shù)和解引用的 CPU 開銷。
-
虛警!
-
未使用的越界指針。
-
臨時超出邊界指針超過
slot_size/2
。 -
指針到整數(shù)的轉(zhuǎn)換和反向轉(zhuǎn)換。
-
將越界指針傳遞給未經(jīng)檢查的代碼(高地址位被設(shè)置,因此如果未經(jīng)檢查的代碼使用該指針進行算術(shù)運算,可能會導(dǎo)致混亂)。
-
-
需要大量編譯器支持。
因此,baggy bounds 檢查是一種減輕有缺陷代碼中緩沖區(qū)溢出的方法。
實現(xiàn)邊界檢查的更多方法
方法 4:非可執(zhí)行內(nèi)存(AMD 的 NX 位,Windows DEP,W^X 等)
-
現(xiàn)代硬件允許為內(nèi)存指定讀取、寫入和執(zhí)行權(quán)限。(R、W 權(quán)限很久以前就有了;執(zhí)行權(quán)限是最近才有的。)
-
可以將堆棧標記為不可執(zhí)行,這樣對手就無法運行他們的代碼。
-
更一般地,一些系統(tǒng)執(zhí)行“W^X”,意味著所有內(nèi)存要么可寫,要么可執(zhí)行,但不能同時。 (當(dāng)然,既不可寫也不可執(zhí)行也是可以的。)
-
優(yōu)勢: 可能無需進行任何應(yīng)用程序更改即可運行。
-
優(yōu)勢: 硬件一直在監(jiān)視你,不像操作系統(tǒng)。
-
缺點: 動態(tài)生成代碼更困難(尤其是使用 W^X)。
-
像 Java 運行時、Javascript 引擎這樣的 JIT 會即時生成 x86 代碼。
-
可以通過先寫入,然后更改為可執(zhí)行來解決問題。
-
-
方法 5:隨機化內(nèi)存地址(ASLR,堆棧隨機化等)
-
觀察:許多攻擊在 shellcode 中使用硬編碼地址![攻擊者抓取一個二進制文件并使用 gdb 來找出東西的位置。]
-
因此,我們可以使攻擊者難以猜測有效的代碼指針。
-
堆棧隨機化:將堆棧移動到隨機位置,并/或在堆棧變量之間放置填充。這使得攻擊者更難確定:
-
當(dāng)前幀的返回地址位于何處
-
攻擊者的 shellcode 緩沖區(qū)將位于何處
-
-
隨機化整個地址空間(地址空間布局隨機化):隨機化堆棧、堆、DLL 的位置等。
-
依賴于很多代碼是可重定位的這一事實。
-
動態(tài)加載器可以為每個庫、程序選擇隨機地址。
-
對手不知道 system()等函數(shù)的地址。
-
-
這仍然可以被利用嗎?
-
對手可能猜測隨機性。特別是在 32 位機器上,沒有太多的隨機位(例如,1 位屬于內(nèi)核/用戶模式劃分,12 位不能被隨機化,因為內(nèi)存映射頁面需要與頁面邊界對齊等)。
-
例如,攻擊者可能進行緩沖區(qū)溢出并嘗試用
usleep(16)
的地址覆蓋返回地址,然后查看連接是否在 16 秒后掛起,或者是否崩潰(在這種情況下,服務(wù)器會使用相同的 ASLR 偏移量 fork 一個新的 ASLR 進程)。usleep()
可能在 21? 或 22? 個地方之一。更多細節(jié)。 -
ASLR 在 64 位機器上更實用(很容易有 32 位的隨機性)。
-
-
-
對手可能提取隨機性。
-
程序可能生成包含指針的堆棧跟蹤或錯誤消息。
-
如果對手可以運行一些代碼,他們可能能夠提取真實地址(JIT 編譯的代碼?)。
-
Flash 的字典(哈希表)中的可愛地址泄漏:
-
讓受害者訪問您的 Flash 啟用頁面(例如,購買廣告)。
-
哈希表在內(nèi)部計算鍵的哈希值。
-
整數(shù)的哈希值是整數(shù)本身。
-
對象的哈希值是其內(nèi)存地址。
-
遍歷哈希表是從最低哈希鍵到最高哈希鍵進行的。
-
因此,攻擊者創(chuàng)建一個字典,插入一個包含 shellcode 的字符串對象,然后向字典中插入一堆數(shù)字。
-
通過遍歷字典,攻擊者可以確定字符串對象位于何處,看看對象引用落在哪些整數(shù)之間!
-
現(xiàn)在,用 shellcode 地址覆蓋代碼指針并繞過 ASLR!
-
-
-
對手可能不關(guān)心確切要跳轉(zhuǎn)到哪里。
- 例如:“堆噴灑”:填充內(nèi)存以便隨機跳轉(zhuǎn)是可以的!
-
對手可能利用一些未隨機化的代碼(如果存在這樣的代碼)。
-
隨機化的一些其他有趣用途:
-
系統(tǒng)調(diào)用隨機化(每個進程有自己的系統(tǒng)調(diào)用號碼)。
-
指令集隨機化,以便攻擊者不能輕易確定特定程序?qū)嵗?#34;shellcode"是什么樣子。
- 例子: 想象一下,處理器有一個特殊的寄存器來保存“解碼密鑰”。每個特定應(yīng)用程序的安裝都與一個隨機密鑰相關(guān)聯(lián)。應(yīng)用程序中的每條機器指令都與該密鑰進行異或運算。當(dāng)操作系統(tǒng)啟動進程時,它設(shè)置解碼密鑰寄存器,處理器使用此密鑰解碼指令后再執(zhí)行它們。
-
實際上使用了哪些緩沖區(qū)溢出防御措施?
-
gcc 和 MSVC 默認啟用棧保護。
-
Linux 和 Windows 默認包含 ASLR 和 NX。
-
由于:
-
性能開銷
-
需要重新編譯程序
-
誤報:安全工具中的常見主題:誤報阻止了工具的采用!通常,一些遺漏但沒有誤報比零遺漏但有誤報更好。
-
返回導(dǎo)向編程(ROP)
ASLR 和 DEP 是非常強大的防御技術(shù)。
-
DEP 防止攻擊者執(zhí)行自己選擇的棧代碼。
-
ASLR 可以防止攻擊者確定 shellcode 或返回地址的位置。
-
但是,如果攻擊者能夠找到位于已知位置的具有已知功能的預(yù)先存在的代碼呢?那么,攻擊者可以調(diào)用該代碼來做壞事。
-
當(dāng)然,預(yù)先存在的代碼并不是故意惡意,因為它是應(yīng)用程序的正常部分。
-
但是,攻擊者可以傳遞意外的參數(shù)給該代碼,或者跳轉(zhuǎn)到代碼的中間并僅執(zhí)行該代碼的所需部分。
-
這種攻擊稱為返回導(dǎo)向編程,或ROP。為了理解 ROP 的工作原理,讓我們看一個具有安全漏洞的簡單 C 程序。示例改編自此處。
void run_shell(){system("/bin/bash");}void process_msg(){char buf[128];gets(buf);}
假設(shè)系統(tǒng)不使用 ASLR 或棧保護,但使用了 DEP。process_msg()
存在明顯的緩沖區(qū)溢出,但攻擊者無法利用此溢出在buf
中執(zhí)行 shellcode,因為 DEP 使棧不可執(zhí)行。然而,run_shell()
函數(shù)看起來很誘人… 攻擊者如何執(zhí)行它?
-
攻擊者反匯編程序并找出
run_shell()
的起始地址在哪里。 -
攻擊者發(fā)起緩沖區(qū)溢出,并用
run_shell()
的地址覆蓋process_msg()
的返回地址。砰!攻擊者現(xiàn)在可以訪問以應(yīng)用程序權(quán)限運行的 shell。
例子:
+------------------+entry %ebp ----> | .. prev frame .. || | | |+------------------+entry %esp ----> | return address | ^ <--Gets overwritten +------------------+ | with address ofnew %ebp ------> | saved %ebp | | run_shell()+------------------+ || buf[127] | || ... | || buf[0] | |new %esp ------> +------------------+
這是我們已經(jīng)看過的緩沖區(qū)溢出的直接擴展。但是我們?nèi)绾蜗蛭覀円D(zhuǎn)到的函數(shù)傳遞參數(shù)呢?
char *bash_path = "/bin/bash";void run_cmd(){system("/something/boring");}void process_msg(){char buf[128];gets(buf);}
在這種情況下,我們要傳遞的參數(shù)已經(jīng)位于程序代碼中。程序中還存在一個對system()
的調(diào)用,但該調(diào)用并未傳遞我們想要的參數(shù)。
我們知道system()
必須與我們的程序鏈接。因此,使用我們可靠的朋友 gdb,我們可以找到system()
函數(shù)的位置以及 bash_path 的位置。
要使用bash_path
參數(shù)調(diào)用system()
,我們必須設(shè)置堆棧,以便在跳轉(zhuǎn)到它時,system()
期望堆棧上有這些內(nèi)容:
| ... |+------------------+| argument | The system() argument.+------------------+%esp ----> | return addr | Where system() should +------------------+ ret after it hasfinished.
因此,緩沖區(qū)溢出需要設(shè)置一個看起來像這樣的堆棧:
+------------------+entry %ebp ----> | .. prev frame .. || || || * - - - - - - - | ^| | | Address of bash_path + * - - - - - - - | || | | Junk return addr for system()+------------------+ |entry %esp ----> | return address | | Address of system()+------------------+ | new %ebp ------> | saved %ebp | | Junk+------------------+ || buf[127] | || ... | | Junk| buf[0] | |new %esp ------> +------------------+ |
本質(zhì)上,我們所做的是為system()
調(diào)用設(shè)置了一個虛假的調(diào)用幀!換句話說,我們模擬了編譯器如果真的想要設(shè)置一個對system()
的調(diào)用會做什么。
如果字符串"/bin/bash"
不在程序中怎么辦?
-
我們可以將該字符串包含在緩沖區(qū)溢出中,然后使
system()
的參數(shù)指向該字符串。| h\0 | ^| * - - - - - - - | || /bas | || * - - - - - - - | || /bin | | <--------------------+| * - - - - - - - | | || | | Address of bash_path--++ * - - - - - - - | || | | Junk return addr from system()+------------------+ | entry %esp ----> | return address | | Address of system()+------------------+ | new %ebp ------> | saved %ebp | | Junk+------------------+ || buf[127] | || ... | | Junk| buf[0] | | new %esp ------> +------------------+ |
請注意,在這些示例中,我一直假設(shè)攻擊者使用了來自system()
的無用返回地址。然而,攻擊者也可以將其設(shè)置為有用的內(nèi)容。
實際上,通過將其設(shè)置為有用的內(nèi)容,攻擊者可以鏈接調(diào)用在一起!
**目標:**我們想要多次調(diào)用system("/bin/bash")
。假設(shè)我們找到了三個地址:
-
system()
的地址 -
字符串"/bin/bash"的地址
-
這些 x86 操作碼的地址:
pop %eax //Pops the top-of-stack and puts it in %eaxret //Pops the top-of-stack and puts it in %eip
這些操作碼是“小工具”的一個示例。小工具是預(yù)先存在的指令序列,可以串聯(lián)在一起創(chuàng)建一個利用。請注意,有一些用戶友好的工具可以幫助您從現(xiàn)有二進制文件中提取小工具(例如,msfelfscan)。
| | ^+ * - - - - - - - + || | | Address of bash_path -+ Fake calling+ * - - - - - - - + | | frame for(4) | | | Address of pop/ret * + system()+ * - - - - - - - + | (3) | | | Address of system()+ * - - - - - - - + |(2) | | | Address of bash_path -+ Fake calling+ * - - - - - - - + | | frame for(1) | | | Address of pop/ret * + system()+------------------+ |entry %esp ----> | return address | | Address of system()+------------------+ | new %ebp ------> | saved %ebp | | Junk+------------------+ || buf[127] | || ... | | Junknew %esp ------> | buf[0] | |+------------------+ |
那么,這是如何工作的呢?記住,返回指令彈出棧頂并將其放入%eip。
-
溢出的函數(shù)通過發(fā)出
ret
來終止。ret
彈出棧頂(system()
的地址)并將%eip
設(shè)置為它。system()
開始執(zhí)行,%esp
現(xiàn)在在(1),并指向pop/ret
小工具。 -
system()
執(zhí)行完畢并調(diào)用ret
。%esp
從(1)->(2),因為ret
指令彈出棧頂并將其分配給%eip
。%eip
現(xiàn)在是pop/ret
小工具的開始。 -
pop/ret
小工具中的 pop 指令從棧中丟棄bash_path
變量。%esp
現(xiàn)在在(3)。我們?nèi)匀辉?code>pop/ret小工具中! -
pop/ret
小工具中的ret
指令彈出棧頂并將其放入%eip
?,F(xiàn)在我們再次在system()
中,并且%esp
在(4)。
等等。
基本上,我們創(chuàng)建了一種新類型的機器,它由堆棧指針驅(qū)動,而不是常規(guī)指令指針!隨著堆棧指針沿著堆棧移動,它執(zhí)行的小工具的代碼來自預(yù)先存在的程序代碼,數(shù)據(jù)來自緩沖區(qū)溢出創(chuàng)建的堆棧數(shù)據(jù)。
這種攻擊規(guī)避了 DEP 保護–我們沒有生成任何新代碼,只是調(diào)用了現(xiàn)有的代碼!
棧讀取:打敗金絲雀
假設(shè):
-
遠程服務(wù)器存在緩沖區(qū)溢出漏洞。
-
服務(wù)器崩潰并重新啟動,如果金絲雀值設(shè)置為不正確的值。
-
當(dāng)服務(wù)器重新啟動時,canary 不會重新隨機化,ASLR 也不會重新隨機化,例如,因為服務(wù)器使用 Linux 的 PIE 機制,并且使用
fork()
來創(chuàng)建新的工作進程而不是execve()
。
因此,要確定一個 8 字節(jié)的 canary 值:
char canary[8];for(int i = 1; i <= 8; i++){ //For each canary byte . . .for(char c = 0; c < 256; c++){ //. . . guess the value.canary[i-1] = c;server_crashed = try_i_byte_overflow(i, canary);if(!server_crashed){//We've discovered i-th byte of the//the canary!break;}}}
此時我們已經(jīng)有了 canary,但請記住,該攻擊假設(shè)服務(wù)器在崩潰后使用相同的 canary。
猜測一個字節(jié)的正確值平均需要 128 次猜測,因此在 32 位系統(tǒng)上,我們只需要 4*128=512
次猜測來確定 canary(在 64 位系統(tǒng)上,我們需要 8*128=1024
次)。
-
比在 canary 上進行暴力破解攻擊要快得多(在具有 16/28 位 ASLR 隨機性的 32/64 位系統(tǒng)上,預(yù)期猜測次數(shù)為
21?
或22?
)。 -
暴力破解攻擊可以使用我們之前討論過的
usleep(16)
探測。 -
Canary 讀取可以擴展到讀取緩沖區(qū)溢出可以覆蓋的任意值!
因此,我們已經(jīng)討論了如果服務(wù)器在重新生成時不更改 canaries,我們?nèi)绾文軌驌魯‰S機化的 canaries。我們還展示了如何使用 gdb 和 gadgets 來執(zhí)行程序中預(yù)先存在的函數(shù),使用攻擊者控制的參數(shù)。但是如果服務(wù)器使用 ASLR 呢?這將阻止您使用離線分析來找到預(yù)先存在的函數(shù)的位置?
這就是今天講座論文討論的內(nèi)容。該論文假設(shè)我們使用的是 64 位機器,所以從現(xiàn)在開始,在本講座中我們也將假設(shè)如此。在這次討論中,主要的變化是函數(shù)參數(shù)現(xiàn)在是通過寄存器傳遞而不是通過棧傳遞。
盲目返回導(dǎo)向編程
步驟 1:找到一個 stop gadget
-
stop gadget 是指指向會掛起程序但不會崩潰的代碼的返回地址。
-
一旦攻擊者能夠擊敗 canaries,他就可以覆蓋溢出函數(shù)的返回地址并開始猜測 stop gadget 的位置。如果客戶端網(wǎng)絡(luò)連接突然關(guān)閉,猜測的地址不是 stop gadget。如果連接保持打開,那么該 gadget 就是 stop gadget。
步驟 2:找到彈出棧中條目的 gadgets
-
一旦你有了一個 stop gadget,你可以用它來找到其他將棧中條目彈出并存入寄存器的 gadgets。
-
定位棧彈出 gadgets 的三個構(gòu)建塊:
-
probe: 潛在的棧彈出 gadget 的地址
-
stop: stop gadget 的地址
-
crash: 非可執(zhí)行代碼的地址(0x0)
-
示例: 找到一個彈出棧中一個元素的 gadget。
sleep(10)^ ^+--- pop rax / \| ret / \| \--->[stop] 0x5.... 0x5....| [trap] 0x0 0x0 <-----------------++----------[probe] 0x4...8 0x4...c -->xor rax, rax | Crash!ret |\__________|
當(dāng)你這樣做很多次后,你將擁有一系列彈出棧中一個元素然后返回的 gadgets。然而,你不會知道這些 gadgets 將彈出的值存儲在哪個 寄存器 中。
-
你需要知道哪些寄存器用于存儲數(shù)據(jù),以便您可以發(fā)出系統(tǒng)調(diào)用。每個系統(tǒng)調(diào)用都期望其參數(shù)在一組特定的寄存器中。
-
請注意,我們也不知道
syscall()
庫函數(shù)的位置。
步驟 3:找到 syscall() 并確定 pop gadgets 使用哪些寄存器
-
pause()
是一個不帶參數(shù)的系統(tǒng)調(diào)用(因此忽略寄存器中的所有內(nèi)容)。 -
要找到
pause()
,攻擊者在棧上鏈接所有的"pop x; ret"
小工具,將pause()
的系統(tǒng)調(diào)用號作為每個小工具的"參數(shù)"推送進去。在鏈的底部,攻擊者放置了syscall()
的猜測地址。| | ^+ * - - - - - - - + || | | Guessed addr of syscall() + * - - - - - - - + | | | | ...+ * - - - - - - - + | | | | Sys call # for pause+ * - - - - - - - + || | | Address of pop rsi; ret //Gadget 2+ * - - - - - - - + | | | | Sys call # for pause+------------------+ | entry %esp ----> | return address | | Address of pop rdi; ret //Gadget 1+------------------+ | new %ebp ------> | saved %ebp | | Junk+------------------+ || buf[127] | || ... | | Junk new %esp ------> | buf[0] | |+------------------+ |
因此,在這個鏈的末端,彈出小工具已經(jīng)將pause()
的系統(tǒng)調(diào)用號放入了一堆寄存器中,希望包括rax
,這是syscall()
查找系統(tǒng)調(diào)用號的寄存器。
一旦這個超級小工具引發(fā)了暫停,我們就知道已確定了syscall()
的位置?,F(xiàn)在我們需要確定哪個小工具將棧頂彈出到rax
中。攻擊者可以通過逐步嘗試一個小工具并查看是否可以調(diào)用pause()
來弄清楚這一點。
要識別任意的"pop x; ret"
小工具,可以使用與您試圖找到的x
寄存器相關(guān)的其他系統(tǒng)調(diào)用的技巧。
因此,這個階段的結(jié)果是知道"pop x; ret"
小工具,syscall()
的位置。
第 4 步:調(diào)用 write()
現(xiàn)在我們想要在服務(wù)器與攻擊者客戶端之間的網(wǎng)絡(luò)套接字上調(diào)用寫入調(diào)用。我們需要以下小工具:
pop rdi; ret (socket)pop rsi; ret (buffer)pop rdx; ret (length)pop rax; ret (write syscall number)syscall
我們必須猜測套接字的值,但這在實踐中相當(dāng)容易,因為 Linux 將進程限制為同時打開 1024 個文件描述符,并且新文件描述符必須是可用的最低文件描述符(因此猜測一個小文件描述符在實踐中效果很好)。
要測試我們是否猜對了文件描述符,只需嘗試寫入并查看是否收到任何內(nèi)容!
一旦我們有了套接字號碼,我們發(fā)出一個寫入請求,發(fā)送的數(shù)據(jù)是指向程序的.text
段的指針!這使得攻擊者可以讀取程序的代碼(雖然已隨機化,但現(xiàn)在完全為攻擊者所知!)?,F(xiàn)在攻擊者可以直接找到更強大的小工具,并利用這些小工具打開一個 shell。
防御 BROP 攻擊
-
每次崩潰后重新隨機化 canaries 和地址空間!
-
使用
exec()
代替fork()
來創(chuàng)建進程,因為fork()
會將父進程的地址空間復(fù)制給子進程。 -
有趣的是,Windows 不容易受到 BROP 攻擊的影響,因為 Windows 沒有
fork()
的等效功能。
-
-
崩潰后休眠?
- 現(xiàn)在 BROP 攻擊是一種拒絕服務(wù)攻擊!
-
邊界檢查?
- 高達 2 倍的性能開銷…
有關(guān) ROP 和 x86 調(diào)用約定的更多信息
-
x86 調(diào)用約定簡介
-
返回導(dǎo)向編程簡介
-
深入了解 ROP:返回導(dǎo)向編程的快速介紹
-
返回導(dǎo)向編程:系統(tǒng)、語言和應(yīng)用
OKWS
注意: 這些講座筆記略有修改,來自 2014 年 6.858 課程網(wǎng)站上發(fā)布的筆記。
今天的講座:如何在 Unix 上構(gòu)建一個安全的 Web 服務(wù)器。我們實驗室 Web 服務(wù)器 zookws 的設(shè)計靈感來自于 OKWS。
特權(quán)分離
-
大型安全理念
-
將系統(tǒng)分割成各自具有特權(quán)的模塊
- 想法: 如果一個模塊被破壞,那么其他模塊就不會被破壞
-
經(jīng)常使用:
-
虛擬機(例如,在自己的虛擬機中運行網(wǎng)站)
-
SSH(分離 sshd、agent)
-
-
挑戰(zhàn):
-
模塊需要共享
-
需要操作系統(tǒng)支持
-
需要仔細使用操作系統(tǒng)正確設(shè)置事物
-
性能
-
OKWS
-
特權(quán)分離的有趣案例研究
-
服務(wù)之間有很多共享
- 嚴格的分區(qū)不起作用
-
大量的代碼
-
-
在 OKcupid 之外并不廣泛使用
-
許多網(wǎng)站都有他們的特權(quán)分離計劃
-
但沒有描述他們計劃的論文
-
背景:Unix 中的安全和保護
-
典型的主體:用戶 ID、組 ID(32 位整數(shù))。
-
每個進程都有一個用戶 ID(uid)和一組組 ID(gid + grouplist)。
-
出于大多是歷史原因,一個進程有一個 gid +額外的組列表。
-
超級用戶主體(root)由
uid=0
表示,繞過大多數(shù)檢查。
-
-
Unix 中的對象+操作是什么,操作系統(tǒng)如何進行訪問控制?
-
文件、目錄。
-
文件操作:讀、寫、執(zhí)行、更改權(quán)限,…
-
目錄操作:查找、創(chuàng)建、刪除、重命名、更改權(quán)限,…
-
每個 inode 都有一個所有者用戶和組。
-
每個 inode 對于用戶、組、其他人都有讀、寫、執(zhí)行權(quán)限。
-
通常表示為寫入基數(shù) 8(八進制)的位向量;
- 八進制很好用,因為每個數(shù)字是 3 位(讀、寫、執(zhí)行)。
-
誰可以更改文件的權(quán)限?
- 只有用戶所有者(進程 UID)。
-
對文件進行硬鏈接:需要對文件有寫權(quán)限。
-
可能的理由:配額。
-
可能的理由:防止將
/etc/passwd
硬鏈接到具有全局可寫/var/mail
的/var/mail/root
。
-
-
目錄的執(zhí)行意味著能夠查找名稱(但不能 ls)。
-
檢查進程打開文件
/etc/passwd
:-
必須能夠在
/
中查找'etc'
,在/etc
中查找'passwd'
。 -
必須能夠打開
/etc/passwd
(讀或讀寫)。
-
-
假設(shè)你想要文件對 group1 和 group2 的交集可讀。
- 在 Unix 中是否可能實現(xiàn)這一點?
-
-
文件描述符。
-
文件打開時執(zhí)行的文件訪問控制檢查。
-
一旦進程有一個打開的文件描述符,就可以繼續(xù)訪問。
-
進程可以傳遞文件描述符(通過 Unix 域套接字)。
-
-
進程。
-
你可以對一個進程做什么?
- 調(diào)試(ptrace),發(fā)送信號,等待退出并獲取狀態(tài),
-
調(diào)試,發(fā)送信號:必須具有相同的 UID(幾乎)。
- 各種例外,在實踐中這變得棘手。
-
等待/獲取退出狀態(tài):必須是該進程的父進程。
-
-
內(nèi)存。
-
一個進程通常不能命名另一個進程的內(nèi)存。
-
例外:調(diào)試機制。
-
例外:內(nèi)存映射文件。
-
-
網(wǎng)絡(luò)。
-
操作。
-
綁定到一個端口。
-
連接到某個地址。
-
讀/寫連接。
-
發(fā)送/接收原始數(shù)據(jù)包。
-
-
規(guī)則:
-
只有 root(UID 0)可以綁定到低于 1024 的端口;
-
(例如,任意用戶不能在端口 80 上運行 Web 服務(wù)器。)
-
只有 root 可以發(fā)送/接收原始數(shù)據(jù)包。
-
任何進程都可以連接到任何地址。
-
只能讀取/寫入進程具有文件描述符的連接上的數(shù)據(jù)。
-
-
此外,防火墻施加自己的檢查,與進程無關(guān)。
-
-
-
進程的主體是如何設(shè)置的?
-
系統(tǒng)調(diào)用:
setuid()
、setgid()
、setgroups()
。 -
只有 root(UID 0)可以調(diào)用這些系統(tǒng)調(diào)用(粗略估計)。
-
-
用戶 ID、組 ID 列表從哪里獲取?
-
在典型的 Unix 系統(tǒng)上,登錄程序以 root(UID 0)身份運行。
-
檢查提供的用戶密碼是否與
/etc/shadow
中的匹配。 -
根據(jù)
/etc/passwd
找到用戶的 UID。 -
根據(jù)
/etc/group
找到用戶的組。 -
在運行用戶的 shell 之前調(diào)用
setuid()
、setgid()
、setgroups()
。
-
-
在切換到非 root 用戶后如何重新獲得權(quán)限?
-
可以使用文件描述符傳遞(但必須編寫專門的代碼)
-
內(nèi)核機制:setuid/setgid 二進制文件。
-
當(dāng)執(zhí)行二進制文件時,將進程 UID 或 GID 設(shè)置為二進制文件所有者。
-
通過文件權(quán)限中的特殊位指定。
-
例如,
su
/sudo
二進制文件通常是 setuid root。 -
即使您的 shell 不是 root,也可以運行
"su otheruser"
。 -
su
進程會檢查密碼,如果正確則以otheruser
身份運行 shell。 -
Unix 上有許多這樣的程序,因為通常需要 root 權(quán)限。
-
-
為什么 setuid 二進制文件在安全方面可能是個壞主意?
-
對手(二進制調(diào)用者)操縱進程的許多方法。
-
在 Unix 中,執(zhí)行的進程會繼承環(huán)境變量、文件描述符等。
-
setuid 程序可能使用的庫不夠謹慎
-
歷史上存在許多漏洞(例如傳遞
$LD_PRELOAD
,…)
-
-
-
如何防止惡意程序利用 setuid-root 二進制文件?
-
內(nèi)核機制:chroot
-
通過路徑名打開文件時更改
/
的含義。 -
不能在 chroot 樹之外命名文件(例如 setuid 二進制文件)。
-
-
例如,OKWS 使用 chroot 將程序限制在
/var/okws/run
中,… -
內(nèi)核還確保
/../
不允許從 chroot 中逃脫。 -
為什么只允許 root 使用 chroot?
-
setuid 二進制文件(如
su
)可能會混淆/etc/passwd
的內(nèi)容。 -
許多內(nèi)核實現(xiàn)(無意中?)允許遞歸調(diào)用
chroot()
以從 chroot 監(jiān)獄中逃脫,因此 chroot 對于以 root 身份運行的進程來說不是一種有效的安全機制。
-
-
為什么 chroot 沒有被修復(fù)以限制根進程在該目錄中?
- Root 可以寫入內(nèi)核內(nèi)存,加載內(nèi)核模塊,訪問磁盤扇區(qū),…
-
背景:傳統(tǒng)的 Web 服務(wù)器架構(gòu)(Apache)
-
Apache 運行
N
個相同的進程,處理 HTTP 請求。 -
所有進程都以用戶
'www'
身份運行。 -
應(yīng)用程序代碼(例如 PHP)通常在每個
N
個 Apache 進程中運行。 -
任何對操作系統(tǒng)狀態(tài)(文件、進程等)的訪問都由
www
的 UID 執(zhí)行。 -
存儲:SQL 數(shù)據(jù)庫,通常一個連接具有對整個數(shù)據(jù)庫的完全訪問權(quán)限。
- 數(shù)據(jù)庫主體是整個應(yīng)用程序。
-
問題:如果任何組件被攻破,對手將獲得所有數(shù)據(jù)。
-
Web 應(yīng)用可能會發(fā)生哪種攻擊?
-
無意中的數(shù)據(jù)泄露(獲取頁面源代碼,隱藏文件,…)
-
遠程代碼執(zhí)行(例如,Apache 中的緩沖區(qū)溢出)
-
有 bug 的應(yīng)用程序代碼(難以編寫安全的 PHP 代碼),例如 SQL 注入
-
對 Web 瀏覽器的攻擊(跨站腳本攻擊)
-
回到 OKWS:他們的應(yīng)用/動機是什么?
-
約會網(wǎng)站:擔(dān)心數(shù)據(jù)保密性。
-
不太擔(dān)心對手闖入并發(fā)送垃圾郵件。
-
大量的服務(wù)器端代碼執(zhí)行:匹配,配置文件更新,…
-
必須在用戶之間共享(例如匹配)-- 不能只是分區(qū)。
-
對整體計劃的良好總結(jié):
- “最容易受攻擊的方面對攻擊者最無用”
為什么這么難?
-
Unix 使降低權(quán)限變得棘手(chroot,UID,…)
-
應(yīng)用程序需要以復(fù)雜的方式共享狀態(tài)。
-
Unix 和 SQL 數(shù)據(jù)庫沒有細粒度的共享控制機制。
OKWS 如何分割 Web 服務(wù)器?
-
論文中的圖 1。
-
這個 Web 服務(wù)器中的請求是如何流動的?
-
okd -> oklogd
-
-> pubd
-
-> svc -> dbproxy
-
-> oklogd
-
-
-
這種設(shè)計如何映射到物理機器?
-
可能有許多前端機器(
okld
,okd
,pubd
,oklogd
,svc
) -
幾臺 DB 機器(
dbproxy
,DB)
-
這些組件如何互動?
-
okld
為每個服務(wù)設(shè)置socketpair
s(雙向管道)。-
一個用于控制 RPC 請求的套接字對(例如,“獲取新的日志套接字對”)。
-
用于日志記錄的一個套接字對(
okld
首先通過 RPC 從oklogd
獲取它)。 -
對于 HTTP 服務(wù):一個用于轉(zhuǎn)發(fā) HTTP 連接的套接字對。
-
對于
okd
:HTTP 服務(wù)的套接字對的服務(wù)器端 FD(HTTP+RPC)。
-
-
okd
監(jiān)聽一個單獨的套接字以接收控制請求(repub,relaunch)。-
在圖 1 中似乎是端口 11277,但在 OKWS 代碼中是 Unix 域套接字。
-
對于repub,
okd
與pubd
通信以生成新模板,- 然后通過 RPC 控制通道將生成的模板發(fā)送給每個服務(wù)。
-
-
服務(wù)通過 TCP 與 DB 代理通信(通過端口號連接)。
OKWS 如何在圖 1 中的組件之間強制隔離?
-
每個服務(wù)作為單獨的 UID 和 GID 運行。
-
chroot 用于將每個進程限制在單獨的目錄中(幾乎)。
-
組件通過管道(或者說 Unix 域套接字對)進行通信。
-
用于傳遞 HTTP 連接的文件描述符傳遞。
-
okld
的目的是什么? -
為什么
okld
不同于okd
? -
為什么
okld
需要以 root 身份運行?(端口 80,chroot/setuid。) -
okld
啟動服務(wù)需要什么?-
創(chuàng)建套接字對
-
獲取新的
oklogd
套接字 -
fork
,setuid/setgid
,exec
服務(wù) -
將控制套接字傳遞給
okd
-
-
oklogd
的目的是什么? -
pubd
的目的是什么? -
為什么我們需要數(shù)據(jù)庫代理?
-
確保每個服務(wù)在受損時無法獲取其他數(shù)據(jù)。
-
DB 代理協(xié)議由應(yīng)用程序開發(fā)人員定義,取決于應(yīng)用程序的要求。
-
一個可能常見的代理類型是模板化的 SQL 查詢。
-
代理強制執(zhí)行整體查詢結(jié)構(gòu)(選擇、更新),
- 但允許客戶端填寫查詢參數(shù)。
-
-
20 字節(jié)令牌是從哪里來的?
- 作為服務(wù)的參數(shù)傳遞。
-
誰檢查令牌?
- DB 代理有令牌列表(和允許的查詢?)。
-
誰生成令牌?
- 不清楚;系統(tǒng)管理員手動?
-
令牌泄露會怎樣?
- 受損組件可能會發(fā)出查詢。
-
-
表 1:為什么所有服務(wù)和
okld
都在同一個 chroot 中?-
這是一個問題嗎?
-
我們?nèi)绾螞Q定?
- 那里有哪些可讀寫文件?
-
可讀性:包含服務(wù)代碼的共享庫。
-
可寫:每個服務(wù)都可以寫入自己的
/cores/<uid>
。 -
配置文件在哪里?
/etc/okws_config
,由okld
保存在內(nèi)存中。 -
oklogd
和pubd
有單獨的 chroots,因為它們具有重要狀態(tài):-
oklogd
的 chroot 包含日志文件,希望確保它沒有被修改。 -
pubd
的 chroot 包含模板,希望避免泄露它們(?)。
-
-
-
為什么 OKWS 需要為每個服務(wù)單獨的 GID?
-
需要執(zhí)行二進制文件,但文件所有權(quán)允許 chmod。
-
解決方案:二進制文件由 root 所有,服務(wù)是組所有者,模式 0410。
-
為什么是 0410(用戶讀取,組執(zhí)行),而不是 0510(用戶讀取和執(zhí)行)?
-
-
為什么不按用戶處理?
-
每個用戶是否嚴格更好?
-
用戶 X 服務(wù)?
-
對于 okcupid 來說,每個服務(wù)的隔離可能是有道理的。
- (即,也許他們需要在用戶之間進行大量共享?)
-
每個用戶隔離需要為每個用戶分配 UID,使
okld
變得復(fù)雜。- 并降低性能(盡管對于某些用例可能仍然可以接受)。
-
OKWS 是否實現(xiàn)了其目標?
-
OKWS 解決了典型 Web 攻擊列表中的哪些攻擊,以及如何解決?
-
除了 XSS 之外的大多數(shù)問題都已解決。
-
通過使用專門的模板例程,XSS 在某種程度上得到解決。
-
-
每個組件被損壞的影響是什么,以及“攻擊面”是什么?
-
okld
:對 Web 服務(wù)器機器的根訪問權(quán)限,但也許沒有對數(shù)據(jù)庫的訪問權(quán)限。- 攻擊面:很小(除了 svc 退出之外沒有用戶輸入)。
-
okd
:攔截/修改所有用戶 HTTP 請求/響應(yīng),竊取密碼。- 攻擊面:解析 HTTP 請求的第一行;控制請求。
-
pubd
:損壞模板,利用可能利用某些服務(wù)中的錯誤?- 攻擊面:從 okd 獲取模板的請求。
-
oklogd
:損壞/忽略/刪除/偽造日志條目。- 攻擊面:來自 okd、okld、svcs 的日志消息。
-
service
:向用戶發(fā)送垃圾,訪問 svc 的數(shù)據(jù)(模塊化 dbproxy)。- 攻擊面:來自用戶的 HTTP 請求(+來自 okd 的控制消息)。
-
dbproxy
:訪問/更改其所連接的數(shù)據(jù)庫中的所有用戶數(shù)據(jù)。-
攻擊面:來自授權(quán)服務(wù)的請求。
- 未經(jīng)授權(quán)服務(wù)的請求(易于丟棄)。
-
-
-
一旦單個服務(wù)被損壞,操作系統(tǒng)內(nèi)核就成為攻擊面的一部分。
- Linux 內(nèi)核漏洞很少見,但每年仍然會出現(xiàn)幾次。
-
OKWS 假設(shè)開發(fā)人員在設(shè)計層面做正確的事情(也許在實現(xiàn)層面不是):
-
將 Web 應(yīng)用程序拆分為單獨的服務(wù)(而不是全部放在一個服務(wù)中)。
-
為 DB 代理定義精確的協(xié)議(否則任何服務(wù)都可以獲取任何數(shù)據(jù))。
-
-
性能?
-
似乎比大多數(shù)替代方案更好。
-
在負載下性能更好(因此在一定程度上抵抗 DoS 攻擊)
-
-
OKWS 與 Apache 相比如何?
-
總體而言,更好的設(shè)計。
-
okld
以 root 身份運行,與 Apache 中沒有任何東西相比,但可能不重要。 -
兩者都沒有很好的解決客戶端漏洞(XSS 等)
-
-
對手如何試圖破壞類似 OKWS 系統(tǒng)?
-
利用 C++代碼中的緩沖區(qū)溢出或其他漏洞。
-
在某個
dbproxy
中找到 SQL 注入攻擊。 -
在服務(wù)代碼中找到邏輯錯誤。
-
發(fā)現(xiàn)跨站腳本漏洞。
-
OKWS 有多成功?
-
論文中描述的問題仍然相當(dāng)普遍。
-
okcupid.com 仍在運行 OKWS,但似乎沒有被其他網(wǎng)站使用。
-
C++可能不是編寫 Web 應(yīng)用程序的好選擇。
-
對于許多 Web 應(yīng)用程序,獲得 C++性能可能并不關(guān)鍵。
-
設(shè)計應(yīng)該適用于其他語言(Python 等)。
-
實際上,6.858 實驗室中的
zookws
受 OKWS 啟發(fā),運行 Python 代碼。
-
-
對于典型的 Web 應(yīng)用程序,DB 代理的想法并沒有起飛。
-
但是 DB 代理對于限制服務(wù)可以訪問的數(shù)據(jù)至關(guān)重要。
-
為什么?
- 需要開發(fā)人員定義這些 API:額外的工作,會妨礙。
-
很難提前精確定義允許的 DB 查詢。
- (盡管如果很困難,可能是安全策略模糊的標志。)
-
-
Apache 的特權(quán)分離工作(盡管仍然難以使用)。
-
Unix 使非根用戶難以操作用戶 ID。
-
性能是一個問題(為每個請求運行一個單獨的進程)。
-
-
scripts.mit.edu
有類似的設(shè)計,以不同的 UID 運行腳本。-
主要擔(dān)心將用戶相互隔離。
-
偏執(zhí)的 Web 應(yīng)用程序開發(fā)人員可以為每個組件創(chuàng)建單獨的鎖。
-
-
敏感系統(tǒng)在更粗粒度上進行分區(qū)。
-
信用卡處理公司將信用卡數(shù)據(jù)與其他所有數(shù)據(jù)分開。
-
使用虛擬機或物理機器隔離來分割應(yīng)用程序、數(shù)據(jù)庫等。
-
你如何將現(xiàn)代 Web 應(yīng)用程序框架與 OKWS 集成?
-
需要幫助 okd 找出如何將請求路由到服務(wù)。
-
需要實現(xiàn) DB 代理,或其變體,以保護數(shù)據(jù)。
-
取決于應(yīng)用代碼對靜態(tài)分析的適應(yīng)性。
-
或者需要要求程序員為服務(wù)注釋可以運行的查詢。
-
-
需要確保應(yīng)用代碼可以在單獨的進程中運行(可能沒問題)。
參考資料
-
css.csail.mit.edu/6.858/2014/readings/setuid.pdf
-
httpd.apache.org/docs/trunk/suexec.html
能力和其他保護機制
注意: 這些講座筆記是從 2014 年 6.858 課程網(wǎng)站 上發(fā)布的筆記稍作修改而來。
混淆的副手問題
"混淆的副手"的作者遇到了什么問題?
-
他們的系統(tǒng)有一個 Fortran 編譯器,
/sysx/fort
(Unix 文件名語法) -
他們希望 Fortran 編譯器記錄使用統(tǒng)計信息,但在哪里?
-
創(chuàng)建了一個特殊的統(tǒng)計文件,
/sysx/stat
。 -
給了
/sysx/fort
“家庭文件許可證”(類似于關(guān)于/sysx 的 setuid)
-
-
出了什么問題?
-
用戶可以調(diào)用編譯器,要求將輸出寫入
/sysx/stat
。- 例如
/sysx/fort
/my/code.f -o/sysx/stat
- 例如
-
編譯器打開提供的路徑名,并成功,因為它的許可證。
-
用戶本身不能寫入那個
/sysx/stat
文件。
-
-
為什么
/sysx/fort
只是編譯器中的一個錯誤?-
原則上,可以通過在各個地方添加檢查來解決這個問題。
-
問題:需要在幾乎所有打開文件的地方添加檢查。
-
完全正確的代碼一旦成為 setuid 二進制文件的一部分就會變得有 bug。
-
-
那么什么是“混淆的副手”?
-
編譯器代表兩個主體運行:
-
用戶主體(用于打開用戶的文件)
-
編譯器主體(用于打開編譯器的文件)
-
-
不清楚在任何給定時間應(yīng)該使用主體權(quán)限。
-
我們能在 Unix 中解決這個混淆的副手問題嗎?
-
假設(shè) gcc 想要在
/etc/gcc.stats
中保留統(tǒng)計信息 -
可以有一個特殊的 setuid 程序,只能寫入該文件
- 不太方便:不能像打開其他文件那樣簡單地打開文件。
-
如果我們讓 gcc 成為某個非根用戶(統(tǒng)計文件所有者)的 setuid,會怎樣?
- 難以訪問用戶的原始文件。
-
如果 gcc 是 setuid-root?(壞主意,但讓我們弄清楚為什么…)
-
大量潛在的緩沖區(qū)溢出可能導(dǎo)致 root 訪問權(quán)限。
-
需要在 gcc 可能打開文件的每個地方進行檢測。
-
-
當(dāng) gcc 打開文件時,我們應(yīng)該執(zhí)行什么檢查?
-
如果是“內(nèi)部”文件(例如
/etc/gcc.stats
),也許不需要檢查。 -
如果是用戶提供的文件,需要確保用戶可以訪問它。
-
可以查看相關(guān)文件的權(quán)限。
-
還需要檢查導(dǎo)致該文件的目錄的權(quán)限。
-
-
潛在問題:競爭條件。
-
如果文件在我們檢查和使用之間發(fā)生更改會怎么樣?
-
常見的漏洞:攻擊者用符號鏈接替換合法文件
-
符號鏈接可能指向,比如
/etc/gcc.stats
,或/etc/passwd
,或… -
被稱為“檢查時間到使用時間”的錯誤(TOCTTOU)。
-
對這個問題有幾種可能的思考方式:
-
環(huán)境權(quán)限: 進程自動使用的權(quán)限是問題所在。任何權(quán)限都不應(yīng)該自動使用。對象的名稱也應(yīng)該是訪問它的權(quán)限。
-
復(fù)雜的權(quán)限檢查: 特權(quán)應(yīng)用程序難以復(fù)制。通過簡化的檢查,特權(quán)應(yīng)用程序可能能夠正確檢查另一個用戶是否應(yīng)該訪問某個對象。
什么是環(huán)境權(quán)限的例子?
-
Unix 用戶 ID,組 ID。
-
防火墻(IP 地址與訪問權(quán)限)
-
HTTP cookies(例如,訪問 http://gmail.com 這樣的 URL)
通過能力給對象命名有什么幫助?
-
傳遞文件描述符而不是傳遞文件名。
-
除非調(diào)用者被授權(quán)打開該文件,否則無法傳遞有效的 FD。
我們能否使用文件描述符解決通過 setuid gcc 設(shè)置的問題?
-
類似:可以使編譯器僅通過 FD 傳遞接受文件。
-
或者,可以創(chuàng)建一個 setuid 輔助程序,打開
/etc/gcc.stats
文件,將一個打開的文件描述符傳遞回我們的編譯器進程。 -
然后,可以繼續(xù)像處理任何其他文件一樣使用這個打開的文件。
-
如何確保只有 gcc 可以運行這個輔助程序?
-
使 gcc 設(shè)置為某個特殊組的 setgid。
-
使輔助程序僅對該特殊組可執(zhí)行。
-
確保該組沒有其他授予的特權(quán)。
-
Capsicum 作者試圖通過能力解決什么問題?
-
在各種應(yīng)用程序中降低不可信代碼的特權(quán)。
-
總體計劃:
-
將應(yīng)用程序分解為較小的組件。
-
減少最容易受攻擊的組件的特權(quán)。
-
仔細設(shè)計接口,以便一個組件無法危害另一個組件。
-
-
為什么這么困難?
-
在傳統(tǒng)的 Unix 系統(tǒng)中難以降低代碼的特權(quán)(“沙盒”)。
-
難以為沙盒化代碼提供有限的訪問權(quán)限(對文件、網(wǎng)絡(luò)等)。
-
什么樣的應(yīng)用程序可能會使用沙盒化?
OKWS
-
處理網(wǎng)絡(luò)輸入的程序:
- 將輸入處理代碼放入沙盒中。
-
復(fù)雜操作數(shù)據(jù)的程序:(gzip,Chromium,媒體編解碼器,瀏覽器插件,…)
- 將復(fù)雜(且可能有錯誤)的部分放入沙盒中。
-
從互聯(lián)網(wǎng)下載的任意程序怎么樣?
-
稍微不同的問題:需要隔離未修改的應(yīng)用程序代碼。
-
一個選擇:程序員編寫他們的應(yīng)用程序以在沙盒中運行。
-
在某些情況下有效:Javascript,Java,Native Client,…
-
需要在沙盒代碼上制定一個環(huán)境標準。
-
-
另一個選擇:對現(xiàn)有代碼施加新的安全策略。
-
可能需要保留程序員正在使用的所有 API。
-
需要對現(xiàn)有 API 施加檢查,在那種情況下。
-
不清楚訪問文件、網(wǎng)絡(luò)等的策略應(yīng)該是什么。
-
-
-
希望避免被欺騙誤用特權(quán)的應(yīng)用程序?
-
假設(shè)兩個 Unix 用戶,Alice 和 Bob,正在某個項目上工作。
-
兩者都在某個組
G
中,并且項目dir
允許該組訪問。 -
假設(shè) Alice 從項目目錄向某人發(fā)送一個文件。
-
風(fēng)險:Bob 可能用符號鏈接替換文件為 Alice 的私人文件。
-
Alice 的進程將隱式使用 Alice 的環(huán)境特權(quán)來打開。
-
可以將這看作對單個文件操作進行沙盒化。
-
有哪些沙盒化計劃(機制)存在(優(yōu)勢,限制)?
-
操作系統(tǒng)通常提供某種安全機制(“原語”)。
- 例如,在 Unix 中的用戶/組 ID,正如我們在上一堂課中看到的。
-
今天,我們將研究操作系統(tǒng)級別的安全原語/機制。
-
當(dāng)您關(guān)心保護操作系統(tǒng)管理的資源時通常是一個很好的選擇。
-
例如,文件,進程,粗粒度內(nèi)存,網(wǎng)絡(luò)接口等。
-
-
許多操作系統(tǒng)級別的沙箱機制在進程級別工作。
-
適用于可以作為一個單元進行隔離的整個進程。
-
可能需要重新設(shè)計應(yīng)用程序以創(chuàng)建用于隔離的進程。
-
-
其他技術(shù)可以提供更細粒度的隔離(例如,在 proc 中的線程)。
-
語言級別的隔離(例如,Javascript)。
-
二進制儀器化(例如,Native Client)。
-
為什么我們需要這些其他的沙箱技術(shù)?
-
更容易控制對非操作系統(tǒng)/更細粒度對象的訪問。
-
或者也許可以以與操作系統(tǒng)無關(guān)的方式進行沙箱化。操作系統(tǒng)級別的隔離通常與更細粒度的隔離結(jié)合使用。
-
更細粒度的隔離通常很難做到正確(Javascript,NaCl)。例如,Native Client 同時使用了細粒度沙箱和操作系統(tǒng)級別的沙箱。
-
-
將在后續(xù)講座中更詳細地討論這些問題。
-
計劃 0:虛擬化所有內(nèi)容(例如,VMs)。
-
在虛擬化環(huán)境中運行不可信代碼。
-
許多示例:x86 qemu,FreeBSD jails,Linux LXC,…
-
幾乎是一種不同類別的機制:嚴格隔離。
-
優(yōu)勢:VM 內(nèi)部的沙箱代碼幾乎與外部沒有交互。
-
優(yōu)勢:可以沙箱未經(jīng)修改的代碼,不期望被隔離。
-
優(yōu)勢:一些 VM 可以由任意用戶啟動(例如,qemu)。
-
優(yōu)勢:通常與其他隔離技術(shù)可組合,提供額外層次。
-
缺點:難以允許一些共享:沒有共享進程,管道,文件。
-
缺點:虛擬化所有內(nèi)容通常會使 VM 相對較重。
- 每個沙箱都會帶來非常重要的 CPU/內(nèi)存開銷。
計劃 1:自主訪問控制(DAC)。
-
每個對象都有一組權(quán)限(訪問控制列表)。
-
例如,Unix 文件,Windows 對象。
-
“自主”意味著應(yīng)用程序在對象上設(shè)置權(quán)限(例如,
chmod
)。
-
-
每個程序都以某些主體的權(quán)限運行。
- 例如,Unix 用戶/組 ID,Windows SIDs。
-
當(dāng)程序訪問對象時,檢查程序的權(quán)限以決定。
“環(huán)境特權(quán)”:每次訪問都隱式使用的權(quán)限。
Name Process privileges| |V VObject -> Permissions -> Allow?
-
如何在 DAC 系統(tǒng)上(例如,Unix)沙箱化程序?
-
必須分配一個新的主體(用戶 ID):
- 否則,現(xiàn)有主體的權(quán)限將被隱式使用!
-
防止進程讀取/寫入其他文件:
-
在整個文件系統(tǒng)上更改權(quán)限?繁瑣,不切實際,需要 root 權(quán)限。
-
即使如此,新程序也可以創(chuàng)建重要的可全球?qū)懭胛募?/p>
-
替代方案:
chroot
(同樣,必須是 root)。
-
-
允許進程讀/寫某個文件:
-
如果可能的話,適當(dāng)設(shè)置文件的權(quán)限。
-
將文件鏈接/移動到沙箱的
chroot
目錄中?
-
-
防止進程訪問網(wǎng)絡(luò):
-
Unix 中沒有真正的答案。
-
可能配置防火墻?但不是真正針對進程的。
-
-
允許進程訪問特定的網(wǎng)絡(luò)連接:
- 如上所述,在 Unix 中沒有很好的計劃。
-
控制沙盒可以殺死 / 調(diào)試 / 等的進程:
-
可以在相同的 UID 下運行,但可能特權(quán)太多。
-
該 UID 也可能具有其他特權(quán)…
-
-
-
**問題:**在大多數(shù) DAC 系統(tǒng)上,只有 root 可以創(chuàng)建新的主體。
- 例如,Unix,Windows。
-
**問題:**一些對象可能沒有明確可配置的訪問控制列表。
- Unix:進程,網(wǎng)絡(luò),…
-
**問題:**文件上的權(quán)限可能與沙盒所需的策略不匹配。
- 可以通過使用
chroot
對文件進行某種程度的解決,但很麻煩。
- 可以通過使用
-
**相關(guān)問題:**使用子特權(quán)執(zhí)行某些操作。
-
回想一下 Alice 通過電子郵件將文件發(fā)送到共享組目錄的示例。
- “混淆副手問題”:程序是多個主體的“副手”。
-
*一個解決方案:*檢查組權(quán)限是否允許訪問(手動,容易出錯)。
-
*替代方案:*明確為每個操作指定特權(quán)。
-
權(quán)限可以幫助:能力(例如,fd)結(jié)合了對象 + 特權(quán)。
-
一些 Unix 功能與純能力設(shè)計不兼容(按名稱創(chuàng)建符號鏈接)。
-
-
計劃 2:強制訪問控制(MAC)。
-
在 DAC 中,安全策略由應(yīng)用程序自身設(shè)置(chmod 等)。
-
MAC 試圖幫助用戶/管理員為應(yīng)用程序指定策略。
-
*“強制”*意味著應(yīng)用程序無法更改此策略。
-
傳統(tǒng)的 MAC 系統(tǒng)試圖強制執(zhí)行軍事機密級別。
-
*示例:*確保絕密程序無法泄露機密信息。
Name Operation + caller process| |V VObject --------> Allow?^|Policy -----------+
-
*注意:*許多系統(tǒng)在其中具有 DAC + MAC 的方面。
-
例如,Unix 用戶 ID 是“DAC”,但可以爭論防火墻是“MAC”。
-
并不重要–了解設(shè)計空間中的極端點是很好的。
-
-
Windows 強制完整性控制(MIC)/ FreeBSD 中的 LOMAC。
-
為每個進程跟蹤“完整性級別”。
-
文件與其關(guān)聯(lián)的最低完整性級別。
-
進程無法寫入高于其完整性級別的文件。
- Windows Vista 中的 Internet Explorer 以低完整性運行,無法覆蓋系統(tǒng)文件。
-
FreeBSD LOMAC 還跟蹤進程讀取的數(shù)據(jù)。
-
(類似于許多基于信息流的系統(tǒng)。)
-
當(dāng)進程讀取低完整性數(shù)據(jù)時,它也變得低完整性。
-
傳遞性,防止對手間接篡改文件。
-
-
對于沙盒化不是立即有用:只有固定數(shù)量的級別。
-
-
SElinux
-
*想法:*系統(tǒng)管理員指定系統(tǒng)范圍的安全策略。
-
策略文件指定是否應(yīng)允許或拒絕每個操作。
-
為了幫助決定是否允許/拒絕,文件標記為“類型”。
- (另一個整數(shù)值,與 inode 中的 uid、gid 等一起存儲。)
-
-
Mac OS X 沙盒(“Seatbelt”)和 Linux
seccomp_filter
。-
應(yīng)用程序為是否允許/拒絕每個系統(tǒng)調(diào)用指定策略。
- (在 MacOSX 的機制中用 LISP 編寫,或者在 Linux 中用 BPF 編寫。)
-
根據(jù)參數(shù)確定系統(tǒng)調(diào)用的安全影響可能很困難。
-
路徑名指的是什么?符號鏈接,硬鏈接,競爭條件,…
-
(盡管 MacOSX 的沙盒提供了更多信息)
-
-
優(yōu)勢: 任何用戶都可以對任意代碼片段進行沙盒化!
-
限制: 程序員必須分別編寫策略和應(yīng)用代碼。
-
限制: 有些操作只能以粗粒度進行過濾。
- 例如,在 MacOSX 的過濾語言中的 POSIX
shm
,根據(jù) Capsicum 論文。
- 例如,在 MacOSX 的過濾語言中的 POSIX
-
限制:策略語言可能使用起來很尷尬,無狀態(tài)等。
- 例如,如果應(yīng)用程序應(yīng)該與某個服務(wù)器建立精確的一個連接?
-
注意:
seccomp_filter
與常規(guī)/舊版seccomp
有很大不同,而 Capsicum 論文討論的是常規(guī)/舊版seccomp
。
-
-
將策略與應(yīng)用代碼分離是否是個好主意?
-
取決于總體目標。
-
如果用戶/管理員想查看或更改策略,可能會很有用。
-
如果應(yīng)用程序開發(fā)人員需要同時維護代碼和策略,這將成為問題。
-
對應(yīng)用程序開發(fā)人員來說,可能有助于澄清策略。
-
較少集中的“MAC”系統(tǒng)(Seatbelt、
seccomp
)提供了一種折衷方案。
-
-
待辦事項: 還要看看《賽里斯墻安全策略》
計劃 3:能力(Capsicum)。
-
不同的訪問控制計劃:能力。
-
如果進程有某個對象的句柄(“能力”),就可以訪問它。
能力 --> 對象
-
沒有特權(quán)、訪問控制列表、策略等的單獨問題。
-
例如:Unix 上的文件描述符是文件的能力。
-
程序無法制造未經(jīng)合法獲取的文件描述符。
- 為什么不? 操作系統(tǒng)創(chuàng)建和管理文件描述符。應(yīng)用程序無法偽造文件描述符。它必須通過漏洞寫入操作系統(tǒng)內(nèi)存。
-
一旦文件打開,就可以訪問它;檢查發(fā)生在打開時。
-
可以將打開的文件傳遞給其他進程。
- 文件描述符也有助于解決“檢查時間與使用時間”(TOCTTOU)漏洞。
-
-
能力通常是短暫的:不是磁盤上的 inode 的一部分。
- 啟動程序的任何內(nèi)容都需要每次重新創(chuàng)建能力。
-
-
全局命名空間
-
為什么這些人如此著迷于消除全局命名空間?
-
全局命名空間需要一些訪問控制策略(例如,環(huán)境權(quán)限)。
-
難以控制沙盒對全局命名空間中對象的訪問。
-
-
內(nèi)核更改
-
只是為了再次確認:為什么我們需要內(nèi)核更改?
-
我們能否將所有內(nèi)容都實現(xiàn)在一個庫中(并通過 LD_PRELOAD 加載)?
-
需要操作系統(tǒng)在進入能力模式后拒絕應(yīng)用程序訪問全局命名空間
-
-
將更多內(nèi)容表示為文件描述符:進程(pdfork)。
- 一般來說是個好主意。
-
能力模式: 一旦進程進入 cap 模式,就無法離開(包括所有子進程)。
-
在能力模式下,只能使用文件描述符 – 沒有全局命名空間。
-
不能通過完整路徑名打開文件:不需要像 OKWS 中的
chroot
。 -
仍然可以通過相對路徑名打開文件,給定目錄的 fd(
openat
)。
-
-
不能在路徑名或符號鏈接中使用“…”:為什么?
-
原則上,“…” 可能沒問題,只要“…” 不走得太遠。
-
難以正確執(zhí)行。
-
假設(shè)設(shè)計:
-
禁止在根能力中查找“…”。
-
路徑名中的非“…”組件不得比“…”多,忽略“.”。
-
假設(shè)一個進程對
/foo
擁有能力C1
。 -
在單個進程中的競爭條件,有 2 個線程:
-
-
-
競爭條件示例:
T1: mkdir(C1, "a/b/c")T1: C2 = openat(C1, "a")T1: C3 = openat(C2, "b/c/../..") # should return a cap for /foo/aLet openat() run until it's about to look up the first ".."T2: renameat(C1, "a/b/c", C1, "d")T1: Look up the first "..", which goes to "/foo"Look up the second "..", which goes to "/"
-
…
-
Unix 權(quán)限仍然適用嗎?
-
是的 – 僅因為你對目錄有一個 cap,就不能訪問目錄中的所有文件。
-
但意圖是沙盒不應(yīng)依賴 Unix 權(quán)限。
-
-
對于文件描述符,添加一個存儲允許操作的包裝對象。
-
內(nèi)核在哪里檢查能力?
-
內(nèi)核中的一個函數(shù)查找 fd 號碼 – 修改它以檢查能力。
-
還修改了查找路徑名的
namei
函數(shù)。 -
良好實踐: 尋找窄接口,否則容易忽略檢查。
-
-
-
libcapsicum
-
應(yīng)用程序開發(fā)人員為什么需要這個庫?
-
最大的功能:在沙盒中啟動新進程。
-
-
fd 列表
-
主要是將大量文件描述符傳遞給子進程的便捷方式。
-
通過字符串命名文件描述符,而不是硬編碼的 fd 號碼。
-
-
cap_enter()
vslch_start()
-
使用
exec
而不是cap_enter
進行沙盒化的優(yōu)勢是什么? -
內(nèi)存中的殘留數(shù)據(jù):例如 OpenSSL/OpenSSH 中的私鑰。
-
應(yīng)用程序忘記關(guān)閉的殘留文件描述符。
-
論文中的圖 7:
tcpdump
在stdin
、stdout
、stderr
上具有特權(quán)。 -
論文中的圖 10:
dhclient
具有原始套接字,syslogd
管道,租約文件。
-
-
優(yōu)點: 任何進程都可以創(chuàng)建一個新的沙盒。
- (即使沙盒也可以創(chuàng)建沙盒。)
-
優(yōu)點: 對資源訪問的細粒度控制(如果它們映射到 FD)。
- 文件、網(wǎng)絡(luò)套接字、進程。
-
缺點: 對持久文件訪問跟蹤的故事較弱。
-
缺點: 禁止全局命名空間,需要以不同方式編寫代碼。
替代性的能力設(shè)計:純能力為基礎(chǔ)的操作系統(tǒng)(KeyKOS 等)。
-
內(nèi)核只提供消息傳遞服務(wù)。
-
消息傳遞通道(非常類似文件描述符)是能力。
-
每個應(yīng)用程序都必須以能力樣式編寫。
-
Capsicum 聲稱更加務(wù)實:一些應(yīng)用程序無需更改。
Linux 能力:解決不同的問題。
-
嘗試將根的特權(quán)劃分為更細粒度的特權(quán)。
-
由各種能力表示:
CAP_KILL, CAP_SETUID
,CAP_SYS_CHROOT
, … -
進程可以以特定能力運行,而不是以 root 的所有特權(quán)。
-
參考:capabilities(7)
在應(yīng)用程序中使用 Capsicum
-
計劃: 確保沙盒化進程不使用路徑名或其他全局 NS。
-
對于可能需要訪問的每個目錄,提前打開 FD。
-
要打開文件,請使用從這些目錄 FD 開始的
openat()
。- … 打開大量文件的程序可能會很麻煩。
-
-
tcpdump
-
2 行版本:在打開所有 FD 后只需
cap_enter()
。 -
使用
procstat
查看生成的能力。 -
8 行版本:還限制
stdin
/stdout
/stderr
。 -
為什么?避免讀取
stderr
日志,更改終端設(shè)置,…
-
-
dhclient
- 已經(jīng)進行了特權(quán)分離,使用 Capsicum 來加強沙盒(2 行)。
-
gzip
-
分叉/執(zhí)行沙盒化的子進程,通過管道使用 RPC 向其提供數(shù)據(jù)。
-
非平凡的更改,主要是為了為 RPC 編組/解組數(shù)據(jù):409 行代碼。
-
*有趣的錯誤:*一開始忘記傳播壓縮級別。
-
-
Chromium
-
在其他平臺上已經(jīng)進行了特權(quán)分離(但在 FreeBSD 上沒有)。
-
~100 行代碼用于為沙盒化進程包裝文件描述符。
-
-
OKWS
- 家庭作業(yè)問題有哪些不同的答案?
Capsicum 是否實現(xiàn)了其目標?
-
使用起來有多難/容易?
-
在應(yīng)用程序中使用 Capsicum 幾乎總是需要應(yīng)用程序更改。
-
(許多應(yīng)用程序傾向于通過路徑名打開文件等。)
-
一個例外:Unix 管道應(yīng)用程序(過濾器)只操作 FD。
-
-
對通過 FD 處理數(shù)據(jù)的流式應(yīng)用程序更容易。
-
其他隔離需要類似的更改(例如,
dhclient
,Chromium)。 -
對于現(xiàn)有應(yīng)用程序,延遲初始化似乎是一個問題。
- 沒有通用解決方案——要么更改代碼,要么早期初始化。
-
建議的計劃:沙盒化并查看哪些地方出問題。
- 可能會有微妙之處:
gzip
壓縮級別錯誤。
- 可能會有微妙之處:
-
-
它提供了哪些安全保證?
-
提供給應(yīng)用程序開發(fā)人員的保證:沙盒只能在打開的 FD 上操作。
-
結(jié)果取決于應(yīng)用程序開發(fā)人員如何劃分應(yīng)用程序、FD。
-
用戶/管理員無法從 Capsicum 獲得任何直接保證。
-
保證假設(shè) FreeBSD 內(nèi)核沒有錯誤(大量代碼),并且 Capsicum 開發(fā)人員捕獲了所有通過 FD 而非資源訪問的方式。
-
-
性能開銷是多少?(CPU,內(nèi)存)
-
訪問文件描述符的輕微開銷。
-
使用
fork
/exec
設(shè)置沙盒需要花費O(1msec)
,非平凡的。 -
特權(quán)分離可能需要 RPC / 消息傳遞,可能會引起注意。
-
-
采用情況?
-
在 FreeBSD 的內(nèi)核中,現(xiàn)在默認啟用(從 FreeBSD 10 開始)。
-
少數(shù)應(yīng)用程序已經(jīng)修改為使用 Capsicum。
dhclient
,tcpdump
,自論文撰寫以來還有幾個。參考 -
Casper 守護程序幫助應(yīng)用程序執(zhí)行非能力操作。例如,DNS 查找,查找
/etc/passwd
中的條目等。參考 -
Capsicum 已經(jīng)移植到 Linux(但不在上游內(nèi)核存儲庫中)。
-
有哪些應(yīng)用程序不適合 Capsicum?
-
需要控制對非內(nèi)核管理對象的訪問的應(yīng)用程序。
-
例如:X 服務(wù)器狀態(tài),DBus,在 Web 瀏覽器中的 HTTP 來源等。
-
例如:需要確保 DB 文件格式正確的數(shù)據(jù)庫服務(wù)器。
-
Capsicum 將管道視為用戶級服務(wù)器(例如,X 服務(wù)器)的一個能力。
-
-
需要從沙盒連接到特定的 TCP/UDP 地址/端口的應(yīng)用程序。
-
Capsicum 通過僅允許對現(xiàn)有打開的 FD 進行操作來工作。
-
需要其他機制來控制可以打開哪些 FD。
-
可能的解決方案:輔助程序可以在能力模式之外運行,根據(jù)策略為沙盒化程序打開 TCP/UDP 套接字。
-
參考
-
蘋果沙盒指南
-
seccomp_filter
-
強制完整性控制
-
賽里斯防火墻安全政策