題:
為什麼用C和C ++編寫的程序如此頻繁地容易受到溢出攻擊?
Nzall
2016-02-23 20:37:56 UTC
view on stackexchange narkive permalink

當我查看過去幾年與實現相關的漏洞時,我發現很多漏洞來自C或C ++,其中很多都是溢出攻擊。

  • Heartbleed是OpenSSL中的緩衝區溢出;
  • 最近,在glibc中發現了一個錯誤,該錯誤允許DNS解析期間出現緩衝區溢出;

這就是我能想到的現在關閉,但我懷疑這些是A)僅用於用C或C ++編寫的軟件,而B)僅基於緩衝區溢出。

特別是關於glibc錯誤,我讀到了評論指出,如果這是在JavaScript中而不是在C中發生,則不會有問題。即使只是將代碼編譯到Javascript,也不是問題。

為什麼C和C ++如此容易受到溢出攻擊?

擁有權利的同時也被賦予了重大的責任
[此答案](http://security.stackexchange.com/questions/95245/security-implications-of-neglecting-the-extra-byte-for-null-termination-in-cc/95248#95248)和[this答案](http://security.stackexchange.com/questions/82750/why-are-buffer-overflows-exected-in-the-direction-they-are/82846#82846)可能很有趣。基本上,它取決於語言的設計以及其實現的級別。
@RoraZ有一些工具可以將C編譯為javascript,例如emscripten。 http://dankaminsky.com/2016/02/20/skeleton/,我指的是底部附近。
您的問題有點像“為什麼只有Windows計算機會感染Windows病毒?”。因為Windows病毒僅在Windows計算機上才可能。 C和C ++可以通過執行未經檢查的指針算術來獲得緩衝區溢出漏洞。其他大多數語言都沒有此功能,因此不會有緩衝區溢出。您的問題還沒有考慮這些語言的流行性。 (也許其他語言的問題更多,但使用不多,因此總漏洞較少)。
評論不作進一步討論;此對話已[轉移為聊天](http://chat.stackexchange.com/rooms/36311/discussion-on-question-by-nate-kerkhofs-why-are-programs-write-in-c-and- c-so)。
在C ++中,導致緩衝區溢出的原因之一是在現代C ++中失敗,而忽略了STL等更安全的概念。如果您像C一樣使用C ++,您將得到應有的回報。
在此之前,有一個絕妙的評論似乎已被刪除:“這是因為手術刀比安全剪刀剪得更多”
C / C ++也最有可能用於風險最大,遭受攻擊最多的軟件。
對我來說,令我流血的是兩個錯誤的編程習慣的結果:1.使用goto語句和2.缺少諸如“禁止使用不帶花括號的if語句”之類的編程標準。這樣,它就不會發生。在缺少單元測試之後,可能也是另一個原因……我同意C和C ++更容易受到這種攻擊,因為它們是相當低級的語言,並且防止不良用法通常是開發人員的責任。
八 答案:
Thomas Pornin
2016-02-23 20:48:25 UTC
view on stackexchange narkive permalink
與大多數其他語言相反,

C和C ++傳統上不檢查溢出。如果源代碼說要在85字節的緩衝區中放入120字節,CPU會很樂意這樣做。這與以下事實有關:儘管C和C ++具有 array 的概念,但該概念僅是編譯時的。在執行時,只有指針,因此沒有運行時方法來檢查關於數組概念長度的數組訪問。

相反,大多數其他語言都具有數組概念,即在運行時保留下來,因此運行時系統可以系統地檢查所有數組訪問。這並不能消除溢出:如果源代碼在將120個字節寫入長度為85的數組中時要求無意義的內容,則仍然沒有意義。但是,這會自動觸發內部錯誤條件(通常是“異常”,例如Java中的 ArrayIndexOutOfBoundException ),該條件會中斷正常執行並且不讓代碼繼續執行。這會中斷執行,並通常意味著停止整個處理(線程死亡),但通常可以防止超出簡單的拒絕服務的利用。

基本上,緩衝區溢出利用需要代碼來完成溢出(讀取或寫入越過訪問緩衝區的邊界),以繼續執行超出該溢出範圍的操作。與C和C ++(以及其他一些語言,例如Forth或Assembly)相反,大多數現代語言都不允許真正的溢出發生,而要冒犯者。從安全角度來看,這要好得多。

*“從安全的角度來看,這要好得多。” *雖然確實如此,但它也使某些類型的編程(尤其是操作系統編程)變得更加困難。請記住,C的傳統可以追溯到一種旨在以可移植方式實現Unix的編程語言。出於充分的原因,C有時被稱為“便攜式彙編器”。
評論不作進一步討論;此對話已[移至聊天](http://chat.stackexchange.com/rooms/36312/discussion-on-answer-by-thomas-pornin-why-are-programs-write-in-c-and- c-so-f)。
-1
@Luaan我懷疑主要區別是從“彙編”到“託管代碼”。如果有的話,似乎更有可能是由於從非JIT代碼轉換為JIT代碼。通過優化編譯時間,您必須選擇一個願意支持的最低基準。使用JITed代碼,您可以針對正在運行的特定計算機進行優化。原則上,您可能可以用C編寫的JIT代碼。不過,我不確定是否有人嘗試過...
-1
DevSolar
2016-02-23 22:12:13 UTC
view on stackexchange narkive permalink

請注意,涉及一些循環推理:安全性問題通常與C和C ++關聯。但是,其中有多少是由於這些語言的固有弱點,又有多少是由於這些語言只是大多數計算機基礎結構的書面語言?


C旨在“比彙編程序高一級”。除了您自己實現的功能之外,沒有其他限制可用於擠出系統的最後一個時鐘週期。

C ++確實提供了對C的各種改進,與安全性最相關的是其容器類(例如 <vector> 和 <string> ),並且自C ++ 11起,智能指針使您無需手動處理內存即可處理數據。但是,由於它是C的進化而不是全新的語言,因此它 仍然提供C的手動內存管理機制,因此如果您堅持使用C


那為什麼為什麼仍用這些語言編寫SSL,綁定或OS內核之類的東西呢?

因為這些語言可以直接修改內存,這使得它們特別適合某種類型的高性能,低級應用程序(例如加密,DNS表查找,硬件驅動程序...或Java VM) ;-))。

因此,如果違反了與安全性相關的軟件,則用C或C ++編寫的軟件的機會很高,這僅僅是因為大多數與安全性相關的軟件 是用C或C ++編寫的,通常出於歷史和/或性能方面的原因。如果是用C / C ++編寫的,則主要的攻擊媒介是緩衝區溢出。

是不同的語言,這將是不同的攻擊vecto r,但我確信也會存在安全漏洞。


開發C / C ++軟件比開發Java軟件更容易。利用Windows系統比利用Linux系統更容易。利用前者無處不在,眾所周知(例如,眾所周知的攻擊媒介,如何查找和利用它們),以及許多人正在尋找獎勵/努力比率高的漏洞。

這並不意味著後者本身就很安全(saf er ,也許,但不是 safe )。這意味著-作為較難獲得較低收益的目標-壞男孩還沒有浪費太多時間。

gnasher729
2016-02-23 21:46:35 UTC
view on stackexchange narkive permalink

實際上,“令人不快”並不是真的 緩衝區溢出。為了使事情“更有效率”,他們將許多較小的緩衝區放入一個大緩衝區中。大緩衝區包含來自各種客戶端的數據。該錯誤讀取了本不應讀取的字節,但實際上並未讀取該大緩衝區之外的數據。一種檢查緩衝區溢出的語言並不會阻止這種情況,因為有人不願意或阻止任何此類檢查來發現問題。

IIRC,BSD內存分配*可以防止該錯誤被忽略,但是實現者由於認為它“太慢”而積極地規避了該系統。從某種意義上說,這就是C / C ++的全部選擇,只是這一次的決定是一個(確實)糟糕的決定。 ;-)
確實。如果您使用C#進行此操作,則可以輕鬆引入等效攻擊。
他們在做出此設計決定之前是否已對代碼進行了概要分析?我發現很難相信他們在`malloc(3)`上遇到瓶頸。
@Kevin內存分配是相對較慢的操作,特別是與一次分配緩衝區並重新使用它相比。如果您正在編寫快速代碼(並且人們經常抱怨Web服務器的內容必須很快),那麼可以,在消除所有其他瓶頸之後,很容易成為瓶頸!如果您分配了許多小緩衝區,這是非常正確的。
@Kevin:使用malloc()來響應從不受信任來源收到的數據將使代碼易於受到攻擊者的攻擊,該攻擊者將觸發導致碎片的分配/釋放模式。使用和回收內存池的代碼可以使用malloc()的代碼無法防範的方式來防止此類問題。
@gbjbaanb:實際上取決於我們正在比較的內容。我記得曾讀過jemalloc的“黃金路”,例如,大約25個週期。由於我們談論的是加密庫,並且加密不是特別快(除非有硬件輔助),所以我確實認為它值得分析。就是說,自編寫此代碼以來,情況發生了很大變化,我似乎還記得他們抱怨特定平台運行緩慢(但為所有平台引入了緩衝區)。
@MatthieuM.consider認為EASTL是遊戲STL的實現,因為通常的STL的分配系統並未針對遊戲進行充分優化。現實世界中有一個示例,其中內存分配是一個瓶頸,因此它並不像Kevin認為的那樣“難以置信”。 OpenSSL可能具有相同的“盡可能快”的要求,或者可能是WRT內存分配設計不良的。
Viktor Toth
2016-02-24 07:49:51 UTC
view on stackexchange narkive permalink

