PHP

PHP

Tutorial PHP: Seguridad en formularios y entrada de datos

PHP: Aprende a filtrar y validar entradas de usuario para asegurar tus aplicaciones. Implementa tokens anti-CSRF y sanitiza con `htmlspecialchars`.

Aprende PHP GRATIS y certifícate

Filtrado y validación de toda entrada del usuario

La validación y el filtrado de la entrada del usuario son pasos fundamentales para garantizar la seguridad y el correcto funcionamiento de las aplicaciones en PHP. Toda información recibida desde fuentes externas, como formularios, URL, cookies o servicios web, debe considerarse potencialmente maliciosa y, por lo tanto, debe ser rigurosamente validada y filtrada antes de su uso.

Una práctica esencial es nunca confiar en los datos proporcionados por el usuario. Los atacantes pueden aprovechar entradas no validadas para inyectar código malicioso, provocar errores o manipular el comportamiento de la aplicación. Por ello, es imprescindible implementar mecanismos de validación que aseguren que los datos cumplen con el formato y las restricciones esperadas.

La validación consiste en comprobar que la entrada del usuario cumple con criterios específicos, como tipos de datos, rangos numéricos o formatos de fechas. Por ejemplo, si se espera un número entero positivo, se debe verificar que el valor recibido sea efectivamente un entero y que sea mayor o igual a cero.

Por otro lado, el filtrado implica eliminar o modificar cualquier contenido que pueda ser potencialmente dañino o no deseado. Esto es especialmente relevante al manejar entradas que podrían ser utilizadas en comandos del sistema, consultas SQL o secciones de código HTML, donde caracteres especiales podrían alterar la lógica o estructura esperada.

Un ejemplo común es el ataque de inyección SQL, donde un usuario malintencionado puede introducir código SQL en campos de texto para manipular consultas a la base de datos. Para prevenir esto, es esencial utilizar parámetros preparados y filtrar adecuadamente las entradas que se utilizan en las consultas.

Es recomendable definir reglas de validación específicas para cada tipo de dato esperado. Por ejemplo:

  • Direcciones de correo electrónico: verificar que tengan un formato válido.
  • Números de teléfono: asegurar que contengan únicamente dígitos y, opcionalmente, símbolos permitidos como '+' o '-'.
  • Fechas: confirmar que siguen el formato esperado y representan fechas válidas.

Además, es importante considerar el contexto en el que se utilizarán los datos. Una misma entrada puede requerir diferentes filtros dependiendo de si se muestra en una página web, se inserta en una base de datos o se utiliza en un script del sistema.

Al implementar la validación y el filtrado, se debe mantener un equilibrio entre la usabilidad y la seguridad. Un filtrado demasiado restrictivo puede frustrar a los usuarios legítimos, mientras que uno demasiado laxo puede dejar vulnerabilidades abiertas. Por ello, es fundamental analizar cuidadosamente los requisitos y los posibles riesgos asociados a cada tipo de entrada.

Por ejemplo, para validar que una edad proporcionada a través de un formulario es un número entero positivo, se podría utilizar el siguiente código:

<?php
$edad = $_POST['edad'] ?? null;

if ($edad === null) {
    echo "No se ha proporcionado la edad.\n";
} elseif (filter_var($edad, FILTER_VALIDATE_INT, ["options" => ["min_range" => 0]]) === false) {
    echo "La edad debe ser un número entero positivo.\n";
} else {
    echo "La edad ingresada es válida: $edad\n";
}

En este ejemplo, se utiliza la función filter_var con el filtro FILTER_VALIDATE_INT y la opción min_range para asegurarse de que la edad sea un entero positivo. Si la validación falla, se informa al usuario que debe ingresar un valor correcto.

Otro aspecto crucial es la validación del lado del servidor. Aunque se pueden implementar validaciones en el lado del cliente, por ejemplo, mediante JavaScript, estas nunca deben reemplazar las validaciones en el servidor. Los usuarios pueden desactivar o manipular las validaciones del lado del cliente, por lo que el servidor debe siempre realizar sus propias comprobaciones.

