Seguridad

Aprender la ciberseguridad - Actividades prácticas de desarrollo seguro

Ejercicios prácticos de desarrollo seguro: validación de las entradas del usuario, protección frente a la inyección SQL y prevención de los fallos XSS en PHP.

· 14 min de lectura · nivel intermedio

La seguridad en el desarrollo es un pilar decisivo de la ciberseguridad. Consiste en integrar prácticas de seguridad desde las primeras etapas del diseño y del desarrollo de las aplicaciones, con el fin de prevenir las vulnerabilidades y proteger los datos sensibles. En el contexto actual, en el que los ciberataques son cada vez más sofisticados, comprender y aplicar medidas de seguridad en el desarrollo de software resulta esencial.

Estos ejercicios le guiarán a través de escenarios prácticos para identificar y corregir las vulnerabilidades habituales del código. Aprenderá a proteger sus aplicaciones frente a ataques como las inyecciones SQL y los fallos XSS (Cross-Site Scripting). Cada ejercicio está pensado para familiarizarle con técnicas concretas y proporcionarle los conocimientos necesarios para desarrollar aplicaciones robustas y seguras.

Illustration 1 — Apprendre la cybersécurité - Activités pratiques en développement sécurisé

Validación de las entradas del usuario

Objetivo :

Aprender a validar y a depurar las entradas del usuario para prevenir las vulnerabilidades habituales, como las inyecciones SQL, los fallos XSS y otros ataques basados en entradas no seguras.

Requisitos previos:

  • Conocimientos básicos de PHP y HTML.
  • Servidor web local (como LAMP, XAMPP o WAMP).

Instrucciones:

  • Cree un formulario HTML sencillo que permita al usuario introducir su nombre y su dirección de correo electrónico.
  • Trate los datos en PHP para evitar la inclusión de caracteres maliciosos.