首先,正如其他人所提到的那樣,C / C ++有時被描述為美化的宏彙編器:它被稱為“接近鐵”,是系統級編程的語言。

因此,舉例來說,這種語言允許我將零長度的數組聲明為佔位符,而實際上它可能表示數據包中的可變長度部分或內存中可變長度區域的開頭,用於

不幸的是,這也意味著C / C ++在不當之手是危險的。如果程序員聲明了一個由10個元素組成的數組,然後寫入元素101,則編譯器將愉快地對其進行編譯,代碼將愉快地執行,從而破壞了該內存位置處的所有內容(代碼,數據,堆棧等)。 / p>

第二,C / C ++是特有的。一個很好的例子是字符串,它基本上是字符數組。但是每個字符串常量都帶有一個額外的,不可見的終止符。這是造成無數錯誤的原因,因為(特別是但不是排他性的)新手程序員經常無法分配終止空值所需的額外字節。

第三,C / C ++實際上已經很老了。該語言是在基本上不存在對軟件系統的外部攻擊的時候出現的。人們希望用戶信任並合作,而不是敵對,因為他們的目標是使程序正常運行,而不是使其崩潰。

這就是為什麼標準C / C ++庫包含許多本來就不安全的功能的原因。以strcpy()為例。它將很高興地複制任何內容,直到終止為空字符為止。如果找不到終止的空字符,它將繼續複製,直到死機,甚至更有可能,直到它覆蓋了至關重要的內容並且程序崩潰為止。在過去的好日子裡,這不是問題,當時不希望用戶進入一個保留用於郵政編碼的字段,例如16000個垃圾字符,然後是要執行的一組特殊構造的字節在堆棧被丟棄並且處理器在錯誤的地址處恢復執行之後。

