pihack@home:~$

Explotación XSS en servicio IoT

Antecedentes

Hoy en día, la tecnología se ha metido tanto en nuestras vidas que la utilizamos constantemente para tomar decisiones cotidianas. Decidimos si utilizar o no un abrigo en función de la información que nos proporciona nuestro widget meteorológico favorito en el móvil. Modificamos nuestra ruta para llegar a nuestro destino, si la aplicación de mapas nos indica que hay atasco en nuestro recorrido habitual. Y así con un sinfín de cosas.

Desafortunadamente, muchas de las webs o aplicaciones móviles que nos proporcionan información que hacen que modifiquemos nuestra conducta, no han sido auditadas desde el pu nto de vista de la ciberseguridad, dejando que la seguridad de aplicación dependa de las buenas prácticas del desarrollador.

Incluso habiendo hecho un trabajo impoluto, la seguridad del sistema también dependerá de las futuras vulnerabilidades que se descubran, y afecten a los distintos plugins que hacen funcionar a la aplicación o servicio online. Esto que hace que tarde o temprano todos los servicios que utilizamos en nuestro día a día van a ser vulnerables.

Objetivos

El objetivo de este proyecto es demostrar lo lejos que se puede llegar abusando de una vulnerabilidad de tipo XSS, provocada por un pequeño despiste durante el desarrollo del servicio.

Para ello, nos introduciremos en la piel del desarrollador del sistema IoT de climatización que se pondrá en producción sin haber sido auditado en busca de vulnerabilidades. Una vez que el producto está desplegado y el usuario está haciendo un uso normal del mismo, nos haremos pasar por un ciberdelicuente que explotará la vulnerabilidad XSS con la que podrá hacer un secuestro de la cookie de sesión del administrador y envenenar los datos que verá el usuario.

Resumen

Nuestra cliente, Alicia, regenta un hotel rural en el que quiere instalar una serie de sensores de temperatura que transmitan la información a la nube y pueda visualizar el estado de cada habitación desde su teléfono móvil. Gracias al histórico de datos podrá hacer un uso más eficiente de la energía para dar un buen servicio a sus clientes reduciendo el coste de la factura.

El sistema consta de una serie de dispositivos basados en la plataforma esp8266 con un sensor de temperatura que transmitirá la información mediante el protocolo MQTT usando la WIFI del hotel.

El usuario podrá registrar cuantos dispositivos desee y visualizar la información en la página web que diseñaremos para tal fin. Para alojar nuestro sistema utilizaremos la plataforma AWS, donde montaremos un servidor completo e instalaremos las aplicaciones y servicios necesarios. Además, registraremos un dominio que asociaremos a nuestra IP de AWS.

Durante el desarrollo introduciremos un error en el código del tipo XSS. Más adelante, durante la intrusión, nos registraremos en la plataforma como un usuario más. Daremos de alta nuestros propios dispositivos IoT, los cuáles simularemos con código en Python. En el proceso de registro nos daremos cuenta de que la web es vulne rable a XSS, por lo que abusaremos de esta vulnerabilidad para introducir código malicioso con el que conseguiremos la cookie de sesión del administrador del sistema. Esto nos permitirá gestionar todos los dispositivos que estén registrados. Entre ellos, los de nuestro objetivo, Alicia, la dueña del hotel.

Para llevar a cabo nuestro plan, haremos uso de un script en Python que enviará al servidor datos ficticios de un supuesto sensor de temperatura. Estos datos serán enviados mediante el protocolo MQTT y capturados por el servidor usando la tecnología Node JS, y almacenados en una BBDD MySQL.

Una vez tengamos los datos de temperatura ficticios almacenados en la base de datos, y utilizando la cuenta de administrador, cambiaremos el ID de usuario del dispositivo de nuestra víctima, por el ID de usuario del dispositivo ficticio. Esto hará que cuando nuestra víctima se conecte para comprobar la temperatura de las habitaciones, visualice datos que no son reales y piense que su hotel se está quemando.

AWS Deployment

En primer lugar nos registramos en AWS y lanzamos una instancia de tipo EC2.

aws

A continuación, escogemos el sistema operativo. En nuestro caso utilizaremos Ub untu 18.04 porque la versión 20 es incompatible con el broker EMQX.

aws

Escogemos tipo de instancia t2.micro que pertenece a la capa gratuita de AWS y es más que suficiente para las necesidades del servicio.

aws

En la sección de almacenamiento configuramos el disco duro SSD con 20 gigas.

aws

A continuación, configuramos un grupo de seguridad. Se pueden tener varios grupos de seguridad diferentes, cada uno con sus reglas de entrada y salida.

aws

Por último, revisamos y lanzamos la instancia.

aws

El siguiente paso es muy importante, ya que consiste en configurar el par de claves para acceder por ssh al sistema. Hay que recordar guardar muy bien la clave privada, ya que sin ella no podremos acceder al servidor.

aws

Por último, nos muestra un mensaje indicando que la instancia se ha lanzado exitosamente.

aws

Desde el Dashboard podemos ver que nuestra instancia está corriendo. Le asignamos un nombre.

aws

En el siguiente paso, asignaremos nuestra instancia a una ip elástica, de esta manera, podremos correr diferentes instancias sobre un mismo host. Simplemente reasignaríamos una nueva instancia a la ip elástica y así, nuestro dominio apuntaría a esa nueva instancia automáticamente.

aws

Asignamos una instancia.

aws

Asociamos la ip elástica a nuestra instancia.

aws

aws

Por último, vemos que en el dashboard ya aparece la ip elástica asociada a nuestra instancia.

aws

Es el momento de hacer una prueba para ver si nos podemos conectar por SSH a nuestra instancia. Para ello, buscamos la ip pública en el dashboard (13.36.212.15), abrimos nuestra consola, navegamos hasta donde tenemos la clave privada que generamos en un paso anterior y escribimos lo siguiente:

$ ssh -i pihackiotUbuntu.pem ubuntu@13.36.212.15

aws

Registro de dominio

Lo siguiente que tenemos que hacer es registrar un dominio. Usaremos namecheap que por un módico precio, nos permitirá utilizar un dominio tipo .XYZ. Nuestro dominio se llamará iothogar.xyz.

aws

Debemos configurarlo de tal forma que apunte a nuestra ip pública 13.36.212.15

aws

Ahora debemos esperar hasta que los servidores DNS ubicados en distintas regiones del mundo, actualizen sus registros y resuelvan correctamente nuestro dominio. Podemos visitar alguna web como dnschecker.org o whatsmydns.org para comprobar si ya lo han hecho.

Instalación de VestaCP

Ahora es el momento de instalar VestaCP, que es un panel de control que nos permitirá administrar nuestro servidor en AWS. Para ello, nos vamos a la web de vestacp y en la sección de instalación, nos vamos a instalación manual. Allí seleccionamos los servicios que queremos instalar y la web nos proporciona un comando de instalación.