También es recomendable utilizar listas blancas (permitir solo valores o caracteres específicos) en lugar de listas negras (bloquear ciertos valores o caracteres). Las listas blancas son más seguras, pues se definen explícitamente los valores aceptables, reduciendo el riesgo de que se escape alguna entrada maliciosa no contemplada.

Por ejemplo, si se espera que el usuario seleccione un valor de entre varias opciones predefinidas, se puede verificar que el valor recibido coincida con uno de los permitidos:

<?php
$opciones_permitidas = ['opcion1', 'opcion2', 'opcion3'];
$seleccion = $_POST['seleccion'] ?? null;

if (!in_array($seleccion, $opciones_permitidas, true)) {
    echo "La opción seleccionada no es válida.\n";
} else {
    echo "Has seleccionado: $seleccion\n";
}

En este código, se comprueba que el valor recibido esté dentro del array de opciones permitidas, garantizando que solo se acepten valores válidos.

Implementar el filtrado y la validación de forma rigurosa fortalece la seguridad de la aplicación y protege tanto los datos como la experiencia del usuario. Es una práctica que debe integrarse en cada punto donde se reciba información externa, asegurando que el sistema maneje únicamente datos confiables y adecuados para su procesamiento.

Uso de funciones y constantes como FILTER_SANITIZE_STRING, htmlspecialchars, ENT_QUOTES

El manejo adecuado de las entradas del usuario es esencial para prevenir vulnerabilidades como inyección de código y ataques Cross-Site Scripting (XSS). Para ello, PHP ofrece funciones y constantes que permiten sanitizar y escape de datos, asegurando que la información sea tratada de forma segura antes de ser procesada o mostrada.

htmlspecialchars y la constante ENT_QUOTES

La función htmlspecialchars convierte caracteres especiales en entidades HTML, evitando que contenido malicioso se interprete como código en el navegador. Esto es especialmente importante al mostrar datos ingresados por el usuario en una página web.

Por ejemplo, si un usuario ingresa <script>alert('XSS');</script>, al mostrar este texto sin sanitizar, el navegador ejecutaría el script, lo que representa un serio riesgo de seguridad. Con htmlspecialchars, este input se transforma en texto plano, neutralizando cualquier intento de ataque.

La constante ENT_QUOTES es un flag que se utiliza con htmlspecialchars para indicar que se deben convertir tanto las comillas simples como las dobles en sus respectivas entidades HTML. Esto asegura que todas las comillas sean tratadas adecuadamente, previniendo posibles inyecciones.

Ejemplo de uso:

<?php
$input_usuario = $_POST['comentario'] ?? '';

$comentario_seguro = htmlspecialchars($input_usuario, ENT_QUOTES, 'UTF-8');

echo "Comentario: $comentario_seguro\n";

En este código:

  • $input_usuario captura el dato proporcionado por el usuario.
  • htmlspecialchars transforma caracteres especiales en entidades HTML.
  • ENT_QUOTES asegura que ambas comillas sean convertidas.
  • Se especifica la codificación 'UTF-8' para un manejo correcto de caracteres.

De esta manera, cualquier intento de inyección de código insertado en comentario será mostrado como texto plano, salvaguardando la integridad de la aplicación.

Alternativas a FILTER_SANITIZE_STRING

La constante FILTER_SANITIZE_STRING ha sido depreciada en versiones recientes de PHP y no se recomienda su uso. En su lugar, se sugiere utilizar FILTER_SANITIZE_SPECIAL_CHARS, que funciona de manera similar a htmlspecialchars, sanitizando caracteres especiales.

Ejemplo de uso con filter_var:

<?php
$input_usuario = $_POST['nombre'] ?? '';

$nombre_seguro = filter_var($input_usuario, FILTER_SANITIZE_SPECIAL_CHARS);

echo "Nombre: $nombre_seguro\n";

En este caso:

  • filter_var aplica el filtro especificado a la variable.
  • FILTER_SANITIZE_SPECIAL_CHARS convierte caracteres como <, >, &, ", y ' en entidades HTML.

Este enfoque es útil para sanitizar datos antes de almacenarlos o procesarlos, minimizando el riesgo de ataques XSS y otros vectores de ataque.