可以肯定的是,C / C ++並不是唯一的特殊語言。其他系統具有不同的特質行為,但可能同樣糟糕。以PHP之類的後端編程語言為例,編寫允許SQL注入的代碼是多麼容易。

最後,如果我們為程序員提供了他們完成工作所需的強大工具,但是卻沒有充分的培訓和對安全環境的意識,無論使用哪種編程語言,都會發生“壞事”。

高效*編程所需的強大工具。通常,不需要*直接內存訪問;幾乎可以看到其他任何高級語言。
“最後,如果我們為程序員提供完成工作所需的強大工具,但是如果沒有足夠的培訓和對安全環境的意識,則無論使用哪種編程語言,都會發生Bad Things。”使用任何編程語言都可能發生不好的事情。但是,由於您對C&C ++的描述之類的原因,當使用某些與其他相比時,它們也傾向於(* tend *)更容易,更頻繁地發生。
更糟糕的是,C標準實際上並未讓程序員“接近金屬”編寫代碼。如果編譯器可以確定某種輸入組合會導致標準不施加任何要求的情況,那麼即使對於未定義行為幾乎沒有其他可能的後果,標準也可能會省略本來會處理此類輸入的代碼根據對可能的輸入的推論,可能會與遺漏代碼一樣糟糕。
沒有“ C / C ++”這樣的東西。您在這裡談論的大部分都是針對C的。
**所有**特定於C,甚至特定於特定的實現。 C語言中沒有規則說編譯器甚至必須“接受”嘗試訪問10元素數組的第101個元素。如果無法避免所謂的未定義行為,它可能會中止編譯。更現實的是,它可以簡單地假設相關代碼無法從“ main”中獲取,而只是忽略了整個違規功能。
@MSalters它不是特定於C的,因為它也適用於C ++。我無法理解您如何認為C ++代碼不會有緩衝區溢出。甚至`std :: vector :: operator []`也沒有邊界檢查。
特質-_adj._一勞永逸,對遺物的慷慨幫助:D
@immibis:與C數組不同,std :: vector始終帶有自己的大小。它可以進行邊界檢查,實際上使用.at(i)可以。但是,恰恰是因為可以方便地獲得尺寸,所以邊界約束通常是沒有意義的。
@MSalters“可以*進行邊界檢查”-是的,而“ operator []”(對向量進行索引的最自然方法)則不能。
@supercat,我已經讀了三遍您的評論,但我仍然聽不懂。介意在一句話中解釋少於十個獨立的從句嗎? :D
@Wildcard:如果某些輸入將導致程序調用未定義行為,則標准在給定此類輸入後對生成的代碼可能執行的操作沒有施加任何約束。例如,給定“ int * p,* q”,如果程序測試“ if(p> q)...”,則編譯器將有權推斷該代碼將永遠不會接收將導致執行該測試的輸入除非p和q是同一對象的一部分。即使平台用於正常指針比較的指令將為所有指針定義全局一致的排名,也沒有標准定義的方法...
...一個程序可以利用它。在1990年期間,許多編譯器會在許多情況下在標準沒有施加要求的情況下產生一致且有用的行為。儘管標準從未承認過這種行為,但程序員認為不需要標準要求編譯器必須完成他們已經在做的事情。不幸的是,一種怪異的歷史修正主義形式已經侵擾了C編譯器的開發,從而使人們相信,編譯器沒有明確記錄的行為是因為它們如此普遍,以至於不值得一提。
...從未真正重要。支持這種觀點的人認為,如果需要允許指針指向不同類型的別名的指令,就會有需求,而忽略了程序員正在編寫需要別名的代碼,而編譯器正在接受此類代碼並運行它的事實。正確地。不需要程序員和編譯器通常做/做的事情的想法很奇怪。
C. M.
2016-02-26 05:53:14 UTC
view on stackexchange narkive permalink

