作者: 1mperio@云鼎實驗室
原文鏈接:https://mp.weixin.qq.com/s/iu4cS_DZTs0sVVg92RBe4Q

北京時間2月26日凌晨3點,SaltStack官方發布高危漏洞風險通告,包含CVE-2021-25281、25282、25283。此前,云鼎實驗室安全研究員1mperio就曾發現該漏洞,并于2020年11月16日報送給SaltStack官方。

在對CVE-2020-17490和CVE-2020-16846進行分析后,發現CVE-2020-17490的補丁存在未修補完全的情況,導致wheel_async仍然存在未授權訪問,可以調用wheel模塊中的方法,基于此對SaltStack的wheel模塊中的方法進行分析,最終發現加載配置模塊存在模板注入,可以實現未授權遠程代碼執行。

引言

SaltStack是VMware子公司,其產品用于運維管理,能夠支持數萬臺服務器,主要功能是配置文件管理和遠程執行命令,十分易用且強大,在github有11.4k star。

SaltStack只用python開發,采用C/S架構,其中Server被稱為Master,Client被稱為Minion,即一個Master能夠向多個Minion下發配置文件,遠程執行命令。SlatStack是系統總稱,主要有salt、salt-master、salt-minion、salt-api等程序組成,其中salt-master和salt-minion的功能為從指定路徑讀取配置文件并啟動。salt-master監聽4505和4506端口,分別用于發布消息和接受監控數據。

salt程序可以調用大量函數,并可以指定minion或指定一組minion作為目標。salt-api可以使用cherrypy或tornado來對外提供REST接口,默認使用cherrypy。

本文主要對salt-master和salt-api展開討論。

文中指定代碼位置采用以下約定:FileLocation:Classname.method()或FileLocation:Method()

CVE-2021-25281 未授權訪問

通過分析CVE-2020-25592的

https://gitlab.com/saltstack/open/salt-patches/-/blob/master/patches/2020/09/25/3002.patch

可以發現 ,補丁通過調用認證模塊對SSH方法進行權限認證,而salt/salt/netapi/init.py:NetapiClient.run()方法通過getattr動態調用NetapiClient類中的方法,并將args和kwargs作為參數傳入。

該類中可調用的方法有

1 — local
2 — local_async
3 — local_batch
4 — local_subset
5 — runner
6 — runner_async
7 — ssh
8 — wheel
9 — wheel_async

經過分析,其中,wheel_async方法存在未授權調用,其他方法(除去SSH)均為生成一個job到zeromq,其后進行消費者再進行認證,而wheel_async異步調用wheel包中的方法。

調用鏈如下:

salt/salt/netapi/init.py:NetapiClient.run() ? salt/salt/netapi/init.py:NetapiClient.wheel_async() ? salt/salt/wheel/init.py:WheelClient.cmd_async() ? salt/salt/client/mixins.py:AsyncClientMixin.asynchronous()

salt/salt/client/mixins.py:AsyncClientMixin.asynchronous()

這里的目標函數是self._proc_function,low參數為POST可控參數,fun參數的值在salt/salt/wheel/init.py:WheelClient.cmd_async()方法中通過low參數的fun鍵獲取。

這里通過salt/salt/client/mixins.py:AsyncClientMixin._proc_function()函數調用salt/salt/client/mixins.py:SyncClientMixin.low(),并通過該函數使用args參數和kwargs參數動態調用wheel包中的方法。

salt/salt/client/mixins.py:SyncClientMixin.low()

可調用的方法如下:

config.apply
config.update_config
config.values
error.error
file_roots.find
file_roots.list_env
file_roots.list_roots
file_roots.read
file_roots.write
key.accept
key.accept_dict
key.delete
key.delete_dict
key.finger
key.finger_master
key.gen
key.gen_accept
key.gen_keys
key.gen_signature
key.get_key
key.print
key.list
key.list_all
key.master_key_str
key.name_match
key.reject
key.reject_dict
minions.connected
pillar_roots.find
pillar_roots.list_env
pillar_roots.list_roots
pillar_roots.read
pillar_roots.write

