讀書筆記 — Linux底下恐造成攻擊的TCP Off-path漏洞

這篇文章主要是記錄我閱讀Yue Cao等五人所著作『Off-Path TCP Exploits: Global Rate Limit Considered Dangerous』,該文於2016年八月發表,在閱讀之前,我建議先閱讀當代對抗TCP Off-path攻擊的能力,該文於SEC發表的投影片可以參考這個連結

在2016年八月初Linux爆發一個嚴重的漏洞,危及的版本從3.6開始,詳情可以參考新聞『Linux security backfires: Flaw lets hackers inject malware into downloads, disrupt Tor users, etc』,原本我打算就這篇新聞稍微聊一下,結果社群朋友就貼出了原始文章的連結,既然論文都出來了,不看完好像說不過去。

(想看中文的新聞,可以參考 ITHOME 所撰寫的『Linux爆核心漏洞,讓駭客能攔截未加密流量』)

這個漏洞的發生,原因是Linux核心實作了我們前文所提及的RFC 5961,原本以為自此之後社會太平,但卻被發現有其他的辦法可以攻破,這代表Linux核心相較於別人很糟糕嗎?我想這不一定,其他作業系統尚未完整實作RFC 5961,代表他們仍然暴露在舊有的漏洞之下。

所以這篇讀書筆記,我們主要就來瞭解一下這個漏洞是如何發生,而外面許多文章為何會告訴大家請做哪些設定,而那些設定究竟能否徹底解決漏洞的發生?

背景

RFC 5961提出了Challenge ACK的封包,該封包主要是讓接收資料的人在收到可疑的Reset/SYN封包之後,可以請資料發送方再傳遞一次,以此確保傳輸的連線不會任意被中斷,影響連線服務品質。

但是,若是ACK封包太頻繁,這會影響CPU及頻寬的使用,假如攻擊方瘋狂的傳送假的封包,若是資料接收者也瘋狂的傳送Challenge ACK給資料傳送者,整體的傳輸就會陷入不停地確認該封包是否正確,影響整體服務品質。

為了避免大量Challenge ACK封包浪費CPU及頻寬的使用,RFC 5961就提出了一個方法,並建議大家採用,他就是ACK Throttling。

ACK封包的節流

這做法很簡單,只要限制在固定時間之內(比如一秒),最多僅能送出一個數量的Challenge ACK封包。而這個數量,可以交由系統管理員/使用者去決定。

而為了實作方便,RFC 5961建議,不需要實作計時器,而是直接採用封包內的timestamp(時間戳記)及counter(計數器)來判斷即可。

RFC 5961中提到的,應為 Challenge ACK 封包的節流,而非所有ACK封包。

攻擊方法

Linux 從2012年九月所發佈的3.6版本,就開始實作RFC 5961,他採用了一個系統參數 sysctl_tcp_challenge_ack_limit ,這個參數可以設定每秒最多可以送出多少 Challenge ACK 封包,預設為100,而這個限制則為系統之下所有服務及連線加總之後的限制。這就造成了一些問題。

攻擊素材

作者提出了一個做法:

  1. 傳送欺騙的封包作為測試 (Server的IP及Port已知,Client的IP已知,Port未知,隨機猜測)
  2. 攻擊方建立一條正常的連線到Server,並試圖觸發對方傳送Challenge ACK封包達到Server的最大值
  3. 計算實際收到的 Challenge ACK封包,如果這個數量小於系統的限制,這代表有一些封包,是用來回應步驟一的欺騙封包,而這些封包已經送到其他人的電腦上。

(備註:作者假設Reset封包在連線中並不常見,當Reset封包只有攻擊者自己在發送,或是很少有人發送,這個狀況是很容易成立)

不停地進行以上的步驟,攻擊者可以成功推斷出:

  1. Client的IP及Port,而且連線是真實存在。
    只要透過正常連線收到的ACK數量小於系統限制,那就是該連線真實存在。
  2. Server下一個可能發送的封包序號 (RCV.NXT)
    如果攻擊者已經找到雙方的IP及Port,那麼只要猜出哪些封包序號可被接收,哪些無法被接受。
    基於Server只會回應有正常序號的封包,做法跟上述一樣,再透過正常管道送100個封包出去,若是只有回來99個,代表同時間傳送的欺騙封包所夾帶的序號是可以被接受的,那就猜到了。
  3. Server/Client 下一個預期收到的ACK號碼 (SND.UNA)
    如果攻擊者都找到上述兩個條件,最後只要再推斷ACK號碼就好。
    基於Server只會回應有正確序號的ACK封包,做法依舊跟上述一樣,算回來的封包是不是100個,就可以猜出當下ACK的號碼。

詳細流程可參考下圖

From https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_cao.pdf

但由於嘗試的時間會送出大量的Reset封包,這可能會被防火牆認為是攻擊的行為,猜測封包序號可以改用ACK封包來代替,而ACK的序號最大可以為4G,所以可以猜0, 1G, 2G, 3G,之所以可以猜測這麼大的數字是因為,有了TCP Scaling Option (RFC 7323)的幫忙,最大的Receive Window Size可達1G,這可以協助縮限序號範圍。

時間同步問題

如同前述所說, Challenge ACK的速度縮現在一秒能傳送多少封包,因為時差的因素,往往可以使用的時間不到一秒。這代表在幾毫秒之內,正常的連線以及欺騙的封包必須在同時抵達才行。你可能認為,我們只要在超短時間內把所有封包一口氣送過去就行了,很不幸的是,實務上你無法這麼做,因為有許多因素包含封包延遲造成抵達速度不同的影響等等,另外,這麼暴力的送一百多個封包出去,是很容易造成封包阻塞及遺失的問題。對於攻擊者來說,若能跟Server對時,那就太棒了,因為它可以有完整的一秒可以使用。