aws

Nos vamos a nuestra consola, nos conectamos por ssh al servidor y lanzamos los comandos que nos ha generado la web de vesta.

En primer lugar lanzamos el curl para descargarnos el instalador y después lo ejecutamos, tal como nos indica.

aws

A continuación, seguimos las indicaciones de la pantalla y esperamos a que se instale todo.

aws

Una vez instalado, nos saldrá el siguiente mensaje:

aws

Ahora ya podremos acceder al panel de vesta, mediante el navegador web mediante el puerto 8083.

aws

Para loguearnos, el usuario y contraseña son los que indicamos en la web de vesta antes de que se generase el archivo de instalación.

Una vez dentro, llegamos al Dashboard, donde podremos configurar todo nuestro servidor.

aws

En primer lugar, vamos a crear un certificado SSL para navegar de forma segura. Este certificado nos lo otorga Let’s encrypt. Pulsamos clic sobre SSL Support y sobre Lets Encrypt support y guardamos. Antes de realizar este paso hay que cerciorarse de que los servidores DNS de todo el planeta resuelvan nuestro dominio correctamente.

aws

Una vez que tenemos el certificado, comprobamos que funciona introduciendo la dirección https://iothogar.xyz. Si aparece la pantalla de inicio es que ha funcionado.

Ahora podemos ligar el certificado con nuestro panel de control de Vesta. Para ello, desde la consola nos vamos a la dirección /usr/local/vesta/ssl#. Una vez ahí, borramos los certificados autogenerados de vesta y los sustituimos por los de Let’s Encrypt mediante un enlace simbólico.

# ln -s /home/admin/conf/web/ssl.$dominio.xyz.crt certificate.crt
# ln -s /home/admin/conf/web/ssl.\$dominio.xyz.key certificate.key

Es una buena práctica guardar una copia del certificado ssl por si se borrase de forma inesperada. Para ello:

# cp /home/admin/conf/web/ssl.$dominio.xyz.crt .../web/ssl.$dominio.xyz.crt.backup
# cp /home/admin/conf/web/ssl.$dominio.xyz.key .../web/ssl.$dominio.xyz.key.backup

Por último, reiniciamos el serivico de vesta: # service vesta restart

A continuación, creamos una cuenta FTP desde vesta, para poder subir todos nuestras archivos haciendo uso de este protocolo. Esto lo hacemos desde la sección WEB -> editar, tal como muestra la siguiente imagen.

aws

Después, modificamos el archivo /etc/vsftpd.conf para poder conectarnos al servidor utilizando FTP pasivo. En una conexión FTP pasivo, el cliente se conecta al servidor enviando la orden PASV, después el servidor le responde indicando el puerto por el que se va a realizar la comunicación. En nuestro caso, el rango de puertos para que se produzca esta conexión será entre 12000 y 12100. El archivo tiene que quedar así.

aws

Antes de conectarnos por ftp, necesitamos configurar el firewall, tanto de vesta como el propio de AWS. Empezaremos por el el de AWS. Aprovecharé esta ocasión para abrir todos los puertos que vamos a necesitar en el proyecto. Esto incluye los puertos del servicio EMQX, para la conexión MQTT, tanto directa como en websocket; MySQL también será otro de los servicios que usemos y necesitaremos abrir su puerto 3306.

aws

Lo mismo hacemos en Vesta, donde la configuración quedaría así

aws

Para realizar el desarrollo de la web utilizaremos el editor Atom y cargaremos todos nuestros archivos mediante un plugin que utilizará el protocolo FTP. El plugin se llama remoteFTP.

aws

Configuramos el archivo $HOME/.atom/.ftpconfig.