我可能會談談一些其他答案中已經說過的事情..但是..我發現問題本身是錯誤的並且是“脆弱的”。

按要求,該問題是假設的很多不了解根本問題的事情。 C / C ++不比其他語言“更容易受到攻擊”。相反,它們將計算設備的大部分功能以及使用該功能的責任直接交給程序員。因此,實際情況是,許多程序員編寫的代碼容易受到利用,並且由於C / C ++不能像某些語言一樣竭盡全力保護程序員免受自身攻擊,因此他們的代碼更容易受到攻擊。這不是C / C ++問題,因為用彙編語言編寫的程序會遇到同樣的問題。

這樣的低級編程之所以如此容易的原因這是因為執行諸如數組/緩衝區邊界檢查之類的事情可能會在計算上變得昂貴,並且在進行防禦性編程時通常是不必要的。例如,想像一下,您正在為一些主要的搜索引擎編寫代碼,該引擎必須在眨眼之間處理數万億條數據庫記錄,因此最終用戶在“頁面加載...”時不會感到無聊或沮喪。被展示。您不希望代碼在循環中每次都檢查數組/緩衝區邊界。雖然進行這種檢查可能需要幾納秒的時間,但是如果您僅處理十條記錄,這是微不足道的,但是當您遍歷數十億或數万億條記錄時,它最多可能需要幾秒鐘或幾分鐘的時間。