解決方法很簡單,我們只要做一個實驗,就是在一秒之內送出兩百個封包給對方,如果有回應超過一百個封包,那就代表通過了實驗。如果沒有,我們就需要調整電腦的時間,再進行試驗,一直到通過為止。(原文透過時差去計算,可使他在三次試驗之內就校正完畢,有興趣自行參考)

猜Port技巧

雖然說只有Client的port要猜,但是仍然有65535種可能性,全部猜完太耗時了,作者認為採用二元搜尋樹的策略會比較好。你可以先知道你最大可以在一秒之內送出多少詐欺封包,假設是32768。那你一開始先送出 32768 ~ 65535 的欺騙封包,假設有Challenge ACK沒有送回來,代表port號在這個範圍之內,以此慢慢縮小範圍,速度會快得多。

猜封包序號技巧

做法跟猜Port差不多,但是由於這可能是一個範圍所以相對好猜,你可以把範圍分成很多個區間範圍。但是區間的大小取決於Window Size,作者建議,這邊最好進行比較保守的估計。要得到初始的Window Size比較簡單,建立一次連線就會知道了,通常都會使用相同的Window Size。

挾持攻擊

挾持攻擊的目的,是希望能夠注入一些資料給接收方,以此為前提之下,有些挑戰是必須要被注意到的:

  1. 避免不必要的連線重置
    任意中斷別人連線,就達不到挾持的目的,這邊的挑戰是推斷封包序號,而不會中斷連線
  2. 同時識別封包序號以及ACK號碼

推斷可被接受的ACK號碼

先確定可被接受的封包序號區間已經被找到,我們才有辦法找到Server期望收到的下一個ACK號碼(SND.UNA)。

From https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_cao.pdf

如上圖所標註,若資料封包的ACK號碼介於SND.UNA-MAX.SND.WND及SND.NXT之間,那麼該封包就可以被接受,如果不在這個區間之內,又如果ACK號碼介於SND.UNA-(2³¹-1)及SND.UNA-MAX.SND.WND之間,接收者將會回應一個Challenge ACK封包。後者這個範圍稱為Challenge ACK Window。以此,SND.UNA是可以推估出來的,以下的方法可以協助我們達到這個目的:

  1. 先辨識 Challenge ACK Window 的位置
    根據RFC 132可擴增Window的大小,最大可以把 Receive Window Size延伸到2¹⁶ ~ 2³⁰ (1G),所以MAX.SND.WND不能大於1G。因此Challenge ACK window size會介於1G~2G之間。接著我們可以利用之前所提的方法,將整個ACK編號的範圍切割成四等份0, 1G, 2G, 3G,其中有一份到兩份可以成功觸發Challenge ACK封包。
    接著我們要開始嘗試送出封包,最好的策略是送一個封包給0,兩個封包給1G,四個封包給2G,八個封包給3G。接著我們開始送出100個封包出去,假設回來6個封包,那我們會知道是1G及2G可以成功觸發Challenge ACK。
  2. 找尋 Challenge ACK Window 最左邊的邊界值SND.UNA-(²³¹-1)
    現在我們知道Challenge ACK Window大概的範圍,接著我們可以利用麵所述二元搜尋樹的方法,找到Challenge ACK Window最左邊邊界值。最後就可以成功推估 SND.UNA

找到確切的封包序號

為了能夠找到序號而不中斷原本的使用者連線,我們得透過其他方法。這個想法是,與其送出假的Reset封包,攻擊者可以改送假的資料封包給接收端,只要封包的ACK號碼在Challange ACK Window之內,接收端就會直接送出Challange ACK而不中斷原本的連線。接著,就使用前文所述方法,就可以找到確切的封包序號。

現在,當攻擊者已拿到確切的封包序號以及預期的ACK號碼,攻擊方送出資料封包就會成功被接收端接受。

防範

作者實驗了最新版本的Windows及FreeBSD及其家族,這些作業系統並不會遭受攻擊,因為他們並沒有完整實作RFC 5961,更重要的是 ACK throttling在Windows及MacOS X完全找不到蹤跡。

依據作者揭露的資料,主要的攻擊層面在於 Challenge ACK 數量的限制,最好的做法就是讓每一條連線都有自己的 Challenge ACK 限制,而非整個系統的限制。假設真的非常在意效能問題,作者建議可以在數量限制上加一些雜訊,而非一個可以猜測出來的固定值,比如說在一個範圍之內的亂數限制,都比定值來得好。

碎念

這篇內容比我想像中的還長,在資安類的議題,在台灣比較少人會將新聞上的議題拿出來討論,剛好這個議題引發我的注意,我想順勢跟大家分享,也希望降低難度之後的文章可以幫助你在短時間內,獲得比新聞更深入的了解。

另外仿間會建議你在系統參數加上

net.ipv4.tcp_challenge_ack_limit = 999999999

如果你文章有看完,你會知道,這確實是短時間之內最好的解法,但不是長久之計,相關產業的朋友,我建議你隨時關注Linux核心在這方面的改善。

Linux 3.6之後的核心,被廣泛使用在許多裝置上,包含手機家電等等,電腦及大型主機有機會更新系統,而其他裝置可能就沒那麼容易了。

最主要的問題可能需要專注在資料被隨意注入的問題上面,舉Web來說,如果是HTTPS的連線,那可能頂多造成中斷或是瀏覽器出錯。但如果是沒有加密的HTTP連線,攻擊者就有機會注入惡意的Javascript或是Flash,那會造成的影響,可能就不是單純阻斷服務這麼簡單了。

附註

  1. 360已經成功實驗了這攻擊模式,請參考 360MeshFire Team:CVE-2016–5696 TCP旁路攻击分析与重现