
Este proyecto pretende proporcionar una serie de herramientas para crear, procesar, enviar, analizar y descifrar un conjunto de paquetes LoRaWAN con el fin de auditar o realizar pruebas de penetración de la seguridad de una infraestructura LoRaWAN.
Las implementaciones de IoT siguen creciendo y una parte de ese crecimiento significativo está compuesta por millones de sensores LPWAN (red de área amplia de baja potencia) desplegados en cientos de ciudades (Smart Cities) alrededor del mundo, también en industrias y hogares. Una de las tecnologías LPWAN más utilizadas es LoRa, para la cual LoRaWAN es el estándar de red (capa MAC). LoRaWAN es un protocolo seguro con cifrado incorporado, pero problemas de implementación y debilidades afectan la seguridad de la mayoría de las implementaciones actuales.
Este proyecto tiene como objetivo proporcionar una serie de herramientas para crear, analizar, enviar, analizar y descifrar un conjunto de paquetes LoRaWAN con el fin de auditar o realizar pruebas de penetración en la seguridad de una infraestructura LoRaWAN.
A continuación, la estructura de este repositorio:
|-- tools
|-- UdpSender.py
|-- UdpProxy.py
|-- TcpProxy.py
|-- lorawan
|-- BruteForcer.py
|-- MicGenerator.py
|-- PacketCrafter.py
|-- PacketParser.py
|-- SessionKeysGenerator.py
|-- Loracrack (https://github.com/matiassequeira/Loracrack/tree/master)
|-- utils
|-- DevAddrChanger.py
|-- Fuzzer.py
|-- FileLogger.py
|-- auditing
|-- datacollectors
|-- MqttCollector.py
|-- UdpForwarderProxy.py
|-- analyzers
|-- LafProcessData.py
|-- bruteForcer
|-- LafBruteforcer.py
|-- keys
|-- dataanalysis
|-- LafPacketAnalysis.py
|-- printer
|-- LafPrinter.py
|-- db
|-- __init__.py
|-- Models.py
|-- Service.py
|-- lorawanwrapper
|-- LorawanWrapper.py
|-- utils
|-- jsonUnmarshaler.go
|-- lorawanWrapper.go
|-- micGenerator.go
|-- sessionKeysGenerator.go
|-- scripts
|-- gateway_channel_changer
|-- LoRa-GW-Installer.sh
|-- Continuous-Channel-Switch.sh
|-- LoRa-GW-Channel-Setup.sh
Ofrecemos diferentes opciones para tener tu LoRaWAN Auditing Framework listo y funcionando:
tools/, para evitar problemas con el mapeo de puertos de Docker.localhost. Consulta las instrucciones a continuación para configurar Docker.Estas instrucciones te darán una copia del proyecto y sus dependencias en tu máquina local. Los comandos a continuación son para un entorno basado en Debian:
Clona este repositorio: git clone --recurse-submodules https://github.com/IOActive/laf.git
Instala python3:
sudo apt-get updatesudo apt-get install python3.6Descarga e instala las dependencias de Python:
sudo pip3 install paho-mqtt && sudo pip3 install sqlalchemy && sudo pip3 install psycopg2-binary &&sudo pip3 install python-dateutilEstablece PYTHONPATH y ENVIRONMENT
cd laf && export PYTHONPATH=$(pwd) && export ENVIRONMENT='DEV'Instala y configura golang:
cd ~/Downloadssudo tar -C /usr/local -xvzf TU_ARCHIVO_GOLANGexport PATH=$PATH:/usr/local/go/binexport GOPATH="$HOME/go"Compila la biblioteca go:
cd laf/lorawanwrapper/utilsgo build -o lorawanWrapper.so -buildmode=c-shared jsonUnmarshaler.go lorawanWrapper.go micGenerator.go sessionKeysGenerator.go hashGenerator.goDependiendo de la base de datos que desees usar:
a. PostgreSQL: Sigue las instrucciones 'Instalar LAF usando Docker' hasta el paso 3.
b. SQLite:
cd laf/auditing/db__init__.py con tu editor de texto preferido y comenta las líneas para usar con Postgres (conexión DB y variables de entorno) y descomenta la línea para usar con sqlite.¡Y eso es todo!
Este método evita lidiar con la instalación de dependencias e inicia una base de datos PostgreSQL donde las herramientas guardan paquetes y datos. Contenedores:
Pasos:
git clone https://github.com/IOActive/laf.gitcd laf/docker-compose up --builddocker exec -ti laf_tools_1 /bin/bashPuedes revisar los datos en la base de datos usando pgAdmin:
Primero, accede a pgAdmin:
Luego, debes agregar el servidor:
Aquí se describe el contenido de los directorios y las herramientas/funciones dentro de ellos.
El propósito principal de las herramientas proporcionadas en este directorio es facilitar la ejecución de una prueba de penetración a una infraestructura LoRaWAN.
Esta herramienta está diseñada para enviar paquetes uplink (hacia el servidor de red o gatewayBridge, dependiendo de la infraestructura) o paquetes downlink (hacia el packet-forwarder). Opcionalmente, los paquetes pueden ser sometidos a fuzzing y se puede calcular un MIC válido.
Argumentos opcionales:
-h, --help muestra este mensaje de ayuda y sale
--lcl-port LCL_PORT Puerto de origen, ej. --lcl-port=623.
--timeout TIMEOUT Tiempo en segundos entre cada paquete enviado. Por defecto es
1s. Durante este tiempo, el remitente escuchará respuestas.
--repeat Enviar mensaje/s varias veces
--fuzz-out FUZZ_OUT [FUZZ_OUT ...]
Datos de fuzzing enviados al puerto de destino (consulta los modos de fuzzing en
utils/fuzzer.py), ej. --fuzz-out 1 2.
--key KEY Ingresa la clave (en formato hex, un total de 32 caracteres
/ 16 bytes) para firmar paquetes (calcular y agregar un nuevo
MIC). Ten en cuenta que para JoinRequests debe ser la
AppKey, y NwkSKey para paquetes de datos. Esto no puede
ser validado de antemano por este programa. ej.
00112233445566778899AABBCCDDEEFF
-a DEVADDR, --devaddr DEVADDR
Dirección de dispositivo a suplantar, en formato hex (8 caracteres
en total), ej. AABB0011.
--fcnt FCNT El contador de trama a establecer en el paquete de datos dado.
Esto no funcionaría en un JoinRequest/JoinAccept ya que
estos paquetes no tienen un fCnt
Argumentos requeridos:
--dst-ip DST_IP IP de destino, ej. --dst-ip 192.168.3.101.
--dst-port DST_PORT Puerto de destino, ej. --dst-port 623.
--data DATA Paquete UDP. También se pueden agregar más paquetes en
el arreglo "data" al final de este script. El paquete
debe ser una cadena de bytes (deberás escapar las comillas
dobles). ***EJEMPLO*** con el formato packet_forwarder:
--data "b'\x02\xe67\x00\xb8\'\xeb\xff\xfez\x80
\xdb{\"rxpk\":[{\"tmst\":2749728315,\"chan\":0,\"rfch\
":0,\"freq\":902.300000,\"stat\":1,\"modu\":\"LORA\",\
"datr\":\"SF7BW125\",\"codr\":\"4/5\",\"lsnr\":9.5,\"r
ssi\":-76,\"size\":23,\"data\":\"AMQAAAAAhQAAAgAAAAAAA
ACH9PRMJi4=\"}]}'" ***EJEMPLO*** usando el formato gatevice
[GV] en modo inmediato, en BW125 y frecuencia 902.3 es "b'{\"tx_mode\": 0, \"freq\": 902.3,
\"rfch\": 0, \"modu\": 16, \"datarate\": 16,
\"bandwidth\":3, \"codr\": 1, \"ipol\":false,
\"size\": 24, \"data\":
\"QOOL8AGA6AMCnudJqz3syCkeooCvqbSn\", \"class\": 2}'"
Ejemplo:
Para enviar un solo paquete cada 2 segundos a (localhost, 10001) desde el puerto 10000, aplicando fuzzing aleatorio al MIC y al FCounter:
python3 UdpSender.py --lcl-port 10000 --dst-ip 127.0.0.1 --dst-port 10001 --timeout 2 --fuzz-out 4 5 --data "b'\x02\xe67\x00\xb8\'\xeb\xff\xfez\x80\xdb{\"rxpk\":[{\"tmst\":2749728315,\"chan\":0,\"rfch\":0,\"freq\":902.300000,\"stat\":1\"modu\":\"LORA\",\"datr\":\"SF7BW125\",\"codr\":\"4/5\",\"lsnr\":9.5,\"rssi\":-76,\"size\":23,\"data\":\"AMQAAAAAhQAAAgAAAAAAAACH9PRMJi4=\"}]}'"
Este proxy UDP está diseñado principalmente para colocarse entre una serie de gateways (packet_forwarders) y un servidor de red o gateway bridge, dependiendo de la infraestructura que se esté evaluando. También ofrece la posibilidad de aplicar fuzzing a los datos en la dirección deseada (uplink o downlink)
Argumentos opcionales:
-h, --help muestra este mensaje de ayuda y sale
--collector-port COLLECTOR_PORT
Puerto del recolector de datos de packet forwarder, ej. --collector-
port 1701. Consulta
auditing/datacollectors/PacketForwarderCollector.py
--collector-ip COLLECTOR_IP
IP del recolector de datos de packet forwarder. Por defecto es
localhost. ej. --collector-ip 192.168.1.1. Consulta
auditing/datacollectors/PacketForwarderCollector.py
--fuzz-in FUZZ_IN [FUZZ_IN ...]
Datos de fuzzing enviados al puerto de destino en los modos dados
(consulta los modos de fuzzing en utils/fuzzer.py), ej. --fuzz-in 1 2
...
--fuzz-out FUZZ_OUT [FUZZ_OUT ...]
Datos de fuzzing enviados al puerto de (origen) en los modos dados
(consulta los modos de fuzzing en utils/fuzzer.py), ej. --fuzz-out
1 2 ...
-k KEY, --key KEY Ingresa una AppSKey de dispositivo (en formato hex, un total de 32
caracteres / 16 bytes) para descifrar su FRMPayload e
imprimirlo en texto plano. También puedes ingresar la AppKey
si deseas descifrar un Join Accept dado. ej.
00112233445566778899AABBCCDDEEFF
-p PATH, --path PATH Ruta del archivo donde guardar los datos. Si no se proporciona,
los datos no se guardarán.
--no-log No imprimir paquetes UDP en la consola
--no-parse No analizar PHYPayload. Si se selecciona esta opción,
las bibliotecas Golang de /lorawanwrapper/ no se
importarán (no se requiere compilar las bibliotecas golang)
Argumentos requeridos:
--port PORT Puerto local para escuchar, ej. --port 623.
--dst-ip DST_IP IP del host de destino, ej. --dst-ip 192.168.3.101.
--dst-port DST_PORT Puerto del host de destino, ej. --dst-port 623.
Ejemplo:
Para enviar paquetes recibidos en el puerto 1234 a (localhost, 1235) y viceversa. Los paquetes recibidos en el puerto serán sometidos a fuzzing (se cambiará el devNonce aleatoriamente) y reenviados a (localhost, 1235).
python3 UdpProxy.py --port 1234 --dst-ip 127.0.0.1 --dst-port 1235 --fuzz-in 9
Este proxy TCP está diseñado principalmente para colocarse entre el servidor de red y un broker MQTT. También ofrece la posibilidad de aplicar fuzzing a los datos.
Argumentos opcionales:
-h, --help muestra este mensaje de ayuda y sale
--fuzz-in FUZZ_IN [FUZZ_IN ...]
Datos de fuzzing enviados al puerto de destino en los modos dados
(consulta los modos de fuzzing en utils/fuzzer.py)
Argumentos requeridos:
--lcl-port LCL_PORT Puerto local para escuchar, ej. --lcl-port=623.
--dst-ip DST_IP IP del host de destino, ej. --dst-ip=192.168.3.101.
--dst-port DST_PORT Puerto del host de destino, ej. --dst-port=623.
Ejemplo:
Enviar y recibir datos desde (localhost, 1884) y (localhost, 1883)
python3 TcpProxy.py --lcl-port 1884 --dst-ip 127.0.0.1 --dst-port 1883
Este directorio contiene una serie de scripts para analizar, crear, forzar por fuerza bruta, etc. paquetes LoRaWAN.
Este script recibe un JoinAccept o JoinRequest en Base64 e intenta descifrar su AppKey con un conjunto de posibles claves que pueden ser proporcionadas en un archivo o generadas sobre la marcha.
Argumentos opcionales:
-h, --help muestra este mensaje de ayuda y sale
-k KEYS, --keys KEYS Archivo que contiene una lista de claves, separadas por \n. Por defecto
usará /auditing/analyzers/bruteForcer/keys.txt
--dont-generate Selecciona esta opción si no deseas generar claves
sobre la marcha con las siguientes combinaciones: 1- Combinar
el primer byte y los últimos quince bytes. ej.
AABBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 2- Combinar posiciones de bytes
pares e impares por igual. ej.
AABBAABBAABBAABBAABBAABBAABBAABB 3- Los primeros 14 bytes
en 00 y combinar los últimos 2. ej.
0000000000000000000000000000BA01
Argumentos requeridos:
-a ACCEPT, --accept ACCEPT
Join Accept en formato Base64 para ser forzado por fuerza bruta. ej. -a
IHvAP4MXo5Qo6tdV+Yfk08o=
-r REQUEST, --request REQUEST
Join Request en formato Base64 para ser forzado por fuerza bruta. ej.
-r AMQAAAAAhQAAAgAAAAAAAADcYldcgbc=
Ejemplo:
Descifrar un JoinRequest con un conjunto de claves de my-keys.txt y también generar aproximadamente 200000 más dinámicamente.
python3 BruteForcer.py -a IHvAP4MXo5Qo6tdV+Yfk08o= -r AMQAAAAAhQAAAgAAAAAAAADcYldcgbc= -k ./my-keys.txt
Este script recibe un paquete PHYPayload en Base64 y una clave que puede ser la NwkSKey o la AppKey dependiendo del tipo de paquete, y genera el nuevo MIC.
Argumentos opcionales:
-h, --help muestra este mensaje de ayuda y sale
--jakey JAKEY [SOLO JoinAccept]. Ingresa la clave utilizada para cifrar el
JoinAccept previamente (en formato hex, un total de 32
caracteres / 16 bytes). Esto no puede ser validado de antemano
por este programa. ej.
00112233445566778899AABBCCDDEEFF. Una clave válida de ejemplo
para el JoinAccept "IB1scNmwJRA32RfMbvwe3oI=" es
"f5a3b185dfe452c8edca3499abcd0341"
Argumentos requeridos:
-d DATA, --data DATA Datos en Base64 a firmar. ej. -d
AE0jb3GsOdJVAwD1HInrJ7i3yXAFxKU=
-k KEY, --key KEY Ingresa la nueva clave (en formato hex, un total de 32
caracteres / 16 bytes) para firmar paquetes (calcular y
agregar un nuevo MIC). Ten en cuenta que para
JoinRequest/JoinAccept debe ser la AppKey, y la NwkSKey para
paquetes de datos. Esto no puede ser validado de antemano
por este programa. ej. 00112233445566778899AABBCCDDEEFF
Ejemplo:
Firmar el PHYPayload dado con la AppKey 00112233445566778899AABBCCDDEEFF.
python3 MicGenerator.py -d AE0jb3GsOdJVAwD1HInrJ7i3yXAFxKU= -k 00112233445566778899AABBCCDDEEFF
Este script recibe un paquete JSON de LoRaWAN y lo transforma a Base64. Hace lo inverso que packetParser.py, por lo que la salida de ese script se puede usar aquí y viceversa.
Argumentos opcionales:
-h, --help muestra este mensaje de ayuda y sale
-k KEY, --key KEY Ingresa una AppSKey o AppKey de dispositivo (en formato hex, un total de
32 caracteres / 16 bytes) para cifrar el FRMPayload o un Join Accept. ej.
F5A3B185DFE452C8EDCA3499ABCD0341
--nwkskey NWKSKEY Ingresa la clave de sesión de red si deseas
generar un paquete de datos con un MIC válido.
Argumentos requeridos:
-j JSON, --json JSON Objeto JSON a analizar. ej. -j '{"mhdr":
{"mType":"JoinRequest","major":"LoRaWANR1"},"macPayloa
d":{"joinEUI":"55d239ac716f234d","devEUI":"b827eb891cf
50003","devNonce":51639},"mic":"7005c4a5"}'
Ejemplo:
Obtener un PHYPayload de JoinRequest en Base64 dado el JSON con los valores pasados en él.
python3 PacketCrafter.py -j '{"mhdr":{"mType":"JoinRequest","major":"LoRaWANR1"},"macPayload":{"joinEUI":"55d239ac716f234d","devEUI":"b827eb891cf50003","devNonce":51639},"mic":"7005c4a5"}'
Este script analiza e imprime un solo dato PHYPayload de LoRaWAN en Base64. Hace lo inverso que packetCrafter.py, por lo que la salida de ese script se puede usar aquí y viceversa.
Argumentos opcionales:
-h, --help muestra este mensaje de ayuda y sale
-k KEY, --key KEY Ingresa una AppKey o AppSKey de dispositivo según el
paquete a descifrar (join accept o paquete de datos).
Debe estar en formato hex, un total de 32 caracteres / 16
bytes. ej. 00112233445566778899AABBCCDDEEFF
Argumentos requeridos:
-d DATA, --data DATA Datos en Base64 a analizar. ej. -d
AE0jb3GsOdJVAwD1HInrJ7i3yXAFxKU=
Ejemplo:
Obtener el JoinRequest en formato JSON del ejemplo anterior.
python3 PacketParser.py -d AE0jb3GsOdJVAwD1HInrJ7i3yXAFxKU=
Este script recibe un JoinAccept y un JoinRequest en Base64, y una AppKey para generar las claves de sesión. Un ejemplo de uso:
Argumentos opcionales:
-h, --help muestra este mensaje de ayuda y sale
Argumentos requeridos:
-a JACCEPT, --jaccept JACCEPT
Payload JoinAccept en base64
-r JREQUEST, --jrequest JREQUEST
Payload JoinRequest en base64
-k KEY, --key KEY Ingresa una AppKey de dispositivo (en formato hex, un total de 32
caracteres / 16 bytes). ej.
00112233445566778899AABBCCDDEEFF
Ejemplo:
Obtener la AppSKey y NwkSKey con los siguientes datos de join.
python3 SessionKeysGenerator.py -r AE0jb3GsOdJVAwD1HInrJ7i3yXAFxKU= -a IB1scNmwJRA32RfMbvwe3oI= -k f5a3b185dfe452c8edca3499abcd0341
Estas son funciones auxiliares utilizadas por UdpSender.py y UdpProxy.py. En Fuzzer.py puedes ver los modos de fuzzing implementados.
El propósito general de este directorio es recolectar paquetes LoRaWAN y analizar diferentes aspectos del tráfico, así como probar un conjunto de claves para intentar forzar por fuerza bruta la AppKey.
Este directorio contiene un conjunto de scripts que reciben paquetes LoRaWAN de diferentes fuentes (por ejemplo, gateway packet_forwarder, The Things Network, etc.) y los guardan en archivos, con un formato estándar. Estos archivos deben ser recuperados posteriormente por el script /auditing/analyzers/LafProcessData.py para ejecutar diferentes sub-herramientas.
Este script se conecta al broker mqqt, recupera todos los topics y guarda los mensajes en un archivo en el campo especificado. El nombre del archivo se compone de la fecha en que inició este script.
Argumentos opcionales:-h, --help muestra este mensaje de ayuda y sale --collector-id COLLECTOR_ID El ID del recolector de datos. Este ID se asociará a los paquetes guardados en la BD. ej. --id 1 --organization-id ORGANIZATION_ID El ID del recolector de datos. Este ID se asociará a los paquetes guardados en la BD. ej. --id 1 --topics TOPICS [TOPICS ...] Lista los temas a los que deseas suscribirte separados por espacios. Si no se proporciona ninguno, el valor predeterminado será "#".
Argumentos requeridos:
--ip IP Dirección IP del broker MQTT, ej. --ip 192.168.3.101.
--port PORT Puerto del broker MQTT, ej. --port 623.
Ejemplo:
Conectarse al broker MQTT con IP 200.200.200.200 en el puerto predeterminado (1883).
python3 GenericMqttCollector.py --ip 200.200.200.200 --port 1883
Este script se conecta a un broker mqtt de loraserver.io y guarda los mensajes en la BD. Debes especificar un collectorID único y puedes especificar los temas a los que deseas suscribirte.
Argumentos opcionales:
-h, --help muestra este mensaje de ayuda y sale
--port PORT Puerto del broker MQTT, ej. --port 623. Predeterminado 1883.
--collector-id COLLECTOR_ID
El ID del recolector de datos. Este ID se
asociará a los paquetes guardados en la BD. ej. --id 1
--organization-id ORGANIZATION_ID
El ID del recolector de datos. Este ID se
asociará a los paquetes guardados en la BD. ej. --id 1
--topics TOPICS [TOPICS ...]
Lista los temas a los que deseas suscribirte separados por
espacios. Si no se proporciona ninguno, el valor predeterminado será "#".
Argumentos requeridos:
--ip IP Dirección IP del broker MQTT, ej. --ip 192.168.3.101.
Este script recibe paquetes UDP del proxy UDP en el formato packet_forwarder de la puerta de enlace y los persiste.
Argumentos opcionales:
-h, --help muestra este mensaje de ayuda y sale
--collector-id COLLECTOR_ID
El ID del recolector de datos. Este ID se
asociará a los paquetes guardados en la BD. ej. --id 1
--organization-id ORGANIZATION_ID
El ID del recolector de datos. Este ID se
asociará a los paquetes guardados en la BD. ej. --id 1
Argumentos requeridos:
-n NAME, --name NAME Identificador de cadena único del recolector de datos. ej.
--name semtech_collector
-p PORT, --port PORT Puerto donde escuchar paquetes UDP. --port 1702.
Ejemplo:
Grabar datos entre una puerta de enlace que envía al puerto local 1700 y un servidor de red xserver que escucha en (localhost, 1701). Guardar los datos en el directorio ./.
python3 PacketForwarderCollector.py --name semtech_collector --port 1700
Este script lee desde un archivo o archivos o stdin y ejecuta diferentes subherramientas. Dependiendo de la opción seleccionada, puedes ejecutar un análisis del tráfico LoRaWAN, intentar forzar por fuerza bruta la AppKey, o analizar todos los paquetes recibidos. Estas opciones se pueden combinar.
Argumentos opcionales:
Este script recupera paquetes de la BD y ejecuta diferentes subherramientas.
Luego, cada subherramienta guardará los datos de salida en la BD. Consulta cada opción para
más información.
argumentos opcionales:
-h, --help muestra este mensaje de ayuda y sale
-a, --analyze Recopila y analiza diferentes aspectos del tráfico. Si
el forzador por fuerza bruta (-b) está activado, los resultados se
correlacionarán
-b, --bforce Intenta forzar por fuerza bruta las AppKeys con JoinRequests y
JoinAccepts payloads
-k KEYS, --keys KEYS [Forzador por fuerza bruta] Ruta del archivo de claves. Si no se proporciona,
se usará "bruteForcer/keys.txt"
--no-gen [Forzador por fuerza bruta] No generar claves, solo intentar claves desde
archivos
-p, --parse Analizar el PHYPayload en información legible
--from-id FROM_ID ID del paquete desde donde comenzar a procesar.
--to-id TO_ID Último ID de paquete a procesar.
Ejemplo:
Procesar paquetes en la BD comenzando desde el ID de paquete 1000, ejecutar un análisis de tráfico e intentar descifrar AppKeys proporcionadas en my-keys.txt, pero no generar más claves dinámicamente.
python3 LafProcessData.py -a -b -k my-keys.txt --no-gen --from-id 1000
Estos scripts proporcionan la funcionalidad orquestada por LafProcessData.py. A continuación, las alertas que son implementadas por LafPacketAnalysis.py y LafBruteForcer.py:
| ID | Título | Analizador | Nivel de riesgo | Descripción | Acción recomendada |
|---|---|---|---|---|---|
| LAF-001 | DevNonce repetido | LafPacketAnalysis.py | Bajo | Los DevNonces para cada dispositivo deberían ser suficientemente aleatorios para no colisionar. Si el mismo DevNonce se repite en muchos mensajes, se puede inferir que un dispositivo está bajo un ataque de repetición. Esto es, un atacante que capturó un JoinRequest y está intentando enviarlo nuevamente a la puerta de enlace. | Verificar cómo se generan los DevNonces: la función que los genera debe implementarse utilizando una biblioteca aleatoria. Además, debes asegurarte de que el servidor verifique los DevNonces históricos (deberían persistirse en la BD) para no aceptar un JoinRequest antiguo válido enviado previamente por el dispositivo y así generar una nueva sesión. |
| LAF-002 | DevEUIs compartiendo el mismo DevAddr | LafPacketAnalysis.py | Información | Dos dispositivos diferentes podrían haber recibido la misma DevAddr. Esto no es una amenaza de seguridad. | Si el dispositivo está activado por aire (OTAA): Verificar la lógica utilizada para asignar DevAddrs y asegurarse de que el servidor no asigne la misma DevAddr a diferentes dispositivos. Si el dispositivo está activado por personalización (ABP): Verificar la DevAddr configurada en el firmware de un dispositivo es única en la red LoRaWAN. |
| LAF-003 | Repetición de Join | Por hacer | Medio | Se detectó un paquete de solicitud de Join duplicado, lo que puede implicar que el servidor LoRaWAN está bajo un ataque de repetición. Esto es, un atacante que pudo haber capturado un paquete de solicitud de Join anterior y lo está enviando nuevamente al servidor LoRaWAN para intentar generar una nueva sesión. | Verificar cómo se generan los DevNonces: la función que los genera debe implementarse utilizando una biblioteca aleatoria. Además, debes asegurarte de que el servidor verifique los DevNonces históricos (deberían persistirse en la BD) para no aceptar un JoinRequest antiguo válido enviado previamente por el dispositivo y así generar una nueva sesión. |
| LAF-004 | Repetición de paquetes de datos de enlace ascendente | Por hacer | Medio | Se detectó un paquete de enlace ascendente duplicado, lo que puede implicar que el servidor LoRaWAN está bajo un ataque de repetición. Esto es, un atacante que pudo haber capturado un paquete de enlace ascendente (enviado por el dispositivo) y lo está enviando nuevamente al servidor LoRaWAN. | En dispositivos activados por aire (OTAA): Asegurarse de que las claves de sesión se regeneren después de cada reinicio del dispositivo o desbordamiento del contador para evitar cualquier efecto de este ataque. Con dispositivos activados por personalización (ABP) de LoRaWAN v1.0.*, no se puede hacer nada para prevenir un ataque de repetición excepto cambiar el dispositivo a OTAA. |
Este directorio proporciona un conjunto de envoltorios para la biblioteca https://github.com/brocaar/lorawan/, que está escrita en Golang. Estas funciones son implementadas por las herramientas.
Aquí encontrarás una serie de scripts destinados a automatizar diferentes tareas. Asegúrate de darles permiso de ejecución si es necesario (chmod +x tu_script para Linux/MacOS).
Configura fácilmente tu puerta de enlace y cambia sus canales con fines de sniffing. Para más información sobre cómo usarlos, puedes ver el readme en este directorio.
Este script se utiliza para instalar todos los paquetes de software necesarios en una Raspberry PI para construir una puerta de enlace LoRaWAN junto con un Concentrador LoRa conectado (iC980-SPI, RHF0M301-SPI, RAK831-SPI o cualquier otro mediante configuración manual).
Dado que no es posible saber en qué frecuencias operan los dispositivos LoRa, hemos creado un script que puede cambiar los canales de las puertas de enlace de las bandas de frecuencia US915 y EU868 con fines de sniffing. Aunque existen puertas de enlace profesionales y costosas que admiten 32 o 64 canales, la mayoría de las puertas de enlace admiten hasta 8 canales. Este script está diseñado para ejecutarse en este tipo de puertas de enlace.
Al menos en la banda de frecuencia US915, los primeros 8 canales son los más utilizados. Pero hay implementaciones conocidas que utilizan otro grupo de canales, como por ejemplo The Things Networks, que utiliza el segundo grupo (8-15) de canales para la comunicación de enlace ascendente.
Actualmente no admitimos otras bandas de frecuencia, pero con algunos cambios en estos scripts podrías hacerlo por tu cuenta :).
TODO
Este proyecto está licenciado bajo la licencia BSD-3-Clause.
| LAF-005 | Repetición de paquetes de datos de enlace descendente | Por hacer | Alto | Se detectó un paquete de enlace descendente duplicado. El servidor está respondiendo a un ataque de repetición o está generando tráfico atípico hacia los dispositivos. | Verificar los registros del servidor y comprobar que las acciones recomendadas anteriores están implementadas. |
| LAF-006 | Posible dispositivo ABP (reinicio de contador y sin Join) | LafPacketAnalysis.py | Alto | Si el contador se reinició (volvió a 0), la DevAddr se mantiene igual y no se detectó ningún proceso Join previo, puede implicar que el dispositivo está activado por personalización (ABP). No se recomienda la implementación de dispositivos ABP porque no se realiza ningún proceso de Join, lo que significa que las claves de sesión se mantienen igual para siempre. Un dispositivo que no cambia sus claves de sesión es propenso a diferentes ataques como escucha o repetición. | Todos los dispositivos activados por personalización (ABP) deben reemplazarse por dispositivos activados por aire (OTAA) si es posible. No se recomienda la implementación de dispositivos ABP. |
| LAF-007 | Contador recibido menor de lo esperado (distinto de 0) | LafPacketAnalysis.py | Medio | Si un atacante obtiene un par de claves de sesión (por haber robado la AppKey en dispositivos OTAA o la AppSKey/NwkSKey en dispositivos ABP), podría enviar datos falsos válidos al servidor. Para que el servidor acepte mensajes falsificados, se requiere que el FCnt (Contador de trama) del mensaje sea mayor que el FCnt del último mensaje enviado. En un escenario donde el dispositivo falsificado original sigue enviando mensajes, el servidor comenzaría a descartar mensajes (válidos) ya que tendrían un FCnt más pequeño. Por lo tanto, cuando se reciben mensajes con un valor de FCnt menor al esperado por el servidor LoRaWAN, es posible inferir que se estableció una sesión paralela. | Si el dispositivo es activado por aire (OTAA), cambiar su AppKey porque probablemente fue comprometida. Si está activado por personalización, cambiar su AppSKey y NwkSKey. Además, asegurarse de que el servidor LoRaWAN esté actualizado y no esté aceptando mensajes duplicados. |
| LAF-008 | Contraseña descifrada con JoinRequest | LafBruteforcer.py | Alto | Fue posible descifrar un mensaje JoinRequest utilizando una AppKey conocida. | Usar AppKeys diferentes a las proporcionadas por los proveedores o usar claves más aleatorias. |
| LAF-009 | Contraseña descifrada | LafBruteforcer.py | Alto | La AppKey del dispositivo se encontró probando con una cadena conocida o no aleatoria. Fue descifrada usando un par de mensajes de Join (Solicitud y Aceptación). | Usar un generador de claves aleatorio para la AppKey en lugar de usar las proporcionadas por los proveedores. Además, no establecer la misma AppKey para más de un dispositivo y no generar AppKeys usando una lógica predecible (ej. valores incrementales, invertir ciertos bytes, etc.). |
| LAF-010 | Cambio de ubicación de la puerta de enlace | LafPacketAnalysis.py | Medio | Si se supone que la puerta de enlace no debe cambiar su ubicación. Puede haber sido robada, movida, o una puerta de enlace falsa puede estar intentando suplantar a la puerta de enlace legítima. | Asegurarse de que la puerta de enlace no haya sido manipulada, tanto física como lógicamente. |