讀軟體開發安全之道:概念、設計與實施14低階編碼缺陷

躺柒發表於2024-08-31

1. 低階編碼缺陷

1.1. 在更靠近機器級別的程式碼中常會出現這類缺陷

  • 1.1.1. 越接近硬體級別越能獲得最大效率的誘惑仍然很大

  • 1.1.2. 更接近硬體級別的程式設計是非常強大的,但其代價是工作量和脆弱性的增加

1.2. 當資料超出了固定的大小,或者超出了分配的記憶體緩衝區容量時,就會出現這類問題

2. 算術漏洞

2.1. 不同程式語言在定義其算術運算時有所不同,這種不同體現在數學上或處理器的相應指令上

2.2. 浮點數運算的範圍比整數運算的範圍更大,但其有限的精度也會導致意外的結果

2.3. 定寬整數漏洞

  • 2.3.1. 定寬整數是包括Java和C/C++在內的許多語言中最基本的構建塊,如果計算超出了定寬整數的限制範圍,你將得到錯誤的結果

  • 2.3.2. 安全性的底線在於我們要了解語言規範,並且避免出現有可能未定義的計算

  • 2.3.2.1. 不要耍小聰明,試圖找到某種方法來檢測未定義的結果,因為在不同的硬體或新版本的編譯器上,你的程式碼可能會停止工作

  • 2.3.3. 錯誤的計算結果會讓程式碼出現各種問題,並且這些問題通常會像雪球一樣越滾越大,形成一連串的功能障礙,並最終導致系統崩潰或藍色畫面

  • 2.3.4. 程式設計師最好能在程式碼執行任何有可能超出範圍的計算之前解決這些問題,並且保證所有數字都在範圍之內

  • 2.3.4.1. 解決問題最簡單的方法是使用比有可能出現的最大值還要大的整數空間,然後檢查並確保無效值永遠不會悄悄潛入

  • 2.3.5. 將計算分解,以便在乘法和除法中使用64位計算,並向下轉換為32位結果

  • 2.3.6. 最乾淨的解決方案是將所有變數都升級為64位,代價是降低一點效率

  • 2.3.7. 這就是使用定寬整數進行計算所涉及的權衡

2.4. 浮點精度漏洞

  • 2.4.1. 浮點數在很多方面都比定寬整數更強健,並且限制更少

  • 2.4.2. 當超出這個極大的範圍時,你會得到一個有符號的無窮大或NaN(Not a Number,不是數字)反饋,而不是像定寬整數那樣對值進行截斷

  • 2.4.3. 1/10在二進位制中是一個無限迴圈小數(即0.00011001100…以1100無限迴圈)​,所以最低位會出現錯誤

  • 2.4.3.1. 這些錯誤都是在低位引入的,因此稱為下溢(underflow)

  • 2.4.4. 錯誤不會帶來重大影響,但是當需要全精度時,或者當計算中納入不同數量級的值時,優秀的編碼人員會格外謹慎

  • 2.4.5. 當無法精確表示結果值時,低位少量的錯誤也會一直累積

  • 2.4.5.1. 儘可能永遠不要使用浮點值來比較值是否相等(或不相等)​,因為這種操作無法容忍計算值的微小差異

  • 2.4.6. 當你必須使用高精度時,請考慮使用超高精度浮點表示法(IEEE 754中定義了128位和256位格式)​

  • 2.4.6.1. 任意精度的十進位制或有理數表示都可能是最佳選擇

2.5. 安全算術

  • 2.5.1. 整數溢位比浮點下溢更容易出現問題,因為它會帶來差別很大的結果,但我們也不能認為浮點下溢很安全而忽略它

  • 2.5.2. 使用型別轉換時要謹慎,因為它有可能像執行計算一樣導致截斷或改變結果

  • 2.5.3. 任何情況下,儘可能限制計算的輸入,確保所有可能出現的值都是可以正確表示的

  • 2.5.4. 使用較大的固定的整數來避免可能出現的溢位;在將結果轉換為較小的整數之前,檢查結果是否在範圍內

  • 2.5.5. 要記住,即使最終結果始終在範圍內,計算的中間值也有可能會溢位,從而導致問題

  • 2.5.6. 在檢查安全敏感程式碼及其周圍的算術正確性時要格外謹慎

3. 記憶體訪問漏洞

3.1. 大多數程式語言都提供了完全託管的記憶體分配,並設定適當的邊界來限制其訪問