Consideraciones al usar funciones de sanitización

Es importante tener en cuenta:

  • Sanitizar no es lo mismo que validar: Sanitizar limpia los datos, pero se debe complementar con una validación que asegure que los datos cumplen con el formato esperado.
  • Orden de operaciones: Sanitizar los datos antes de validarlos puede alterar el contenido y producir falsos negativos en la validación. Por ello, se recomienda validar primero y luego sanitizar antes de mostrar los datos.
  • Codificación de caracteres: Siempre especificar la codificación 'UTF-8' en funciones como htmlspecialchars para un manejo correcto de caracteres especiales y evitar problemas de interpretación.

Uso combinado de htmlspecialchars y validación

Un procedimiento robusto implica validar los datos y luego sanitizarlos antes de mostrarlos:

<?php
$email = $_POST['email'] ?? '';

if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $email_seguro = htmlspecialchars($email, ENT_QUOTES, 'UTF-8');
    echo "Email válido: $email_seguro\n";
} else {
    echo "El email proporcionado no es válido.\n";
}

Aquí se realiza:

  • Validación del email con FILTER_VALIDATE_EMAIL.
  • Sanitización del email con htmlspecialchars para evitar XSS al mostrarlo.

Sanitización de URLs y números enteros

Para otros tipos de datos, PHP ofrece filtros específicos:

  • URLs:
<?php
$url = $_GET['url'] ?? '';

if (filter_var($url, FILTER_VALIDATE_URL)) {
    $url_segura = filter_var($url, FILTER_SANITIZE_URL);
    echo "URL válida: $url_segura\n";
} else {
    echo "La URL proporcionada no es válida.\n";
}
  • Números enteros:
<?php
$id = $_GET['id'] ?? '';

if (filter_var($id, FILTER_VALIDATE_INT)) {
    $id_seguro = intval($id);
    echo "ID válido: $id_seguro\n";
} else {
    echo "El ID proporcionado no es válido.\n";
}

En estos ejemplos, se utiliza FILTER_SANITIZE_URL para limpiar URLs y FILTER_VALIDATE_INT junto con intval para asegurar que el ID es un número entero.

Importancia del escape de datos en consultas y HTML

Además de sanitizar, es crucial escapar los datos cuando se integran en consultas SQL o en código HTML. Si bien htmlspecialchars es efectivo al mostrar datos en la web, para consultas SQL se deben utilizar sentencias preparadas o escapar adecuadamente las variables.

Por ejemplo, al insertar datos en la base de datos:

<?php
$mensaje = $_POST['mensaje'] ?? '';

$mensaje_seguro = htmlspecialchars($mensaje, ENT_QUOTES, 'UTF-8');

$stmt = $pdo->prepare('INSERT INTO mensajes (contenido) VALUES (:contenido)');
$stmt->execute(['contenido' => $mensaje_seguro]);

En este caso:

  • Se utiliza una sentencia preparada para prevenir inyecciones SQL.
  • El mensaje se sanitiza con htmlspecialchars si posteriormente se mostrará en HTML.

Resumen de mejores prácticas

  • Utilizar htmlspecialchars con ENT_QUOTES y codificación 'UTF-8' al mostrar datos en HTML.
  • Evitar el uso de funciones depreciadas como FILTER_SANITIZE_STRING y optar por alternativas seguras.
  • Validar los datos con filtros apropiados antes de procesarlos.
  • Emplear sentencias preparadas en consultas a bases de datos.
  • No confiar nunca en la entrada del usuario y aplicar siempre medidas de seguridad adecuadas.

Aplicando estas funciones y constantes de manera correcta, se fortalece la seguridad de las aplicaciones PHP y se protege tanto al servidor como a los usuarios frente a posibles ataques y vulnerabilidades.

Tokens anti-CSRF y validaciones adicionales en formularios

La protección contra ataques Cross-Site Request Forgery (CSRF) es esencial en el desarrollo de aplicaciones web seguras en PHP. Los ataques CSRF explotan la confianza que una aplicación web tiene en el navegador del usuario, permitiendo que acciones no autorizadas se ejecuten sin el conocimiento del usuario. Para mitigar este riesgo, se utilizan tokens anti-CSRF, que son valores únicos y secretos que se incorporan en los formularios y se validan en el servidor al procesar las solicitudes.

