Supervisord是一款由Python语言开发,用于管理后台应用(服务)的工具,方便运维人员使用图形化界面进行管理。

近期,Supervisord曝出了一个需认证的远程命令执行漏洞(CVE-2017-11610),通过POST请求Supervisord管理界面恶意数据,可以获取服务器操作权限,存在严重的安全风险。

Supervisor介绍

Supervisor 是基于 Python 的进程管理工具,可以帮助我们更简单的启动、重启和停止服务器上的后台进程,是 Linux 服务器管理的效率工具。Supervisor有四个组件:

  1. supervisord

运行Supervisor的后台服务,它用来启动和管理那些你需要Supervisor管理的子进程,响应客户端发来的请求,重启意外退出的子进程,将子进程的stdout和stderr写入日志,响应事件等。它是Supervisor最核心的部分。

  1. supervisorctl

相当于supervisord的客户端,它是一个命令行工具,用户可以通过它向supervisord服务发指令,比如查看子进程状态,启动或关闭子进程。它可以连接不同的supervisord服务,包括远程机上的服务。

  1. Web服务器

这是supervisord的Web客户端,用户可以在Web页面上完成类似于supervisorctl的功能。

  1. XML-RPC接口

这是留给第三方集成的接口,你的服务可以在远程调用这些XML-RPC接口来控制supervisord管理的子进程。上面的Web服务器其实也是通过这个XML-RPC接口实现的。

漏洞简介

本次漏洞就出在XML-RPC接口对数据的处理上。

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

开启web访问的配置如下

漏洞分析

本次分析从Supervisor入口出发,根据漏洞的相关披露,层层进行解析。

先从入口看起:Supervisord启动文件Supervisord.py。

因为事先知道攻击数据是通过http方式传入到sever端的,所以重点关注下Supervisord启动方法(run)中的启动http服务方法,这里是self.options.openhttpservers()

跟入options.py文件中去:

在这里可以看到self.httpservers = self.make_http_servers(supervisord)

在这里调用了make_http_servers()方法

在Options类中找到make_http_servers()方法:

可见这个方法是从supervisor.http中导入的,我们继续跟进http.py文件查看make_http_servers()方法的实现

根据漏洞的披露,我们知道这个漏洞是在XML-RPC接口的调用上出现了问题,在上图代码的最后一行supervisor_xmlrpc_handler()方法,就是用来处理RPC调用的。

我们从supervisor的入口开始,一路顺藤摸瓜找到了罪魁祸首,接下来跟进supervisor_xmlrpc_handler()方法看一下出现漏洞的原因

supervisor_xmlrpc_handler方法在xmlrpc.py文件中实现,

**
**找到漏洞纰漏的traverse方法,可以看到supervisor_xmlrpc_handler()方法中的call函数将解析出来的method以及params传入tracerse方法中。

我们先来看下call函数在哪里被调用,以及method,params到底是什么:

在supervisor_xmlrpc_handler这个类中,有一个continue_request()函数,如下所示

在params, method = self.loads(data)一行中,可以看到params和method的产生,并且在这个函数最下面一行,可见调用了call()将返回值给了value。我们先看下这个类中的loads函数,如下所示:

可见params和method是由xml tag中的methodName和params中的值得来的。

上面说的有些抽象,为了方便下面漏洞的理解,下面举个例子:在这里利用python使用RPC协议给supervisord发一个请求,来看下RPC协议的结构和params、method分别是什么。

抓取的流量如下图:

这里的supervisor.supervisord.options.warnings.linecache.os.system就是method参数值,而param的值就是touch /tmp/success1

正常的交互中,这两个值往往是method=supervisor.startProcess;param=要启动的应用名

现在call()函数已经明晰了,再看看call函数中的tracerse方法。下面继续跟入tracerse方法

在tracerse方法中,将传入的method通过“.”来拆分,赋值给path判断开头是否为“_”,如果是,则报错

然后看ob = getattr(ob, name, None)这行,getattr()是一个自省函数,下面举一个简单的例子说明下getattr()

我们仔细分析下如下代码:

在这里类似一个递归,将原本链状的method中的方法遍历出来,例如method原先的结构是a.b.c.d这里path列表就应该是[a,b,c,d],然后遍历name。通过ob = getattr(ob, name, None),首先将ob中的a方法赋给新的ob,再将新的ob(现在是a方法)中的b方法取出来赋给新的ob。以此类推,最终的ob会是链状结构最后一个方法(也就是d),然后传入params值,被执行

所以如果想攻击利用成功,必须找到一个调用链,例如如下调用链

下图是调用关系:

Options中的warnings方法

Warnings中的linecache方法

Linecache中的os方法

漏洞利用

解决方案

升级supervisor到最新版本或在配置中修改[inet_http_server]配置