3.2. 指標允許透過地址直接訪問記憶體,這可能是C語言中最強大的功能

3.3. 緩衝區溢位

  • 3.3.1. 當程式碼訪問的記憶體位置在預期的目標緩衝區之外時,就會發生緩衝區溢位(buffer overflow或buffer overrun)​

  • 3.3.2. 緩衝區(buffer)是表示記憶體中任意區域的通用術語:資料結構、字串、陣列、物件或任何型別的變數

  • 3.3.3. 訪問(access)是讀取記憶體或寫入記憶體的統稱

  • 3.3.4. 緩衝區溢位指的是在預期記憶體區域之外,對記憶體進行讀取或寫入

  • 3.3.5. 緩衝區溢位並不是堆記憶體獨有的,而是任何型別的變數都有可能發生的,包括靜態分配和棧上的區域性變數

  • 3.3.6. 緩衝區溢位bug會意外地讀取記憶體,這可能會將資訊洩露給攻擊者,或者導致程式碼行為異常

  • 3.3.7. 不要低估執行顯式的記憶體分配、範圍內訪問,以及精準釋放未使用的記憶體的難度和重要性

  • 3.3.7.1. 使用簡單模式來分配、使用和釋放是最好的,其中包括異常處理,以確保不會跳過釋放操作

  • 3.3.7.2. 當一個元件給其他程式碼的引用分配記憶體時,一定要定義隨後記憶體釋放的責任,將其釋放到介面的一側或另一側

  • 3.3.8. 即使是在包含完全範圍檢查、垃圾收集等功能的語言中,你仍會遇到麻煩

  • 3.3.8.1. 任何直接在記憶體中修改資料結構的程式碼都可能會導致與緩衝區溢位類似的問題

3.4. 要想降低這種記憶體意外洩露的風險並不難,但我們必須覆蓋有可能發生洩露的資料結構中的所有位元組

  • 3.4.1. 不要試圖準確預測編譯器會如何分配欄位偏移量,因為這可能會隨著時間和平臺的變化而變化

  • 3.4.2. 避免這些問題最簡單的方法是在分配後將緩衝區清零,除非我們可以確保它們被完全覆蓋,或者知道它們不會被洩露到信任邊界之外

  • 3.4.3. 即使你的程式碼本身並不使用敏感資料,但透過這種記憶體洩露路徑也可能在程序中的任何位置將其他資料洩露出去

4. Heartbleed漏洞

4.1. Heartbleed是研究低階語言脆弱性的一個很好的物件

  • 4.1.1. 小錯誤會引發巨大的影響

  • 4.1.2. 如果恰好發生在記憶體中錯誤的位置上,則緩衝區溢位可能會暴露具有高價值的秘密

  • 4.1.3. 設計(協議規範)中已經預測到了這個錯誤,指出程式碼應該忽略位元組長度不正確的心跳請求,但在沒有明確測試的情況下,沒有人注意到這個漏洞

4.2. Heartbleed是TLS Heartbeat擴充套件中OpenSSL實踐的一個缺陷,於2012年在RFC 6520中一起提出

4.3. Heartbleed漏洞不僅作為“第一個帶有徽標的bug”而成為新聞,還在部署了流行的OpenSSL TLS庫的伺服器中揭示了一個微小的漏洞

4.4. 所謂心跳,就是往返互動心跳請求,其有效載荷是16至16384(214)位元組之間的任意資料,與之對應的心跳響應中也攜帶了相同的載荷

4.5. 格式錯誤的心跳請求中會發生嚴重缺陷,這些請求提供了一個小的有效載荷,卻聲稱是一個較大的載荷

4.6. 最終會被洩露的資料取決於記憶體分配的弱點,攻擊者可以重複利用這個漏洞來訪問伺服器記憶體,並最終得到各種敏感資料

4.7. 對“會說謊”的心跳請求做出預測

  • 4.7.1. 這些請求會要求比它們所提供的載荷多得多的載荷

4.8. 在格式正常的請求中,一切都可以完美地執行,只有格式錯誤的請求才會讓本無惡意的程式碼出現問題

4.9. 心跳響應中洩露的伺服器記憶體並不會對伺服器造成直接傷害:只有對被洩露的大量資料進行仔細分析後,人們才能看出損失程度

  • 4.9.1. 它不太可能發生,並且返回比接收到的載荷多得多的載荷資料,乍看起來似乎是無害的

相關文章