Bienvenidos a un nuevo post en ByteMind. En este caso y bajo petición de algunos usuarios vamos a explicar como realizar un filtrado de datos de entrada en PHP obtenidos a través de peticiones GET y POST.
En estos casos es de vital importancia limpiar dichas variables de posibles inyecciones SQL o XSS que pudiésemos ser víctimas, evitando así posteriores dolores de cabeza ante accesos no autorizados en nuestro sistema.
Índice
¿Cómo aplicar esta limpieza de datos?
El método para aplicar esta sanitización de los datos es muy simple de realizar aunque si tienes formularios grandes o muchos puntos de entrada de datos puede llevarte bastante tiempo recorrer y filtrar cada uno de los campos existentes en tu portal.
Para ello vamos a explicar tres formas de realizar este filtrado: mediante el uso de filtros predefinidos, especificando la codificación de caracteres y mediante el uso de expresiones regulares.
Uso de filtros predefinidos
Las extensiones de filtrado de PHP permiten validar los datos, especialmente cuando vienen de fuentes externas. La extensión se divide entre filtros de validación, para comprobar que los datos cumplen ciertos requisitos, y filtros de saneamiento que serán los encargados de limpiar los datos de forma que se eliminen los caracteres no válidos.
Las banderas u opciones de filtrado sirven para modificar el comportamiento del saneamiento o la validación.
Entre las funciones de filtrado podemos encontrar:
filter_list
La función filter_list() devuelve un array con todos los filtros soportados.
1 |
array filter_list (void) |
filter_var
La función filter_var() filtra una variable $variable con el filtro indicado en $filter. El filtro por defecto FILTER_DEFAULT equivale a FILTER_UNSAFE_RAW, y no filtra realmente nada.
1 |
mixed filter_var (mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] ) |
Por ejemplo, si queremos validar que el valor existente en la variable $int es un entero podemos utilizar el filtro FILTER_VALIDATE_INT como vemos en el siguiente ejemplo:
1 2 3 4 5 6 |
$int = 0; if (filter_var($int, FILTER_VALIDATE_INT) === 0 || !filter_var($int, FILTER_VALIDATE_INT) === false) { echo("El entero es válido"); } else { echo("El entero no es válido"); } |
También podemos asegurarnos de que un string es un correo con el filtro FILTER_VALIDATE_EMAIL como en el siguiente ejemplo:
1 2 3 4 5 6 7 8 9 |
$email = "prueba@ejemplo.com"; // Primero eliminamos cualquier carácter que pueda dar problemas $email = filter_var($email, FILTER_SANITIZE_EMAIL); // Luego validamos el email if (!filter_var($email, FILTER_VALIDATE_EMAIL) === false) { echo("$email es una dirección de correo válida"); } else { echo("$email no es una dirección de correo válida"); } |
filter_var_array
La función filter_var_array() devuelve los datos del array $data y opcionalmente los filtra.
1 |
mixed filter_var_array (array $data [, mixed $definition [, bool $add_empty = true ]] ) |
$definition puede ser otro array que define filtros específicos para cada uno de los elementos de $data, o simplemente una constante de filtro, por ejemplo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$inputs = ["<a>leon</a>", "gato"]; $animales = filter_var_array($inputs, FILTER_SANITIZE_STRING); var_dump($inputs); /* Devuelve array (size=2) 0 => string '<a>leon</a>' (length=37) 1 => string 'gato' (length=4) */ var_dump($animales); /* Devuelve array (size=2) 0 => string 'leon' (length=4) 1 => string 'gato' (length=4) */ |
filter_input
La función filter_input() toma una variable externa de un determinado tipo y opcionalmente la filtra.
1 |
mixed filter_input (int $type, string $variable_name [, int $filter = FILTER_DEFAULT [, mixed $options ]] ) |
Estas variables pueden venir de $_GET, $_POST, $_COOKIE, $_SERVER, $_ENV, por lo que type deberá ser uno de los siguientes valores: INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER o INPUT_ENV.
Los filtros y opciones funcionan de la misma forma que en las funciones anteriores. Podemos ver un ejemplo en el siguiente código:
1 2 3 |
$busqueda = filter_input(INPUT_GET, 'buscar', FILTER_SANITIZE_SPECIAL_CHARS); $url = filter_input(INPUT_GET, 'buscar', FILTER_SANITIZE_ENCODED); echo "Has buscado $busqueda.\n"; |
filter_input_array
La función filter_input_array() obtiene variables externas de un determinado tipo (GET, POST…) y opcionalmente las filtra.
1 |
mixed filter_input_array (int $type [, mixed $definition [, bool $add_empty = true ]] ) |
Es igual que filter_input pero permite facilitar un array $definition para filtrar todos los valores de un array externo de vez.
filter_has_var
La función filter_has_var() comprueba si existe una variable en un tipo concreto (GET, POST, COOKIE…)
Constantes de filtrado
Además de las mencionadas en este post existe una larga serie de filtros existentes en php que puede verse en su página oficial.
A continuación dejamos una tabla con los más utilizados:
Constante | Validación |
FILTER_VALIDATE_BOOLEAN | Valida un booleano |
FILTER_VALIDATE_EMAIL | Valida una dirección de email |
FILTER_VALIDATE_FLOAT | Valida un float |
FILTER_VALIDATE_INT | Valida un integer |
FILTER_VALIDATE_IP | Valida una dirección IP |
FILTER_VALIDATE_REGEXP | Valida una expresión regular |
FILTER_VALIDATE_URL | Valida una URL |
FILTER_SANITIZE_EMAIL | Elimina caracteres peligrosos de una dirección de email |
FILTER_SANITIZE_ENCODED | Elimina caracteres especiales |
FILTER_SANITIZE_MAGIC_QUOTES | Hace lo mismo que la función addslashes() |
FILTER_SANITIZE_NUMBER_FLOAT | Elimina todo menos dígitos, +, – y opcionalmente .,eE |
FILTER_SANITIZE_NUMBER_INT | Elimina todo menos dígitos |
FILTER_SANITIZE_SPECIAL_CHARS | Elimina caracteres especiales |
FILTER_SANITIZE_STRING | Elimina etiquetas y caracteres especiales de un string |
FILTER_SANITIZE_STRIPPED | Alias de FILTER_SANITIZE_STRING |
FILTER_SANITIZE_URL | Elimina caracteres peligrosos de una URL |
FILTER_UNSAFE_RAW | No hace nada, igual que FILTER_DEFAULT |
FILTER_CALLBACK | Llama a una función callback definida por el usuario |
Especificar la codificación de caracteres
La sanitización de datos o data sanization consiste en la modificación de los inputs de entrada de forma que se eliminen algunos caracteres indeseables, normalizando los datos para su posterior uso de forma segura.
Supongamos que tenemos un sistema de búsqueda en el que mostramos los resultados de la cadena buscada:
1 2 3 |
if(isset($_GET['query'])){ echo '<p>Los resultados de ', htmlspecialchars($_GET['query'], ENT_QUOTES), ' son: </p>'; // Código para mostrar los resultados } |
Con la función utilizada, htmlspecialchars() recudimos el riesgo, pero sigue siendo posible la realización de ataques XSS. Para limitarlo aún más podemos utilizar ENT_QUOTES con el cual se consigue filtrar las comillas simples y dobles, aunque todavía es posible realizar ataques.
Para asegurarnos de la fiabilidad de la protección debemos utilizar la misma codificación de caracteres definida en el documento HTML, de esta forma reduciremos más los riesgos. Veamos un ejemplo a continuación:
1 2 3 4 |
// Definimos la cofificación en la cabecera header('Content-Type: text/html; charset=UTF-8'); // Definimos la codificación utilizada en el filtrado: echo htmlspecialchars($_GET['query'], ENT_QUOTES, 'UTF-8'); |
Uso de expresiones regulares
En cualquier formulario de cualquier aplicación siempre hay que validar los datos que son introducidos por los usuarios.
Para realizar esta validación de una forma más precisa y compleja se utilizan expresiones regulares. Para ello, PHP, dispone de la función preg_match() que exige dos parámetros obligatorios, la expresión regular a utilizar y el string que se debe de comprobar. A continuación vamos a verlo con un ejemplo que asegura que el nombre sean sólo letras y el email tenga un formato de correo adecuado:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<?php $errores = array(); if(isset($_POST['enviar'])) { // Verificamos que se ha introducido un nombre if (empty($_POST["nombre"])) { $errores[] = "El campo nombre es obligatorio <br>"; } else { $nombre = $_POST["nombre"]; // validamos que el campo nombre contiene sólo letras if (!preg_match("/^[a-zA-Z]+/", $nombre)) { $errores[] = "Sólo se permiten letras como nombre de usuario <br>"; } } // Verificamos que se ha introducido el correo if (empty($_POST["email"])) { $errores[] = "El campo email es obligatorio <br>"; } else { $email = $_POST['email']; // validamos que el formato del correo sea adecuado if (!preg_match("/([\w\-]+\@[\w\-]+\.[\w\-]+)/", $email)) { $errores[] = "Formato de email incorrecto"; } } // En caso de haber errores, los mostramos if(empty($errores)) { echo "Nombre: $nombre <br>"; echo "Email: $email <br>"; } else { var_dump($errores); } } |
Mediante el uso de expresiones regulares se consigue un nivel de precisión muy alto que valide los requisitos de cada campo y los caracteres permitidos. De todas formas, PHP, tiene una extensión de filtrado, que ya vimos anteriormente y con la que es posible realizar filtros de forma más rápida para la validación y saneamiento de los datos.
Y hasta aquí es todo por ahora. Espero les ayude en su día a día y a desarrollar un código más seguro. Como siempre si tienen alguna duda o aportación, pueden hacerlo a través de los comentarios.