CVE-2021-25282 有限制任意文件寫漏洞

其中salt/salt/wheel/pillar_roots.py:write()方法存在任意寫入文件漏洞,不過需要opts["pillar_roots"]中的路徑存在。

這里的讀文件是沒有辦法利用的,由于是異步調用,所以返回的是jid和tag,通過jid和tag去查詢任務執行的結果時是有認證的。

salt/salt/wheel/pillar_roots.py:write()

CVE-2021-25283 模板注入漏洞

通過—log-level=debug參數開啟debug模式,定位到了master自動加載的邏輯。

salt/salt/master.py:Maintenance.run()

從代碼中可以看出,每一個self.loop_interval將循環一次,loop_interval在配置文件中可以配置,默認為60s。通過debug發現在salt.daemons.masterapi.clean_old_jobs中讀取minion配置文件。

調用棧如下:

salt/salt/daemons/masterapi.py:clean_old_jobs() ? salt/salt/minion.py:MasterMinion.init() ? salt/salt/config/init.py:minion_config()

在 salt/salt/minion.py:MasterMinion.init()中發現,自動加載值加載grains相關的參數,grains為saltstack收取各個minion中系統信息的功能。

salt/salt/minion.py:MasterMinion.init()

salt/salt/config/init.py:minion_config()

可以看到minio在加載配置文件的時候調用了一個很誘人的方法apply_sdb(),這個方法解析配置中以sdb://開頭的字符串。

salt/salt/config/init.py:apply_sdb()

salt/salt/utils/sdb.py:sdb_get()

在這個函數中sdb://aaaa/bbbb字符串,saltstack將會在配置文件中找aaaa這個配置項,并讀取其中driver字段,賦值給fun變量,經bbbb賦值給query參數。最后的salt.loader.sdb(opts, fun, utils=utils)是一個動態調用,通過LazyLoader加載fun變量值對應的方法,并調用,其中LazyLoader將加載salt.sdb包下的所有文件,并調用其中的get方法。

經過查找,最終定位到salt/salt/sdb/rest.py文件。

salt/salt/sdb/rest.py:query()

在這里,key為上述字符串中bbbb的值,可以看到這里還接收形如bbbb?ccc=ddd的參數,并且通過**key_vars傳遞到compile_template方法中。

這里的render使用的是jinja,眾所周知,jinja是可以進行模板注入的,也就是說,在模板可控的情況下,如果不存在過濾,將可以執行任意代碼,并且這里傳入的參數是profile[key]['url'],也就是配置文件中aaaa配置項中bbbb字典url的值。compile_template函數詳情如下:

salt/salt/template.py:compile_template()

這里的render調用的是salt/salt/renderers/jinja.py中的render方法,調用鏈如下:

salt/salt/template.py:compile_template() ? salt/salt/utils/templates.py:JINJA() ? salt/salt/utils/templates.py:wrap_tmpl_func() ? salt/salt/utils/templates.py:render_jinja_tmpl()

最后調用到render_jinja_tmpl中的template.render()方法,在此處渲染模板,此中并未對傳入的參數進行過濾,可以進行模板注入。

但自動加載的邏輯中未加載master的配置文件,但經過翻找,發現某個方法調用了master_config方法,master_config和minion_config一樣,都調用了apply_sdb()方法,從而能夠實現未授權RCE。

修復建議

  1. 盡快更新官方補丁。
  2. 如果沒有用到wheel_async模塊,可以在salt/netapi/init.py中將其入口刪除。

拓展鏈接

CVE-2020-17490 CVE-2020-16846 詳情請見:

漏洞分析|SaltStack未授權訪問及命令執行漏洞分析(CVE-2020-16846/25592)


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1491/