CVE-2018-1002105 bug的描述中說到是k8s apiserver在處理代理請求時會留下易受攻擊的TCP連接趾浅。這將導致前端請求能夠跨過ApiServer直接請求到服務后端,從而留下安全隱患。這篇文章中,我們將分析這個漏洞到底是怎么回事。
關于CVE-2018-1002105更多信息可以查看這個鏈接。
請求代理
首先說請求代理。在k8s中础芍,請求代理是一個非常常見的模式:apiserver將來自前端的請求轉發(fā)到后端服務處理,并將后端的響應返回給前端数尿。
典型的應用是kube-aggregator仑性。kube-aggregator提供了API用于向k8s集群注冊其他的apiserver,kube-aggregator負責處理api發(fā)現以及客戶端請求代理右蹦。metrics-server就是一個典型的基于kube-aggregator的apiserver诊杆。metrics-server基于heapster項目演化而來,為k8s集群提供了node/pod性能數據何陆。更多關于metrics-server的分析可以看這篇文章晨汹。
http upgrade
請求代理部分是本次bug的主角,具體來說是涉及到http upgrade贷盲。
首先我們了解一下http upgrade淘这。upgrade是http/1.1提供協議升級接口剥扣,讓客戶端和服務端可以協商使用其他的協議通信,例如https/websocket等铝穷。
- 當客戶端向服務端發(fā)起upgrade請求钠怯,請求頭包含upgrade及希望升級的協議,如下所示: OPTIONS * HTTP/1.1 Host: example.bank.com Upgrade: TLS/1.0 Connection: Upgrade
- 服務端接受升級請求曙聂,返回101 Switching Protocol響應晦炊,如下所示: HTTP/1.1 101 Switching Protocols Upgrade: TLS/1.0, HTTP/1.1 Connection: Upgrade
- 服務端發(fā)送響應后,切換到新的協議宁脊。
- 客戶端再通過當前連接通過新的協議發(fā)送請求断国。 更多關于upgrade的信息可以在RFC2817找到。
在apiserver請求代理的過程中榆苞,客戶端通過自身的請求認證的方式連接apiserver并思,apiserver通過證書連接其他的apiserver(如metrics-server)。所以语稠,客戶端與服務后端并不直接通信,而是交由apiserver負責轉發(fā)弄砍。我們看k8s中的upgrade proxy實現仙畦。
tryUpgrade函數(k8s.io/apimachinery/pkg/util/proxy/upgradeaware.go)負責處理upgrade請求的轉發(fā),其執(zhí)行流程描述如下: 1音婶、判斷是否是upgrade請求慨畸,如果不是直接返回 2、連接服務后端衣式,獲取連接對象backendConn 3寸士、Hijack http請求,操作底層的tcp buffer 4碴卧、雙向代理數據:將request中讀取的內容寫入backend讀取buffer弱卡,將backend寫出的內容寫入到request的寫出buffer。 5住册、關閉相關連接
貼一些核心代碼:
go func() {
var writer io.WriteCloser
if h.MaxBytesPerSec > 0 {
writer = flowrate.NewWriter(backendConn, h.MaxBytesPerSec)
} else {
writer = backendConn
}
_, err := io.Copy(writer, requestHijackedConn)
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
glog.Errorf("Error proxying data from client to backend: %v", err)
}
close(writerComplete)
}()
go func() {
var reader io.ReadCloser
if h.MaxBytesPerSec > 0 {
reader = flowrate.NewReader(backendConn, h.MaxBytesPerSec)
} else {
reader = backendConn
}
_, err := io.Copy(requestHijackedConn, reader)
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
glog.Errorf("Error proxying data from backend to client: %v", err)
}
close(readerComplete)
}()
這里存在的安全問題是代碼中并沒有判斷客戶端向后端服務發(fā)送upgrade請求后后端服務的響應婶博,而是直接默認返回是SwitchingProtocols響應,之后直接將客戶端的請求發(fā)送給后端服務荧飞,并將后端服務的響應返回給客戶端凡人。這樣就存在一種可能性,客戶端可以構造一個upgrade請求叹阔,讓后端服務不進行協議的切換挠轴,然后再發(fā)送正常的其他http請求,這樣后端服務將認為是來自于apiserver的正常請求耳幢,將可以繞過k8s的各種安全機制直接獲取后端服務的響應岸晦。
因而,在后期的修復中,主要的邏輯也是提前判斷upgrade請求的響應是否是SwitchingProtocols委煤,如果不是堂油,則關閉連接,不代理接下來的數據傳輸碧绞。代碼如下所示:
if rawResponseCode != http.StatusSwitchingProtocols {
// If the backend did not upgrade the request, finish echoing the response from the backend to the client and return, closing the connection.
glog.V(6).Infof("Proxy upgrade error, status code %d", rawResponseCode)
_, err := io.Copy(requestHijackedConn, backendConn)
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
glog.Errorf("Error proxying data from backend to client: %v", err)
}
// Indicate we handled the request
return true
}
Bug的影響
在k8s框架中府框,只要使用到這段代理邏輯的模塊都將受到影響,kube-aggregator是肯定的讥邻。其他還包括:
- kubectl proxy命令將創(chuàng)建一個本地到apiserver的proxy server迫靖。
- kubelet 在處理來自客戶端的attach/exec/portforward請求時,創(chuàng)建了一個upgrade proxy來轉發(fā)相應的數據流兴使。