La implementación de tokens anti-CSRF implica generar un token único para cada sesión o solicitud, almacenarlo en una variable de sesión y luego incrustarlo en los formularios como un campo oculto. Al recibir la solicitud, el servidor verifica que el token proporcionado coincida con el almacenado en la sesión, asegurando que la acción es legítima y ha sido iniciada por el usuario autorizado.

Ejemplo de generación y verificación de un token anti-CSRF:

<?php
session_start();

// Generar un token si no existe
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// Incluir el token en un formulario
echo "<form action='procesar.php' method='POST'>";
echo "<input type='hidden' name='csrf_token' value='{$_SESSION['csrf_token']}'>";
echo "<input type='text' name='dato_usuario'>";
echo "<input type='submit' value='Enviar'>";
echo "</form>\n";

En el código anterior:

  • Se utiliza la función random_bytes para generar un token seguro y aleatorio.
  • El token se almacena en $_SESSION['csrf_token'].
  • Se incluye un campo oculto <input type="hidden" name="csrf_token"> con el valor del token.

Al procesar el formulario en procesar.php, se debe validar el token:

<?php
session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token_usuario = $_POST['csrf_token'] ?? '';

    if (!hash_equals($_SESSION['csrf_token'], $token_usuario)) {
        die('Error: token CSRF no válido.' . "\n");
    }

    // Procesar los datos del usuario
    $dato_usuario = $_POST['dato_usuario'] ?? '';
    echo 'Datos procesados: ' . htmlspecialchars($dato_usuario, ENT_QUOTES, 'UTF-8') . "\n";
}

En este código:

  • Se recupera el token enviado por el usuario desde $_POST['csrf_token'].
  • Se utiliza la función hash_equals para comparar de forma segura el token del usuario con el almacenado en la sesión.
  • Si el token no es válido, se detiene la ejecución y se muestra un mensaje de error.

Es importante destacar que hash_equals proporciona una comparación en tiempo constante, evitando ataques de tiempo que podrían permitir a un atacante adivinar el token.

Además de los tokens anti-CSRF, es fundamental realizar validaciones adicionales en los formularios para reforzar la seguridad. Estas validaciones incluyen:

Comprobar el método de solicitud: Asegurarse de que se está utilizando el método HTTP correcto (por ejemplo, POST para operaciones que modifican datos).

Restringir el tipo de contenido: Verificar que el encabezado Content-Type sea el esperado, especialmente en solicitudes POST.

Limitar el tiempo de validez del token: Asociar una marca de tiempo al token y validar que no haya expirado, reduciendo la ventana de oportunidad para un ataque.

Implementación de un token con tiempo de caducidad:

<?php
session_start();

// Generar un token con tiempo de expiración
if (empty($_SESSION['csrf_token']) || $_SESSION['csrf_token_exp'] < time()) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    $_SESSION['csrf_token_exp'] = time() + 600; // El token expira en 10 minutos
}

// Incluir el token en el formulario
echo "<form action='procesar.php' method='POST'>";
echo "<input type='hidden' name='csrf_token' value='{$_SESSION['csrf_token']}'>";
echo "<input type='text' name='dato_usuario'>";
echo "<input type='submit' value='Enviar'>";
echo "</form>\n";

En procesar.php, validar el token y su vigencia:

<?php
session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token_usuario = $_POST['csrf_token'] ?? '';

    if (
        empty($token_usuario) ||
        !hash_equals($_SESSION['csrf_token'], $token_usuario) ||
        $_SESSION['csrf_token_exp'] < time()
    ) {
        die("Error: token CSRF inválido o expirado.\n");
    }

    // Procesar los datos del usuario
    $dato_usuario = $_POST['dato_usuario'] ?? '';
    echo 'Datos procesados: ' . htmlspecialchars($dato_usuario, ENT_QUOTES, 'UTF-8') . "\n";
}

Este enfoque añade una capa adicional de seguridad al limitar la validez del token en el tiempo, lo que dificulta aún más la explotación de un posible ataque CSRF.