Corrección y explicacionesCorrección
Creación de un formulario para introducir los nombres y los correos electrónicos.
html <form method="post" action=""> Nom : <input type="text" name="nom"><br> Email : <input type="email" name="email"><br> <input type="submit" value="Envoyer"> </form>
Tratamiento de los datos en PHP para evitar la inclusión de caracteres maliciosos.
<?php if ($_SERVER["REQUEST_METHOD"] == "POST") { $nom = $_POST['nom']; $email = $_POST['email']; // Validation et assainissement des données $nom = trim($nom); $nom = stripslashes($nom); $nom = htmlspecialchars($nom); $email = filter_var($email, FILTER_SANITIZE_EMAIL); if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { echo "Adresse email invalide."; } else { echo "Nom : $nom<br>"; echo "Email : $email<br>"; } } ?>
Explicaciones
Validación y depuración de los datos
trim($nom) : Suprime los espacios en blanco al principio y al final de la cadena.stripslashes($nom) : Suprime las barras invertidas ( \ ) añadidas por algunas funciones de PHP para escapar los caracteres especiales.htmlspecialchars($nom) : Convierte los caracteres especiales en entidades HTML (por ejemplo, < se convierte en &lt;), lo que impide la ejecución de código HTML o JavaScript.
Validación del correo electrónico
filter_var($email, FILTER_SANITIZE_EMAIL) : Suprime los caracteres no válidos en una dirección de correo electrónico.filter_var($email, FILTER_VALIDATE_EMAIL) : Valida que el correo electrónico está bien formado.
Corrección y explicaciones detalladas
Supresión de los espacios y caracteres innecesarios:$nom = trim($nom);
Esta función elimina los espacios en blanco u otros caracteres predefinidos (como los saltos de línea) al principio y al final de la cadena.
Eliminación de las barras invertidas:$nom = stripslashes($nom);
stripslashes() retira las barras invertidas añadidas para escapar las comillas en determinadas configuraciones de PHP, lo que protege frente a ciertas formas de inyección.
Conversión de los caracteres especiales:$nom = htmlspecialchars($nom);
htmlspecialchars() convierte los caracteres especiales en entidades HTML para impedir los ataques XSS. Por ejemplo, < se convierte en &lt;, > se convierte en &gt;, y así sucesivamente.
Depuración del correo electrónico:$email = filter_var($email, FILTER_SANITIZE_EMAIL);
FILTER_SANITIZE_EMAIL retira todos los caracteres no autorizados en una dirección de correo electrónico.
Validación del correo electrónico:if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { echo "Adresse email invalide."; }
FILTER_VALIDATE_EMAIL comprueba que la dirección de correo electrónico está bien formada según el estándar de las direcciones de correo. Si el correo no es válido, se muestra un mensaje de error.


Protección frente a la inyección SQL

Objetivo: Comprender y aplicar medidas de seguridad para prevenir los ataques por inyección SQL.

Instrucciones

  • Cree una base de datos sencilla con una tabla de usuarios.
  • Escriba una consulta SQL vulnerable a la inyección SQL.
  • Modifique el código para utilizar consultas preparadas y prevenir así la inyección SQL.

Etapas

1. Creación de la base de datos y de la tabla:

  • Utilice un SGBD (por ejemplo, MariaDB, MySQL o PostegreSQL) para crear una base de datos y una tabla users.
  • Inserte después al menos un usuario en esa tabla (por ejemplo, admin)

2. Consulta SQL vulnerable:

  • Escriba una consulta SQL en un archivo PHP (por ejemplo, login.php) que sea vulnerable a la inyección SQL.
  • Abra su navegador y acceda al archivo login.php.
  • Inyecte consultas SQL maliciosas para ver si la aplicación es vulnerable.

3. Uso de consultas preparadas:

  • Modifique el código para utilizar consultas preparadas y prevenir la inyección.
  • Vuelva a probar la aplicación utilizando la misma inyección SQL que antes.
Illustration 2 — Apprendre la cybersécurité - Activités pratiques en développement sécurisé

Corrección y explicaciones1. Creación de la base de datos y de la tabla
Solución de la consulta SQL para la creación de la base de datos y de la tabla:
CREATE DATABASE security_test; USE security_test; CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50), password VARCHAR(50) );
Inserción de datos:
INSERT INTO users (username, password) VALUES ('admin', 'password123'), ('user1', 'pass1');
Explicaciones de los comandos MySQL:
CREATE DATABASE security_test; : Este comando crea una nueva base de datos llamada security_test.
USE security_test; : Este comando selecciona la base de datos security_test para que los comandos siguientes se ejecuten en ese contexto.
CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50), password VARCHAR(50)); : Este comando crea una tabla users con tres columnas: id (clave primaria autoincrementada), username y password (ambas de tipo VARCHAR con una longitud máxima de 50 caracteres).
INSERT INTO users (username, password) VALUES ('admin', 'password123'), ('user1', 'pass1'); : Este comando inserta dos registros en la tabla users.
Estas etapas permiten crear un entorno de prueba sencillo para los ejercicios sobre las inyecciones SQL.
2. Consulta SQL vulnerable
Contenido del archivo login.php:
<?php $conn = new mysqli('localhost', 'root', '', 'security_test'); if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } if ($_SERVER["REQUEST_METHOD"] == "POST") { $username = $_POST['username']; $password = $_POST['password']; $query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'"; $result = $conn->query($query); if ($result->num_rows > 0) { echo "Login successful!"; } else { echo "Invalid credentials."; } } ?> <form method="post" action=""> Username: <input type="text" name="username"> Password: <input type="password" name="password"> <input type="submit" value="Login"> </form>
Explicación del código:
Conexión a la base de datos: new mysqli('localhost', 'root', '', 'security_test'); crea una conexión a la base de datos security_test en localhost con el usuario root sin contraseña.
Consulta SQL: $query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'"; construye dinámicamente una consulta SQL basada en las entradas del usuario.
Comprobación de los resultados: if ($result->num_rows > 0) comprueba si la consulta ha devuelto filas, lo que indica una conexión correcta.
Vulnerabilidad:
Al inyectar una consulta SQL maliciosa en los campos de entrada, por ejemplo admin' OR '1'='1 como nombre de usuario y anything como contraseña, el atacante puede sortear la autenticación.
Detalles de la inyección SQL
Ejemplo de ataque
Nombre de usuario: admin' OR '1'='1Contraseña: anything
Construcción de la consulta SQL:
Cuando estos valores se envían mediante el formulario web, el código PHP vulnerable construye la consulta SQL siguiente:
SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = 'anything';
Para comprenderlo mejor, descompongamos esta consulta:
Cláusula WHERE:username = 'admin' : Comprueba si el nombre de usuario es admin.OR '1'='1' : Siempre verdadero, porque '1' es igual a '1'.
AND password = 'anything' : Comprueba si la contraseña es anything.
Evaluación de la cláusula WHERE:
En SQL, los operadores AND y OR siguen una lógica precisa:
A OR B es verdadero si una de las condiciones A o B es verdadera.A AND B es verdadero si las dos condiciones A y B son verdaderas.
En este caso:La condición username = 'admin' puede ser verdadera o falsa.La condición '1'='1' es siempre verdadera.La condición password = 'anything' puede ser verdadera o falsa.La presencia de OR '1'='1' significa que la cláusula entera username = 'admin' OR '1'='1' es siempre verdadera, sea cual sea el contenido del campo username.
Resultado de la consulta:
Dado que OR '1'='1' es siempre verdadero, la consulta puede reducirse a:
SELECT * FROM users WHERE TRUE AND password = 'anything';Lo que equivale a:
SELECT * FROM users WHERE password = 'anything';
No obstante, el AND con una condición siempre verdadera y una condición sin importancia (password = 'anything') significa que MySQL devolverá la primera fila de la tabla si alguna fila corresponde a la contraseña facilitada, o incluso todas las filas si una de las condiciones es falsa. Pero, como OR '1'='1' es siempre verdadero, la consulta devuelve todos los usuarios.
Impacto:La consulta devuelve una fila de la tabla users, lo que hace creer al script PHP que la autenticación ha tenido éxito.
El atacante queda así autenticado correctamente sin conocer la contraseña real.Uso de consultas preparadas
Modificación del archivo login.php:<?php $conn = new mysqli('localhost', 'root', '', 'security_test'); if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } if ($_SERVER["REQUEST_METHOD"] == "POST") { $username = $_POST['username']; $password = $_POST['password']; $stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?"); $stmt->bind_param("ss", $username, $password); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows > 0) { echo "Login successful!"; } else { echo "Invalid credentials."; } } ?> <form method="post" action=""> Username: <input type="text" name="username"> Password: <input type="password" name="password"> <input type="submit" value="Login"> </form>
Explicación del código:
1. La clase mysqli_stmt
La clase mysqli_stmt de PHP representa una consulta SQL preparada. Permite ejecutar consultas SQL de manera segura, evitando las inyecciones SQL. La clase se utiliza junto con un objeto mysqli (que representa una conexión a una base de datos).
2. Creación de una consulta preparada
Línea de código:
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$conn->prepare() es un método del objeto mysqli que toma como parámetro una cadena de caracteres con la consulta SQL y signos de interrogación ? como marcadores de posición para los parámetros.
Este método devuelve un objeto mysqli_stmt que representa la consulta preparada.Los signos de interrogación ? indican los lugares donde los valores de las variables se insertarán de manera segura.
3. Asociación de los parámetros
Línea de código:
$stmt->bind_param("ss", $username, $password);
bind_param() es un método del objeto mysqli_stmt que se utiliza para vincular las variables PHP a los marcadores de posición ? de la consulta preparada.
El primer argumento "ss" es una cadena de caracteres que especifica los tipos de los parámetros:"s" para string (cadena de caracteres)"i" para integer (entero)"d" para double (número en coma flotante)"b" para blob (datos binarios)
En este caso, "ss" indica que los dos parámetros son cadenas de caracteres.
Los argumentos siguientes son las variables que hay que vincular: $username y $password.
4. Ejecución de la consulta
Línea de código:
$stmt->execute();
execute() es un método del objeto mysqli_stmt que ejecuta la consulta preparada con los valores de los parámetros vinculados.Cuando se llama a execute(), la consulta SQL se envía al servidor de base de datos con los valores de las variables insertados en lugar de los marcadores de posición.
5. Recuperación de los resultados
Línea de código:
$result = $stmt->get_result();
get_result() es un método del objeto mysqli_stmt que devuelve un objeto mysqli_result que representa los resultados de la consulta.Este objeto puede utilizarse para acceder a los datos devueltos por la consulta SQL.
Importancia de las consultas preparadas
El uso de consultas preparadas impide tales inyecciones, porque las entradas del usuario no se insertan directamente en la consulta SQL. Las consultas preparadas tratan las entradas como datos en bruto, separados de los comandos SQL.

Protección frente a los fallos XSS

Objetivo: Aprender a identificar y a prevenir los fallos XSS comprendiendo cómo puede un atacante inyectar scripts maliciosos en una aplicación web.

Requisitos previos:

  • Conocimientos básicos de PHP y HTML.
  • Servidor web local (como XAMPP o WAMP).

Etapas:

1. Creación de una aplicación web vulnerable

Cree un formulario HTML sencillo que permita al usuario enviar un comentario y muestre después ese comentario en la misma página sin validación.

<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $comment = $_POST['comment'];
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Commentaires</title>
</head>
<body>
    <form method="post" action="">
        Commentaire: <input type="text" name="comment">
        <input type="submit" value="Soumettre">
    </form>

    <h2>Commentaires:</h2>
    <p><?php echo $comment; ?></p>
</body>
</html>

2. Inyección de un script malicioso:

Envíe el formulario con un script malicioso como comentario:

<script>alert('XSS Attack!');</script>

Cuando se muestre el comentario, el script se ejecutará y aparecerá una alerta con el mensaje "XSS Attack!".

3. Comprensión del comportamiento de la petición:

El formulario envía los datos del comentario mediante una petición POST.

El valor de $_POST['comment'] se muestra directamente sin validación, lo que permite la inyección de código JavaScript.

4. Prevención del fallo XSS

Modifique el script PHP para utilizar htmlspecialchars() y convertir así los caracteres especiales en entidades HTML, lo que impide la ejecución del script.

<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $comment = htmlspecialchars($_POST['comment'], ENT_QUOTES, 'UTF-8');
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Commentaires</title>
</head>
<body>
    <form method="post" action="">
        Commentaire: <input type="text" name="comment">
        <input type="submit" value="Soumettre">
    </form>
    <h2>Commentaires:</h2>
    <p><?php echo $comment; ?></p>
</body>
</html>

5. Comprobación de la protección

Envíe de nuevo el formulario con el script malicioso:

<script>alert('XSS Attack!');</script>

Esta vez el script no se ejecutará y el comentario mostrará literalmente <script>alert('XSS Attack!');</script>.