因此,您“信任”數據源(例如,掃描網站並將數據放入數據庫的“ Web bot”)已經檢查了數據。這不應該是不合理的假設。對於典型的程序,您希望在輸入時檢查數據,以便處理的代碼可以以最快的速度運行。許多代碼庫也採用這種方法。甚至有一些文檔,他們希望程序員在調用庫函數對數據執行操作之前已經檢查過數據。

但是,不幸的是,許多程序員並沒有進行防禦性的編程,只是假設數據必須有效且在安全範圍/參數之內。這就是攻擊者所利用的。

某些編程語言經過精心設計,旨在通過自動將其他檢查插入到生成的程序中來嘗試保護程序員免受這種不良編程習慣的侵害。明確地寫入他們的代碼。同樣,當您僅循環遍歷幾百次或更短的代碼時,這很好。但是,當您進行數十億或數万億次迭代時,它會加長數據處理的延遲,這可能變得無法接受。因此,在選擇用於特定代碼段的語言以及檢查數據中潛在的危險/可利用條件的頻率和位置時,這是一個折衷。

tl; dr:可能不必要的安全檢查和速度之間需要權衡。
“但是,當您進行數十億或數万億次迭代時,它加起來就是”-當您遍歷數組時,它在循環之前加起來恰好是一次*單檢查,因為現代編譯器非常聰明。您唯一需要支付邊界檢查的時間是編譯器是否無法確定它是否安全,這通常意味著這是隨機訪問。在這種情況下,您要多支付大約1個週期,這在某些情況下是可以累加的(例如,矩陣運算),但是對於所有代碼的99.9%,這是完全可以忽略的。
不一定是真的。是的,現代編譯器確實非常聰明,可以優化很多代碼。但這只是一個計算機程序,而不是一個聰明的人,它可以查看您的代碼並完全確定地知道程序員打算做什麼。在某些情況下,編譯器無法進行“完美的選擇”優化,而退回到更安全的優化(對於某些目的而言可能太慢了),程序員應將其關閉。人們傾向於依靠“智能編譯器”為他們工作的傾向是這種問題持續存在的部分原因。
除此之外,還有幾個假設。首先是“ 99.9%”-這個數字來自哪裡?在我看來,聽起來“ 80%的統計數據都是在現場完成的”。其次,要處理的數據是一個整潔的數組..並非總是如此。確實,使數據符合“安全”操作的概念是程序員試圖避免的計算量大的數據處理的一部分,並且僅假設數據是“安全的”,或者假定編譯器將“對其進行修復以使其符合要求”。是。”等等。
Bing Bang
2016-02-23 23:37:48 UTC
view on stackexchange narkive permalink