{
 protocol: ftp,
 host: iothogar.xyz,
 port: 21,
 user: user,
 pass: pass,
 promptForPass: false,
 remote: /,
 secure: false,
 secureOptions: null,
 connTimeout: 10000,
 pasvTimeout: 10000,
 keepalive: 10000,
 watch:[
	dist/stylesheets/main.css,
        dist/stylesheets/,
        dist/stylesheets/*.css
       ],
 watchTimeout:500
}

MQTT

Introducción

MQTT o Message Queue Telemetry Transport es un protocolo usado para la comunicación machine-to-machine en el Internet of Things. Es ampliamente utilizado en las comunicaciones entre sensores debido a que consume muy poco ancho de banda y puede ser utili zado por dispositivos con pocos recursos.

MQTT utiliza una arquitectura con topología de estrella, con un nodo central llamado broker que se encarga de gestionar la red y de transmitir los mensajes que publican los sensores. Para mantener viva la comunicación, los clientes envían un paquete (PINGREQ) periódicamente, mientras que el broker responde a dicho mensaje con un (PINGRESP). La comunicación puede ser cifrada.

mqtt Topología de red emqx. Imagen de “geekytheroy.com”

La comunicación se basa en una serie de topics o temas bajo los cuales publican los sensores y a los que se debe subscribir un nodo que quiera recibir dicha información. La comunicación puede ser uno a uno o uno a muchos. Los topics se representan con una cadena de caracteres con un orden jerárquico y separados por el caracter “/”.

mqtt Jerarquía en red MQTT. Imagenes de “geekytheroy.com”

Ejemplo: edificio1/planta3/habitacion1/temperatura, edificio1/planta3/#/temperatura

En el primer ejemplo un nodo se estaría subscribiendo al sensor de temperatura ubicado en la habitación1 de la planta3 del eficio1, mientras que en el ejemplo 2 el no do se estaría subscribiendo a todos los sensores de temperatura ubicados en la planta3 del edificio1. Asimismo el sensor de temperatura estaría publicando su valor bajo el tópico edificio1/planta3/habitacion1/temperatura.

Otra característica importante a comentar es la de la calidad del servicio o Quality of Service QoS. Los niveles de calidad de servicio determinan cómo se entrega cada mensaje MQTT y deben especificarse por cada mensaje.

El primer nivel es QoS = 0. En este nivel el cliente publica un mensaje confiando en que llegue a su destino pero sin cerciorarse de que lo hace. El segundo nivel es QoS = 1. En este nivel el cliente se cerciora de que el mensaje ha llegado al menos una vez. Esto es posible gracias a que el broker devuelve al cliente un mensaje indicando que ha recibido su publicación. El tercer y último nivel es QoS = 2. En este nivel el cliente se cerciora de que el mensaje ha llegado sólo una vez. Se utiliza en los casos en los que suponga un problema que un mensaje llegue de forma duplicada.

Cuanto mayor es la calidad de servicio, más se sobrecarga la red y menor rendimiento tiene el sistema.

mqtt QoS

En este proyecto haremos uso del broker EMQx.

Instalación de EMQx

EMQx es un broker open source con una versión gratuita que es más que suficiente para los fines de este proyecto. Para instalarlo seguiremos los siguientes pasos.

mqtt Instalación de EMQx

Una vez instalado, y antes de iniciar el servicio, hay que configurar dos cosas. Por un lado, el servicio de websocket de emqx utiliza por defecto el mismo puerto que VestaCP, el puerto 8083. Por ello, lo modificaremos para que no entren en conflicto. Esto se hace en el archivo etc/emqx/emqx.conf. Ahí buscamos el puerto 8083 y lo cambiamos por el 8093. Hacemos lo mismo con el puerto 8084 y lo cambiamos por el 8094, que es el que utiliza web socket secure.

Además, EMQx tiene una serie de plugins que nos puede facilitar la vida a la hora de gestionar el servicio. Tal es el caso del plugin Dashboard, que nos habilita un panel de control donde gestionar multitud de cosas. Acepta tanto peticiones http como https, pero esta última requiere habilitarla en su archivo de configuración. Dicho archivo se encuentra en la ruta etc/eqmx/plugins/emqx_dashboard.conf. Una vez dentro buscamos la sección https y vamos descomentando las líneas que necesitemos.

Otro archivo de configuración es etc/emqx/plugins/emqx_management.conf, asociado al plugin Management. Aquí tenemos que descomentar las líneas de conexión https.

Tanto en emqx_dashboard como en emqx_management, en la parte de https, tenemos que comprobar la ruta que tienen configurada para acceder al certificado ssl. Es probable que haya que modificarlo a etc/emqx/certs/cert.pem y cert.key.

A continuación se muestra un resumen de los puertos relacionados con el broker EMQx y que tendremos que abrir tanto en Vesta como en AWS.

emqx.conf

listener.tcp.external = 0.0.0.0:1883
listener.ws.external = 8083 cambio por 8093 (por conflicto con vesta)
listener.wss.external = 8084 cambio por 8094
listener.ssl.external = 8883
listener.tcp.internal = 11883

emqx_management.conf

management.listener.http = 8080 cambio por 8090
management.listener.https  =  8081 cambio por 8091

emqx_dashboard.conf

dashboard.listener.http = 18083
dashboard.listener.https = 18084

A continuación, copiamos los certificados SSL en la ruta de EMQx /etc/emqx/certs/. Los renombramos por cert.pem y key.pem. Cambiamos el grupo y propietario de los nuevos archivos, ya que si no, el servicio no podrá leerlos y no funcionará. Ante cualquier duda se puede consultar el log en /var/log/emqx/emqx.log.1.

Certificado SSL en EMQx

# cp /home/admin/conf/web/ssl.$dominio.crt /etc/emqx/certs/cert.pem
# cp /home/admin/conf/web/ssl.\$dominio.key /etc/emqx/certs/key.pem
# chown emqx:emqx cert.pem
# chown emqx:emqx key.pem

Por último, lanzamos el servicio con service emqx start.

Si ahora nos vamos a la dirección http://iothogar.xyz:18083 debería aparecer una pantalla de login, cuyas credenciales son admin/public.

mqtt Dashboard

Vamos a mostrar un ejemplo de cómo funciona MQTT, mediante el plugin WebSocket que proporciona EMQx. En el panel lateral hacemos clic en la sección de WebSocket y nos aparece un panel de configuración en el que modificaremos el puerto de comunicación por defecto y pondremos el 8093. Pulsamos sobre conectar y el plugin nos proveerá de una conexión no cifrada mediante websocket.

mqtt Conexión WebSocket

A continuación vamos a subscribirnos a un determinado topic, llamado “pepito”, con un QoS de 0. Esto es que no se verificará que el mensaje se ha entregado.

mqtt Subscripción a un topic

Por último, vamos publicar dos mensajes. Uno bajo el tópico “pepito” y otro bajo el tópico “juanito”. En la parte inferior izquierda veremos los mensajes que se han publicado, mientras que en la parte inferior derecha se muestran los mensajes que se han recibido.

mqtt Publicación de mensajes

Como podemos observar, sólo se han recibido los mensajes publicados bajo el tópico “pepito”.

A continuación, vamos a crear la BBDD con MySQL.

Base de datos MySQL

Desde Vesta CP podemos crear la BBDD.

mqtt Creación Base de Datos

Una vez creada, nos vamos a PHPMyAdmin y creamos las tablas de users y estaciones

Creación tabla users

CREATE TABLE `admin_masteriot`.`users`(
                      `users_id` INT NOT NULL AUTO_INCREMENT,
                      `users_date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                      `users_email` VARCHAR(60) NOT NULL,
                      `users_username` VARCHAR(60) NOT NULL,
                      `users_password` VARCHAR(60) NOT NULL,
                      PRIMARY KEY (`users_id`)) ENGINE = INNODB;

Creación de la tabla estaciones

CREATE TABLE `admin_masteriot`.`estaciones`(
                      `estaciones_id` INT NOT NULL,
                      `estaciones_date` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
                      `estaciones_serie` VARCHAR(45) NULL,
                      `estaciones_alias` VARCHAR(45) NULL,
                      `estaciones_user_id` INT NULL,
                      PRIMARY KEY(`estaciones_id`),
                      INDEX `fk_estaciones_1_idx`(`estaciones_user_id` ASC),
                      CONSTRAINT `fk_estaciones_1`
                      FOREIGN KEY (`estaciones_user_id`)
                      REFERENCES `admin_masteriot`.`users`(`users_id`)
                      ON DELETE CASCADE
                      ON UPDATE CASCADE);

Creación de la tabla mediciones

CREATE TABLE `admin_masteriot`.`mediciones`(
                      `mediciones_id` INT NOT NULL,
                      `mediciones_date` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
                      `mediciones_serie` VARCHAR(45) NULL,
                      `mediciones_alias` VARCHAR(45) NULL,
                      `mediciones_estaciones_id` INT NULL,
                      PRIMARY KEY(`mediciones_id`),
                      INDEX `fk_mediciones_1_idx`(`mediciones_estaciones_id` ASC),
                      CONSTRAINT `fk_mediciones_1`
                      FOREIGN KEY (`mediciones_user_id`)
                      REFERENCES `admin_masteriot`.`estaciones`(`estaciones_id`)
                      ON DELETE CASCADE
                      ON UPDATE CASCADE);

La tabla estaciones tiene un campo foráneo ligado al id del usuario.

Podemos comprobar que todo se ha creado correctamente conectándonos a la BBDD y tratando de guardar un registro en la misma.

mqtt Conexión con BBDD usando mycli

mqtt Inserción de registro en tabla user

Para tener mayor garantía de seguridad, se podría implementar un control de acceso para el protocolo MQTT basado en ACL, Access Control List. Este método permite establecer reglas de acceso tanto a los usuarios como a los dispositivos. En nuestro caso vamos a prescindir de ello, puesto que sobrepasa las intenciones de este proyecto. Por no hablar de que tampoco sería útil para el tipo de vulnerabilidad que vamos a explotar.

Diseño WEB

En esta sección vamos a implementar la página web que utilizarán nuestros clientes. En lugar de desarrollar una desde cero, vamos a descargar alguna disponible gratuitamente en la web, la cual modificaremos a nuestro gusto para obtener el resultado esperado.

En concreto vamos a utilizar la versión Lite de Amper Admin, de Wrappixel.

Registro

A continuación, mostramos el código PHP de la página de registro.php

<?php
                  $conn = mysqli_connect("localhost","admin_masteriot","j65966298","admin  _masteriot");
                  if ($conn==false){
                          echo "Error al conectarse a la BBDD";
                          die();
                  }
  
                  $email = "";
                  $username = "";                                                        
                  $password = "";                                                        
                  $password_r = "";                                                      
                  $msg = "";                                                             
                                                                                         
                  if (isset($_POST['email']) && isset($_POST['username']) && isset($_POST['password']) && isset($_POST['password_r')){
                          $username = strip_tags($_POST['username']);
                          $email = strip_tags($_POST['email']);
                          $password = strip_tags($_POST['password']);
                          $password_r = strip_tags($_POST['password_r']);
  
                          if ($password==$password_r){
                                  $result = $conn->query("SELECT * FROM users WHERE users_email = '". $email."'");
                                  $users = $result->fetch_all(MYSQLI_ASSOC);
                                  $count = count($users);
  
                                  if($count==0){
                                          $password = sha1($password);
					  $conn->query("INSERT INTO users (users_email,users_password,users_username) VALUES ('".$email."','".$password."','".$username."');");
                                          $msg.="Usuario creado correctamente. Puede ingresar haciendo clic aqui";
                                  }else{
                                          $msg.="email no valido";
                                  }
                          }else{
                                  $msg = "Rellene correctamente el formulario";
                                  echo($msg);
                          }
                  }
          ?>


Así como, una captura de la pantalla.

web Register.php

Login

A continuación, mostramos el código PHP de login.php

<?php
                          session_start();
                          $_SESSION['logged'] = false;
                                                                                         
                          $msg="";                                                       
                          $username="";                                                  
                                                                                         
                          if(isset($_POST['username']) && isset($_POST['password'])){    
  
                            if($_POST['username']==""){
                              $msg.="Debe ingresar un email";
                            }else if ($_POST['password']==""){
                              $msg.="Debe ingresar un password";
                            }else{
                              $username = strip_tags($_POST['username']);
                              $password = sha1(strip_tags($_POST['password']));
                          
                              $conn = mysqli_connect("localhost","admin_masteriot","j6596  6298","admin_masteriot");
          
                              if ($conn==false){
                                echo "Error al conectarse a la BBDD";
                                die();
                              }
                          
                              $result = $conn->query("SELECT * FROM `users` WHERE `users_  username` = '".$username."' AND `users_password` = '".$password."' ");
                              $users = $result->fetch_all(MYSQLI_ASSOC);
			      $count = count($users);
  
                              if ($count == 1){
                                $_SESSION['users_id'] = $users[0]['users_id'];
                                $_SESSION['users_username'] = $users[0]['users_username']  ;
  
                                $_SESSION['logged'] = true;
                                echo '<meta http-equiv="refresh" content="1; url=dashboar  d.php">';
  
                              }else{
                                $msg.="Acceso denegado";
                                $_SESSION['logged']=false;
                              }
                            }
                          }
                          
                         ?>

Mostramos la captura del login.php

web login.php

Devices

Una vez iniciada sesión, podemos añadir los dispositivos a utilizar, introduciendo su número de serie y alias. Esto lo haremos en devices.php.

<?php
                          session_start();
                          $logged = $_SESSION['logged'];
  
                          if(!$logged){
                          echo "Ingreso no autorizado";
                                  die();
                          }
  
                          $conn = mysqli_connect("localhost","admin_masteriot","password","admin_masteriot");
  
                          if ($conn==false){
                                  echo "Error al conectarse a la BBDD";
                                  die();
                          }
  
                          $alias="";
                          $serie="";
                          $user_id=$_SESSION['users_id'];
                          $msg="";
  
                          $estaciones_result = $conn->query("SELECT * FROM `estaciones` WHERE `estaciones_user_id`=".$user_id);
                          $estaciones = $estaciones_result->fetch_all(MYSQLI_ASSOC);
  
                          if (isset($_POST['serie']) && isset($_POST['alias'])) {
			  $alias = $_POST['alias'];
                                  $serie = $_POST['serie'];
  
                                  $conn -> query("INSERT INTO `estaciones` (`estaciones_s  erie`, `estaciones_alias`,`estaciones_user_id`) VALUES ('".$serie."', '".$alias."', '".$user_id."');");
                                  $msg = "usuario: ". $user_id . " - alias: " . $alias . " agregado con exito";
                                  if ($conn == true){
                                          echo($msg);
                                  } else{
                                          $msg .= "error - " . $exito . " - " . "alias -   " . $alias . " serie - " . $serie . "user_id - " . $user_id;
                                  }
  
                                  $estaciones_result = $conn->query("SELECT * FROM `estaciones` WHERE `estaciones_user_id` = ".$user_id);
                                  $estaciones = $estaciones_result->fetch_all(MYSQLI_ASSOC);
                          }
                  ?>

web Devices

Dashboard

En el Dashboard se mostrarán los valores de temperatura que envíen los dispositivos, así como una tabla que mostrará el histórico reciente de los valores.

web Dashboard

En este fichero tenemos que insertar el código JavaScript que se encargará de leer la información publicada por el dispositivo y lo mostrará en el navegador. Lo haremos a través de un WebSocket que, mediante la librería https://unpkg.com/mqtt/dist/mqtt.min.js, se subscribirá a las publicaciones del dispositivo. Además, se mostrará un histórico de las mediciones en formato tabla.

<?php
    session_start();
    $logged = $_SESSION['logged'];
    $user_id = $_SESSION['users_id'];
    $usuario = "javier";
  
    if(!$logged){
      echo "Ingreso no autorizado";
      //die();
      echo '<meta http-equiv="refresh" content="1; url=login.php">';
  
    }
  
    $conn = mysqli_connect("localhost","admin_masteriot","j65966298","admin_masteriot");
  
    if ($conn==false){
      echo "Error al conectarse a la BBDD";
      die();
    }
  
          $estaciones_result = $conn->query("SELECT * FROM `estaciones` WHERE `estaciones_user_id`=" . $user_id);
          $estaciones = $estaciones_result->fetch_all(MYSQLI_ASSOC);
  
          $consulta = "SELECT estaciones.estaciones_serie, mediciones.mediciones_date, mediciones.mediciones_valor from mediciones inner join estaciones ON estaciones.estacione  s_id=mediciones.mediciones_estaciones_id inner join users on users.users_id=estaciones.estaciones_user_id and users.users_id=" . $user_id . " ORDER BY DESC LIMIT 20";
  
    $mediciones_result = $conn->query($consulta);
    $mediciones = $mediciones_result->fetch_all(MYSQLI_ASSOC);
  
  ?>

	<!DOCTYPE html>
	  <html dir="ltr" lang="en">
  
  <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <!-- Tell the browser to be responsive to screen width -->
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <meta name="keywords"
          content="wrappixel, admin dashboard, html css dashboard, web dashboard, bootstrap 4 admin, bootstrap 4, css3 dashboard, bootstrap 4 dashboard, Ample lite admin bootst  rap 4 dashboard, frontend, responsive bootstrap 4 admin template, Ample admin lite dash  board bootstrap 4 dashboard template">
      <meta name="description"
          content="Ample Admin Lite is powerful and clean admin dashboard template, inpir  ed from Bootstrap Framework">
      <meta name="robots" content="noindex,nofollow">
      <title>Ample Admin Lite Template by WrapPixel</title>
      <link rel="canonical" href="https://www.wrappixel.com/templates/ample-admin-lite/"   />
      <!-- Favicon icon -->
      <link rel="icon" type="image/png" sizes="16x16" href="plugins/images/favicon.png">
      <!-- Custom CSS -->
      <link href="plugins/bower_components/chartist/dist/chartist.min.css" rel="styleshee  t">
      <link rel="stylesheet" href="plugins/bower_components/chartist-plugin-tooltips/dist  /chartist-plugin-tooltip.css">
      <!-- Custom CSS -->
      <link href="css/style.min.css" rel="stylesheet">
  </head>
  
  <body>
      <!-- ============================================================== -->
      <!-- Preloader - style you can find in spinners.css -->
      <!-- ============================================================== -->
	<div class="preloader">
	<div class="lds-ripple">
	<div class="lds-pos"></div>
	<div class="lds-pos"></div>
	</div>
	</div>
	<!-- ============================================================== -->
      <!-- Main wrapper - style you can find in pages.scss -->
      <!-- ============================================================== -->
      <div id="main-wrapper" data-layout="vertical" data-navbarbg="skin5" data-sidebartyp  e="full"
          data-sidebar-position="absolute" data-header-position="absolute" data-boxed-lay  out="full">
          <!-- ============================================================== -->
          <!-- Topbar header - style you can find in pages.scss -->
          <!-- ============================================================== -->
          <!-- asdfasdfsadfasd -->
          <header class="topbar" data-navbarbg="skin5">
              <nav class="navbar top-navbar navbar-expand-md navbar-dark">
                  <div class="navbar-header" data-logobg="skin6">
                      <!-- ==============================================================   -->
                      <!-- Logo -->
                      <!-- ==============================================================   -->
                      <a class="navbar-brand" href="dashboard.php"></a>
                      <a class="nav-toggler waves-effect waves-light text-dark d-block d-  md-none"
                          href="javascript:void(0)"><i class="ti-menu ti-close"></i></a>
                  </div>
		<!-- ============================================================== -->
                  <!-- End Logo -->
                  <!-- ============================================================== -->
                  <div class="navbar-collapse collapse" id="navbarSupportedContent" data-  navbarbg="skin5">
                      <ul class="navbar-nav d-none d-md-block d-lg-none">
                          <li class="nav-item">
                              <a class="nav-toggler nav-link waves-effect waves-light tex  t-white"
                                  href="javascript:void(0)"><i class="ti-menu ti-close"><  /i></a>
                          </li>
                      </ul>
                      <!-- ==============================================================   -->
                      <!-- Right side toggle and nav items -->
                      <!-- ==============================================================   -->
                      <ul class="navbar-nav ml-auto d-flex align-items-center"></ul>
                  </div>
              </nav>
          </header>
          <aside class="left-sidebar" data-sidebarbg="skin6">
              <!-- Sidebar scroll-->
              <div class="scroll-sidebar">
                  <!-- Sidebar navigation-->
                  <nav class="sidebar-nav">
                      <ul id="sidebarnav">
                          <!-- User Profile-->
                          <li class="sidebar-item pt-2">
                              <a class="sidebar-link waves-effect waves-dark sidebar-link  " href="dashboard.html"
                                  aria-expanded="false">
				<i class="far fa-clock" aria-hidden="true"></i>
                                  <span class="hide-menu">Dashboard</span>
                              </a>
                          </li>
                          <li class="sidebar-item"> <a class="sidebar-link waves-effect w  aves-dark sidebar-link"
                                  href="devices.php" aria-expanded="false">
                                  <i class="fa fa-user" aria-hidden="true"></i><span clas  s="hide-menu">Dispositivos</span></a>
                          </li>
                          <?php if($_SESSION['users_username'] === "admin"): ?>
                            <li class="sidebar-item"> <a class="sidebar-link waves-effect   waves-dark sidebar-link"
                                    href="devices_list_admin.php" aria-expanded="false">
                                    <i class="fa fa-user" aria-hidden="true"></i><span cl  ass="hide-menu">Listado Dispositivos</span></a>
                            </li>
                            <?php endif; ?>
                          <li class="sidebar-item"> <a class="sidebar-link waves-effect w  aves-dark sidebar-link"
                                  href="logout.php" aria-expanded="false"><i class="fa fa  -table"
                                      aria-hidden="true"></i><span class="hide-menu">Cerr  ar sesion</span></a>
                          </li>
                      </ul>
  
                  </nav>
                  <!-- End Sidebar navigation -->
              </div>
              <!-- End Sidebar scroll-->
          </aside>
          <div class="page-wrapper">
              <!-- ============================================================== -->
		<!-- Bread crumb and right sidebar toggle -->
              <!-- ============================================================== -->
              <div class="page-breadcrumb bg-white">
                  <div class="row align-items-center">
                      <div class="col-lg-3 col-md-4 col-sm-4 col-xs-12">
                          <h4 class="page-title text-uppercase font-medium font-14">Dashb  oard</h4>
                      </div>
                      <div class="col-lg-9 col-sm-8 col-md-8 col-xs-12">
                          <div class="d-md-flex">
                              <ol class="breadcrumb ml-auto">
                                  <i class="fas fa-user" aria-hidden="true"></i><span cla  ss="hide-menu"><?php echo($_SESSION['users_username']) ?></span></a>
                              </ol>
                          </div>
                      </div>
                  </div>
                  <!-- /.col-lg-12 -->
              </div>
              <div class="container-fluid">
                  <!-- ============================================================== -->
                  <!-- Three charts -->
                  <!-- ============================================================== -->
                    <div class="row justify-content-center">
                      <?php foreach ($estaciones as $estacion) {?>
                        <div class="col-lg-4 col-sm-6 col-xs-12">
                            <div class="white-box analytics-info">
                                <h3 class="box-title"><?php echo $estacion['estaciones_se  rie'] ?></h3>
                                <ul class="list-inline two-part d-flex align-items-center   mb-0">
                                 <li>
                                        <div><canvas width="67" height="30"
                                                style="display: inline-block; width: 67px  ; height: 30px; vertical-align: top;"></canvas>
                                        </div>
                                    </li>
                                    <li class="ml-auto"><span id="<?php echo $estacion['e  staciones_serie'] ?>" class="counter text-success">--</span></li>
                                </ul>
                            </div>
                        </div>
                        <?php } ?>
  
  
                      </div>
  
  
                      <div class="row justify-content-center">
                        <div class="col-sm-12">
                            <div class="white-box">
                                <div class="table-responsive">
                                    <table class="table">
                                        <thead>
                                            <tr>
                                                <th class="border-top-0">Estacion_serie</  th>
                                                <th class="border-top-0">Fecha</th>
                                                <th class="border-top-0">Temperatura</th>
                                            </tr>
                                        </thead>
                                        <tbody>
                                          <?php foreach ($mediciones as $medicion) {?>
                                            <tr>


                                              <td><?php echo $medicion['estaciones_seri  e'] ?></td>
                                                <td><?php echo $medicion['mediciones_date  '] ?></td>
                                                <td><?php echo $medicion['mediciones_valo  r'] ?></td>
                                            </tr>
                                          <?php }?>
                                        </tbody>
                                    </table>
                                </div>
                            </div>
                          </div>
                      </div>
  
      <script src="plugins/bower_components/chartist-plugin-tooltips/dist/chartist-plugin  -tooltip.min.js"></script>-->
  
      <script src="js/pages/dashboards/dashboard1.js"></script>
  
	  </body>
  
	  </html>

	<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
	<script type="text/javascript">
  
          function process_msg(topic, message){
                  var msg = message.toString();
                  $("#"+topic).html(msg);
          }
  
          // Connection options
          const options = {
  
                  clean: true,
                  connectTimeout: 4000,
                  clientId: 'emqx156javascript',
                  username: '',
                  password: '',
                  keepalive: 60,
  
          }
  
          
	  /**********************
                  Conexion
          **********************/
          const WebSocket_URL = 'wss://iothogar.xyz:8094/mqtt'
          const client = mqtt.connect(WebSocket_URL, options)
  
          client.on('connect', () => {
                  console.log('Mqtt conectado por WS con exito!')
                  <?php foreach ($estaciones as $estacion) {?>

                       client.subscribe('<?php echo $estacion['estaciones_serie'] ?>',   {qos: 0},(error) => {})
                  <?php } ?>
          })
  
          client.on('reconnect', (error) => {
                  console.log('reconnecting:', error)
          })
  
          client.on('error', (error) => {
                  console.log('Connection failed:', error)
          })
  
          client.on('message', (topic, message) => {
                  console.log('mensaje - ',message.toString(),'recibido segun el topico',topic)
                  process_msg(topic,message);
          })

Listado de dispositivos

El administrador del sistema tendrá una sección extra donde podrá gestionar todos los dispositivos de los usuarios. De esta manera podrá solucionar rápidamente algunos de los problemas que pudieran tener sus cliente, como modifiar el número de serie o el alias de un dispositivo.

web Devices

Para modificar los campos de una tabla de manera individual contamos con una libreria de jQuery que nos facilita esta opción. En concreto, utilizamos la librería de NicolasCARPi

Para utilizarla necesitamos incorporar el siguiente script:

	<script src="js/jquery.jeditable.min.js"></script>
	<script src="js/custom.js"></script>

A la hora de renderizar la tabla, se hace de la siguiente manera:

	<div class="table-responsive">
                          <table id="editableTable" class="table">
                                  <thead>
                                          <tr>
                                                  <th class="border-top-0">Id</th>
                                                  <th class="border-top-0">User_id</th>
                                                  <th class="border-top-0">Serie</th>
                                                  <th class="border-top-0">Alias</th>
                                                  <th class="border-top-0">Fecha</th>
                                          </tr>
                                  </thead>
                                  <tbody>
                                          <?php foreach ($devices as $device) {?>
                                                  <tr>
                                                          <td><?php echo $device['estaciones_id'] ?></td>
                                                          <td><span id="estaciones_user_id" data-id="<?php echo $device['estaciones_user_id'] ?>" class="edit"><?php echo $device['estaciones_user_id'] ?></span></td>
                                                          <td><span id="estaciones_serie" data-id="<?php echo $device['estaciones_user_id'] ?>" class="edit"><?php echo $device['estaciones_serie'] ?></span></td>
                                                          <td><span id="estaciones_alias" data-id="<?php echo $device['estaciones_user_id'] ?>" class="edit"><?php echo $device['estaciones_alias'] ?></span></td>
                                                          <td><?php echo $device['estaciones_date'] ?></td>
                                                          <td><a href="delete.php?id=<?php echo $data['id']; ?>">Delete</a></td>
                                                  </tr>
                                          <?php } ?>
                                  </tbody>
                          </table>
	</div>

El código php incrustado en el renderizado de la tabla llama a la función edit que se encuentra en el archivo custom.js. También vamos añadir otro cógido para modificar el correo electrónico de la tabla de usuarios.

		$(".edit").editable('action.php', {
                          data: $(this).attr("data-id"),
                          submitdata : function (value, settings) {
                                  var estaciones_user_id = $(this).attr("data-id");
                                  var action = 'edit';
                                  return {
                                          "estaciones_user_id": estaciones_user_id,
                                          "action": action
                                  }
                          }
                  });
  
                $(".edituser").editable('action.php', {
                          data: $(this).attr("data-id"),
                          submitdata : function (value, settings) {
                                  var users_id = $(this).attr("data-id");
                                  var action = 'edituser';
                                  return {
                                          "users_id": users_id,
                                          "action": action
                                  }
                          }
                  });

Esta función envía el id y la acción que se desea ejecutar al archivo action.php que se encargará de ejecutarlo sobre la base de datos.

<?php
                          session_start();
                          $logged = $_SESSION['logged'];
  
                          if(!$logged || $_SESSION['user_id' != 5]){
                                  echo "Ingreso no autorizado";
                                  die();
                                  echo '<meta http-equiv="refresh" content="1; url=login.  php">';
                          }
  
                          $conn = mysqli_connect("localhost","admin_masteriot","password","admin_masteriot");
                          if ($conn==false){
                                  echo "Error al conectarse a la BBDD";
                                  die();
                          }
                          if ($_POST['action'] == 'edit' && $_POST['estaciones_user_id'])   {
                                  $updateField='';
                                  if(isset($_POST['estaciones_user_id'])) {
                                          $updateField.= "estaciones_user_id='".$_POST['estaciones_user_id']."'";
                                  } else if(isset($_POST['estaciones_serie'])) {
                                          $updateField.= "estaciones_serie='".$_POST['estaciones_serie']."'";
                                  } else if(isset($_POST['estaciones_alias'])) {
                                          $updateField.= "estaciones_alias='".$_POST['estaciones_alias']."'";
                                  }
                                  if($_POST['id']) {
                                          $sqlQuery = "UPDATE estaciones SET ".$_POST['id  ']." = '".$_POST['value']."' WHERE estaciones_user_id='" . $_POST['estaciones_user_id'] . "'";
                                          mysqli_query($conn, $sqlQuery) or die("database error:". mysqli_error($conn));
                                          $data = array(
                                                  "message" => "Record Updated",
                                                  "status" => 1
                                          );
                                          echo $_POST['value'];
                                  }
                          }
  
                          if ($_POST['action'] == 'edituser'){
                                  $updateField='';
                                  if(isset($_POST['users_email'])) {
                                          $updateField.="users_email='".$_POST['users_email']."'";
					  }
                                  if($_POST['id']) {
                                          $sqlQuery = "UPDATE users SET ".$_POST['id']."   = '".$_POST['value']."' WHERE users_id='" . $_POST['users_id']."'";
                                          mysqli_query($conn, $sqlQuery) or die("database   error: ".mysqli_error($conn));
                                          $data = array(
                                                  "message" => "Record Updated",
                                                  "status" => 1
                                          );
                                          echo $_POST['value'];
                                  }
                          }
  
                          if ($_POST['action'] == 'delete' && $_POST['estaciones_id']) {
                                  $sqlQuery = "DELETE FROM estaciones WHERE estaciones_id  ='" . $_POST['estaciones_id'] . "'";
                                  mysqli_query($conn, $sqlQuery) or die("database error:"  . mysqli_error($conn));
                                  $data = array(
                                          "message"       => "Record Deleted",
                                          "status" => 1
                                  );
                                  echo json_encode($data);
                          }
                  ?>


Dispositivo IoT

ESP8266

Para realizar el prototipo de nuestro sistema IOT contamos con un dispositivo ESP8266. El ESP8266 es una familia de chips SoC de bajo costo y consumo de energía, con la peculiaridad de que integran un módulo WiFi y un módulo Bluetooth.

Device

En esta sección vamos a programarlo para simular un sistema de control de temperatura que envíe la información al servidor, haciendo uso del protocolo MQTT. Utilizaremos, para ello, el IDE de Arduino.

Antes de programar la tarjeta, hay que instalar algunas librerías en el IDE de Arduino:

***\*https://github.com/espressif/arduino-esp32***
***\*python2***
***\*pyserial***
***\*esptool***
***\*En el gestor de tarjetas instalar: esp8266 community huzzah***

Dependencias ESP8266

A continuación, mostramos el código con el que programamos el ESP8266.

	  #include <PubSubClient.h>
          #include <ESP8266WiFi.h>
          #include <Hash.h>
          #include <Arduino.h>
  
          char* ssid = "tuwifi";
          char* password = "tupassword";
  
          const char *mqtt_server = "iothogar.xyz";
          const int mqtt_port = 1883;
          char *mqtt_pass="";
          char *mqtt_user="";
  
          WiFiClient espClient;
          PubSubClient client(espClient);
  
          long lastMsg = 0;
          char msg[25];
          char msg101[25];
          char msg102[25];
          char msg103[25];
  
          void setup_wifi();
          void callback(char* topic, byte* payload, unsigned int length);
          void reconnect();
  
          void setup() {
                  // put your setup code here, to run once:
                  pinMode(BUILTIN_LED, OUTPUT);
                  Serial.begin(115200);
                  randomSeed(micros());
                  setup_wifi();
                  client.setServer(mqtt_server, mqtt_port);
                  client.setCallback(callback);
		  }
  
          void loop() {
                  // put your main code here, to run repeatedly:
                  if (!client.connected()){
                          reconnect();
                  }
  
                  //client.loop();
                  long now = millis();
    
                  if (now - lastMsg > 5000){
                  lastMsg = now;
                  temperatura++;
      
                  String to_send_h101 = String(temperatura);
                  to_send_h101.toCharArray(msg101,25);
      
                  String to_send_h102 = String(temperatura+1);
                  to_send_h102.toCharArray(msg102,25);
  
                  String to_send_h103 = String(temperatura+2);
                  to_send_h103.toCharArray(msg103,25);
      
                  Serial.print("Publicamos mensaje -> ");
                  Serial.println("h101", msg101);
                  Serial.println("h102", msg102);
                  Serial.println("h103", msg103);
      
                  client.publish("h101", msg101);
                  client.publish("h102", msg102);
                  client.publish("h103", msg103);
                  }
          }
  
          void setup_wifi(){
                  delay(10);
  
                  Serial.println();
                  Serial.print("Conectando a ");
                  Serial.println(ssid);
  
                  WiFi.begin(ssid, password);
  
                  while (WiFi.status() != WL_CONNECTED){
                          delay(500);
                          Serial.print(".");
		  }
                  Serial.println("");
                  Serial.println("Conectado a red WiFi!");
                  Serial.println("Direccion IP: ");
                  Serial.println(WiFi.localIP());
          }
  
          void callback(char* topic, byte* payload, unsigned int length){
                  String incoming = "";
                  Serial.println("Mensaje reicibido desde -> ");
                  Serial.print(topic);
                  Serial.println("");
  
                  for (int i=0; i<length; i++){
                          incoming += (char)payload[i];
                  }
  
                  incoming.trim();
                  Serial.println("Mensaje -> " + incoming);
          }
  
          void reconnect(){
    
                  while(!client.connected()){
                          Serial.print("Intentando conexion Mqtt...");
                          String clientId = "esp8266_";
                          clientId += String(random(0xffff), HEX); 
              
                          if(client.connect(clientId.c_str(), mqtt_user, mqtt_pass)){
                                  Serial.println("Conectado!");
  
                          }else{
                                  Serial.println("fallo: (con error -> ");
                                  //Serial.print(client.state());
                                  Serial.println(" Intentamos de nuevo en 5 segundos");
                                  delay(5000);
                          }
                  }
    
          }


emqx.ino

Una vez cargado el programa en la placa ESP8266 navegamos hasta el dashboard y vemos lo siguiente:

emqx Dashboard con datos del dispositivo

¡Nuestro dispositivo funciona! Enviamos datos correctamente al broker y el websoket recibe la información.

A continuación, instalaremos NodeJS en el servidor y se encargará de guardar los valores en la base de datos MySQL.

NodeJS

NodeJS es un entorno de ejecución para JavaScript construido con el motor de JavaScript V8. Este servicio nos vendrá muy bien para poder recibir la información de broker y guardarla en la base de datos MySQL en tiempo real. En primer lugar necesitamos instalar node y su gestor de paquetería npm.

apt install node
apt install npm
npm install mqtt
npm install mysql

A continuación, creamos un directorio dentro de la ruta de nuestra web con el nombre node y lanzamos el comando npm init. Tras contestar a las preguntas, creará un archivo “package.json” que contendrá diversa información de proyecto como la versión del mismo, dependencias necesarias… Por último, creamos el archivos index.js.

const mysql = require('mysql'); // or use import if you use TS
const util = require('util');
const mqtt = require('mqtt');
 
const conn = mysql.createPool({
          connectionLimit: 10,
          host: "iothogar.xyz",
          user: "admin_masteriot",
          password: "password",
          database: "admin_masteriot"
  });
  
  const options = {
          port: 1883,
          host: "iothogar.xyz",
          clientId: "habitacion_" + Math.round(Math.random() * (0-10000) * -1),
          username: "",
          password: "",
          keepalive: 60,
          reconnectPeriod: 1000,
          protocolId: "MQIsdp",
          protocolVersion: 3,
          clean: true,
          encoding: "utf8"
  }
  
  const client = mqtt.connect("mqtt://iothogar.xyz", options);
  
  client.on('connect', function(){
          console.log("Conexión MQTT Exitosa");
          client.subscribe('+/#', function(err){
                  console.log("Subscripcion Exitosa!");
          });
  });
  
  client.on('message', function(topic, message){
          // node native promisify
          const query = util.promisify(conn.query).bind(conn);
          let resultado;
  
          (async () => {
                  try {
                          const rows = await query("SELECT * FROM \`estaciones\` WHERE \`estaciones_serie\`='" + topic + "'");
                          console.log(rows[0].estaciones_serie);
                          this.resultado = rows[0].estaciones_id;
                          console.log("resultado -> " + this.resultado);
			  let insertar = "INSERT INTO \`mediciones\` (\`mediciones_valor\`, \`mediciones_estaciones_id\`) VALUES ('10', '" + this.resultado + "');";
                          conn.query(insertar,function(err,result,fields){
                                  if(err) throw err;
  
                          });
  
                  } finally {
                          //conn.end();
                  }
          })()
  
  });

index.js

En el código anterior, hay que hacer mención especial a la consulta a la BBDD, ya que estamos haciendo una consulta anidada y ésta requiere de un mayor tiempo de respuesta, necesitamos utilizar el método async para utilizar la respuesta del SELECT e introducirlo como dato en el INSERT.

Además, al tener muchos dispositivos comunicándose con la base de datos de manera constante, la gestión de conexiones se complica si pretendemos hacerla manualmente. Para evitar este problema se utiliza el Pool de conexiones y se crea con el método createPool().

Por último, instalamos el gestor de procesos PM2 que se encargará de tener activo la aplicación index.js. Así nos aseguramos de que estará siempre funcionando.

npm install pm2 -g
pm2 start index.js

Ataque

Recopilación de información

En primer lugar, hacemos un reconocimiento de la página web objetivo. Nos registramos y le damos el uso que se espera. Registramos dispositivos y accedemos al Dashboard.

Durante este proceso detectamos que la web nos devuelve texto que hemos introducido previamente en algún formulario, por lo que sospechamos que pudiera ser vulnerable a SQL Injection o XSS.

ataque Agregar dispositivos

ataque Vulnerable a XSS

Vamos a seguir este vector de ataque. El objetivo es capturar la cookie de sesión del administrador de la web. Para ello, vamos a introducir un script que enviará a nuestro equipo la cookie de sesión del admin.

Antes de proceder, vamos a configurar Apache para que se ponga a la escucha de un determinado puerto. Modificamos el archivo /etc/apache2/ports.conf y el archivo /etc/apache2/sites-enabled/000-default.conf

ataque Configuración ports.conf

ataque Configuración 000-default.conf

En el siguiente paso, necesitamos conocer la IP externa de nuestro equipo, donde recibiremos la cookie, que en nuestro caso es la 83.51.251.70. Además, hay que configura el router para que enlace las conexiones entrantes por un determinado puerto, en nuestro caso el 8081 a la ip de nuestro equipo y al puerto correspondiente, que es también el 8081. Arrancamos nuestro servicio apache con service apache2 start. Y ahora sí, configuramos el script que vamos a incluir en el campo “alias” del formulario de la web.

#tail -f /var/log/apache2/access.log

El registro creado en el log tendrá el siguiente aspecto:

192.168.1.1 - - [21/Sep/2021:14:07:05 +0200] "GET /index.php?cookie=hide\_passwords\%3D0\%3B\%20PHPSESSID\%3Dp7qj82aj6380dgn6scmor1l493 HTTP/1.1" 404 493 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86\_64; rv:92.0) Gecko/20100101 Firefox/92.0"

Donde la parte que nos interesa en este caso es:

PHPSESSID\%3Dp7qj82aj6380dgn6scmor1l493. El \%3D en formato urlencode es un ‘=’. Por lo que la cookie realmente es: p7qj82aj6380dgn6scmor1l493.

Para utilizarla, nos logueamos en el servicio con nuestro usuario pihack y después cambiamos la cookie por la capturada, usando el complemento de Firefox editthiscookie2.

De esta manera, pasaremos de ser el usuario \textbf{pihack} al usuario \textb f{javier}

ataque

Cambiamos la cookie de sesión y al actualizar la página, hemos conseguido acceder como el usuario “javier”. El hecho de que el usuario “javier” haya accedido a la información del dispositivo que hemos registrado, nos da a entender que tiene permisos de administrador. De hecho, en el menú lateral tiene una opción que nosotros, como “pihack”, no tenemos. Es la opción “Administrador”. Vamos a comprobarla.

ataque

Esta sección da acceso a un listado de todos los dispositivos y todos los usuarios registrados en la plataforma. Además, los campos son editables directamente e individualmente, tal como vemos en la imagen:

ataque

Esto nos permite reasignar un dispositivo a otro usuario. Por tanto, ya podemos perfilar el ataque contra nuestro objetivo que, recordemos, es una de las usuarias de esta plataforma.

El ataque consistirá en crear uno o varios dispositivos con el mismo alias y serie que los de nuestro objetivo (en caso de que no nos deje pondremos nombres muy parecido), generar datos falsos para que queden registrados y, después, sustituir sus dispositivos por los nuestros. De tal forma que cuando nuestro objetivo consulte la información de sus dispositivos, se muestre la de los nuestros con datos envenenados.

Vamos a resumir los pasos a dar:

1. Registrar dispositivos con mismo nombre que el del objetivo.
2. Desarrollar script en Python para enviar datos falsos a nuestros dispositivo  s.
3. Sustituir los dispositivos de nuestro objetivo por los nuestros

Registro de dispositivos maliciosos

…continuará…