Otra práctica recomendada es implementar la rotación de tokens después de cada solicitud, generando un nuevo token y actualizando el almacenado en la sesión. Esto asegura que cada formulario tenga un token único que no se pueda reutilizar en el futuro.

Ejemplo de rotación de token:

<?php
session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token_usuario = $_POST['csrf_token'] ?? '';

    if (empty($token_usuario) || !hash_equals($_SESSION['csrf_token'], $token_usuario)) {
        die("Error: token CSRF no válido.\n");
    }

    // Rotar el token después de una solicitud exitosa
    unset($_SESSION['csrf_token']);
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));

    // Procesar los datos del usuario
    $dato_usuario = $_POST['dato_usuario'] ?? '';
    echo 'Datos procesados: ' . htmlspecialchars($dato_usuario, ENT_QUOTES, 'UTF-8') . "\n";
}

En este caso, se elimina el token antiguo y se genera uno nuevo una vez que la validación es exitosa.

Además de los tokens anti-CSRF, se recomienda realizar las siguientes validaciones adicionales en los formularios:

Validación del origen de la solicitud: Comprobar el encabezado Referer para asegurarse de que la solicitud proviene de la misma aplicación. Sin embargo, esta técnica puede no ser fiable en todos los casos debido a proxies y configuraciones de privacidad del navegador.

Implementar listas blancas de redireccionamiento: Si se utilizan redirecciones después de procesar el formulario, validar que las URLs de destino sean seguras y pertenecientes a dominios autorizados.

Limitar el tamaño de los datos: Establecer límites en la cantidad y tamaño de los datos enviados para prevenir ataques de denegación de servicio (DoS).

Utilizar cabeceras de seguridad: Configurar cabeceras como X-Frame-Options, Content-Security-Policy y X-XSS-Protection para fortalecer la protección contra diversas amenazas.

Por ejemplo, para limitar el tamaño de un campo de texto:

<?php
$dato_usuario = $_POST['dato_usuario'] ?? '';

if (strlen($dato_usuario) > 255) {
    die("Error: el dato ingresado excede el tamaño permitido.\n");
}

// Continuar con el procesamiento

En este código, se verifica que la longitud de $dato_usuario no exceda los 255 caracteres, previniendo desbordamientos o excesos de datos.

Es vital también realizar la validación del lado del servidor de todos los datos, incluso si ya se ha implementado validación en el cliente con JavaScript. Los atacantes pueden omitir o modificar la validación del lado del cliente, por lo que el servidor debe garantizar que los datos recibidos cumplan con los criterios esperados.

Asimismo, es recomendable implementar una estrategia de detección y respuesta ante posibles ataques. Esto incluye registrar intentos fallidos de validación, monitorear patrones sospechosos y bloquear direcciones IP que muestren actividad maliciosa.

Por ejemplo, contar los intentos fallidos y bloquear al usuario después de varios intentos:

<?php
session_start();

if (!isset($_SESSION['intentos_fallidos'])) {
    $_SESSION['intentos_fallidos'] = 0;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token_usuario = $_POST['csrf_token'] ?? '';

    if (empty($token_usuario) || !hash_equals($_SESSION['csrf_token'], $token_usuario)) {
        $_SESSION['intentos_fallidos']++;

        if ($_SESSION['intentos_fallidos'] >= 5) {
            die("Error: demasiados intentos fallidos. Intente nuevamente más tarde.\n");
        }

        die("Error: token CSRF no válido.\n");
    }

    // Restablecer el contador en caso de éxito
    $_SESSION['intentos_fallidos'] = 0;

    // Procesar los datos del usuario
    $dato_usuario = $_POST['dato_usuario'] ?? '';
    echo 'Datos procesados: ' . htmlspecialchars($dato_usuario, ENT_QUOTES, 'UTF-8') . "\n";
}

Con este código, se incrementa un contador de intentos fallidos almacenado en la sesión, y si se supera un umbral definido (en este caso, 5 intentos), se bloquea el procesamiento.