基本上,程序員是懶惰的人(包括我自己)。他們做的事情就像使用gets()而不是fgets()並在堆棧上定義I / o緩衝區,並且沒有足夠注意尋找內存可能無意被覆蓋的方式(對程序員來說是無意的,對於黑客來說是故意的:)。 >

在堆棧上擁有一個I / O緩衝區不是邪惡的,只是不要用它調用`gets`。
很難想像程序員會出於“懶惰”而使用C / C ++!
@DmitryGrigoryev他們讓我在學校學習C / C ++,但我懶於學習其他語言:)
-1
@JOW您顯然不做我要做的那種編程。我在生產中大約有10,000行密集的C代碼...
@BingBang讀完這篇文章我感到不安。
-1
Yakk
2016-02-27 03:41:50 UTC
view on stackexchange narkive permalink

有大量現有的C代碼,它們未經檢查地寫入緩衝區。其中一些在庫中。如果任何外部狀態都可以更改寫入的長度,則此代碼是不安全的,否則將是非常不安全的。如果上述代碼的用戶發生了數學錯誤,並且編寫了過多的錯誤,則與上述方法一樣,該方法同樣有用。不能在編譯時保證數學正確完成。

還有大量現有的C代碼會根據內存中的偏移量進行讀取。如果未檢查偏移量是否有效,則可能會洩漏信息。

C ++代碼通常用作與C互操作的高級語言,因此會遵循許多C概念,並且與C進行通信時會產生錯誤API是常見的。

C ++編程樣式可以防止此類溢出,但是只要發生1個錯誤即可允許它們發生。

此外,還有指針懸垂的問題,即內存在哪裡資源被回收,並且指針現在指向的內存/生命週期與原始指針不同,從而允許某些漏洞利用和信息洩漏。

這類錯誤-“圍欄”錯誤,“懸空”指針錯誤–如此常見,並且很難完全消除,以至於許多語言都是使用明確設計的系統開發的,以防止它們發生。

毫不奇怪,在語言中為了消除這些錯誤,這些錯誤的發生頻率幾乎沒有。它們有時仍會發生:運行該語言的引擎有問題,或者設置了與C / C ++案例的環境相匹配的手動情況(重用池中的對象,使用由使用者細分的公共大緩衝區等)。 )。但是,由於這些用途很少,因此問題發生的頻率就會降低。

在C / C ++中,每個動態分配,每個緩衝區的使用都存在這些風險。而達到完美是不可能的。

Rich
2016-02-27 05:07:40 UTC
view on stackexchange narkive permalink

大多數常用語言(例如Java和Ruby)都可以編譯為在VM中運行的代碼。 VM旨在隔離機器代碼,數據和通常的堆棧。這意味著常規語言操作不能更改代碼或重定向控制流(有時有特殊的API可以做到這一點,例如用於調試)。

C和C ++通常直接編譯為CPU的本機語言-這會帶來性能和靈活性方面的好處,但意味著錯誤的代碼可能會覆蓋程序存儲器或堆棧,從而執行不在原始程序中的指令。

通常在C ++中(可能是故意)緩衝區溢出時發生。相比之下,在Java或Ruby中,緩衝區溢出將立即導致異常,並且(VM錯誤除外)不能覆蓋代碼或更改控制流。

這與是否在VM上運行無關。您可以使VM具有與c相同的行為,就像您可以使程序直接編譯為機器語言一樣安全,就像說Java(例如ADA)一樣
從理論上講,在幾乎所有實際情況下,Java都可在防止代碼覆蓋的VM上運行,而C / C ++則可在不能這樣做的裸機上運行。
是。 ADA不能在VM上運行,並且可以像Java一樣阻止大多數此類攻擊。是否擁有VM完全與此無關(您認為VM的其他特色是什麼,否則它是無法做到的?該地獄實際上僅引入了可能的安全漏洞,因為JIT需要可寫和可執行的內存!)


該問答將自動從英語翻譯而來。原始內容可在stackexchange上找到,我們感謝它分發的cc by-sa 3.0許可。
Loading...