Finalmente, es fundamental mantenerse actualizado con las mejores prácticas de seguridad y revisar periódicamente el código para identificar y corregir posibles vulnerabilidades. La seguridad es un proceso continuo que requiere atención constante para proteger las aplicaciones y los datos de los usuarios.

Para seguir leyendo hazte Plus

¿Ya eres Plus? Accede a la app

20 % DE DESCUENTO

Plan mensual

19.00 /mes

15.20 € /mes

Precio normal mensual: 19 €
58 % DE DESCUENTO

Plan anual

10.00 /mes

8.00 € /mes

Ahorras 132 € al año
Precio normal anual: 120 €
Aprende PHP GRATIS online

Todas las lecciones de PHP

Accede a todas las lecciones de PHP y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Php

PHP

Introducción Y Entorno

Instalación Y Primer Programa De Php

PHP

Introducción Y Entorno

Tipos De Datos, Variables Y Constantes

PHP

Sintaxis

Operadores Y Expresiones

PHP

Sintaxis

Estructuras De Control

PHP

Sintaxis

Funciones Y Llamada De Funciones

PHP

Sintaxis

Cadenas De Texto Y Manipulación

PHP

Sintaxis

Manejo De Números

PHP

Sintaxis

Manejo De Fechas Y Tiempo

PHP

Sintaxis

Manejo De Arrays

PHP

Sintaxis

Introducción A La Poo En Php

PHP

Programación Orientada A Objetos

Clases Y Objetos

PHP

Programación Orientada A Objetos

Constructores Y Destructores

PHP

Programación Orientada A Objetos

Herencia

PHP

Programación Orientada A Objetos

Encapsulación

PHP

Programación Orientada A Objetos

Polimorfismo

PHP

Programación Orientada A Objetos

Interfaces

PHP

Programación Orientada A Objetos

Traits

PHP

Programación Orientada A Objetos

Namespaces

PHP

Programación Orientada A Objetos

Autoloading De Clases

PHP

Programación Orientada A Objetos

Manejo De Errores Y Excepciones

PHP

Programación Orientada A Objetos

Manejo De Archivos

PHP

Programación Orientada A Objetos

Patrones De Diseño

PHP

Programación Orientada A Objetos

Introducción A Los Formularios En Php

PHP

Formularios

Procesamiento De Datos De Formularios

PHP

Formularios

Manejo De Archivos En Formularios

PHP

Formularios

Redirecciones Y Retroalimentación Al Usuario

PHP

Formularios

Formularios Dinámicos Y Separación De Lógica

PHP

Formularios

Introducción A La Persistencia En Php

PHP

Persistencia

Conexión A Bases De Datos

PHP

Persistencia

Consultas Y Operaciones Crud

PHP

Persistencia

Gestión De Transacciones

PHP

Persistencia

Manejo De Errores Y Excepciones En Base De Datos

PHP

Persistencia

Patrones De Acceso A Datos

PHP

Persistencia

Concepto De Sesiones En Php

PHP

Sesiones Y Cookies

Configuración De Sesiones

PHP

Sesiones Y Cookies

Cookies

PHP

Sesiones Y Cookies

Manejo Avanzado De Sesiones Y Cookies

PHP

Sesiones Y Cookies

Principales Vulnerabilidades En Php

PHP

Seguridad

Seguridad En Formularios Y Entrada De Datos

PHP

Seguridad

Protección Frente A Inyección Sql

PHP

Seguridad

Gestión De Contraseñas Y Cifrado

PHP

Seguridad

Seguridad En Sesiones Y Cookies

PHP

Seguridad

Configuraciones De Php Para Seguridad

PHP

Seguridad

Introducción Al Testing En Php

PHP

Testing

Phpunit

PHP

Testing

Cobertura De Código En Testing

PHP

Testing

Test Doubles (Mocks, Stubs, Fakes, Spies)

PHP

Testing

Pruebas De Integración Y Funcionales

PHP

Testing

Accede GRATIS a PHP y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender la importancia de validar y filtrar la entrada del usuario.
  • Aprender a utilizar filter_var y htmlspecialchars para validar y sanitizar datos.
  • Implementar tokens anti-CSRF para proteger formularios.
  • Conocer prácticas de validación y seguridad del lado del servidor.