You are on page 1of 12

Validar y sanear datos en PHP

Tutorial en el que mostramos y enseñamos los métodos más efectivos a la hora de validar y sanear datos en PHP. Todo programador debería conocer este tipo de técnicas que permitirán mantener la integridad y seguridad de sus aplicaciones… ¡no os lo perdáis!

Introducción: la importancia de validar y sanear datos
Un punto crítico de cualquier tipo de aplicación es la entrada de datos por parte del usuario, ya que, si no se toman las medidas oportunas, un usuario puede (intencionadamente o no) causar fallos en la aplicación. En el mundo del desarrollo web, esto cobra mucha más importancia, pues se incrementa mucho el número de usuarios potenciales y por tanto, el número de usuarios malintencionados. Ataques conocidos y fáciles de llevar a cabo son SQL Inyection y XSS (Cross Site Scripting). En aplicaciones web es importante realizar siempre la validación en el lado del servidor. Otra vulnerabilidad que tiene que ver con la validación, que es debido muchas veces a la inexperiencia del desarrollador, es confiar la validación de lados al lado de cliente (Javascript). Esto es algo grave, pues existen herramientas para enviar datos a una web sin que intervenga para nada un navegador ni Javascript.

La extensión Filter
Esta extensión de PHP nos expone un conjunto de funciones que nos permitirán la validación y saneamiento de datos de una forma sencilla. La funcion filter_var nos permite filtrar una variable según el filtro especificado. El orden de los parámetros es el siguiente:
  

$var: Variable que se quiere filtrar $filter: Filtro que se desea aplicar. Será una constante numérica $options: Conjunto de opciones que modificarán el funcionamiento del filtro. Será una constante numérica o un array

A continuación un listado de filtros de validación posibles: FILTER_VALIDATE_BOOLEAN Valida la variable como un booleano. FILTER_VALIDATE_EMAIL Valida la variable como una dirección de correo electrónico correcta. FILTER_VALIDATE_FLOAT Valida que la variable sea del tipo float. FILTER_VALIDATE_INT Valida la variable como un número entero.

letras y $-_. en caso contrario. echo filter_var($var. A continuación el listado de filtros para sanear: FILTER_SANITIZE_EMAIL Elimina todos los caracteres execpto letras. FILTER_VALIDATE_URL Valida el valor coma una URL de acuerdo con la RFC 2396.{}|\\^~[]`<>#%”.eE.y opcionalmente . FILTER_VALIDATE_INT). La función nos devolverá el entero en caso de que la validación sea correcta. FILTER_SANITIZE_ENCODED Codifica la cadena como una URL válida. view plaincopy to clipboardprint? 1.[]. FILTER_SANITIZE_NUMBER_INT Elimina todos los caracteres excepto números y los signos + -./?:@&=. . FILTER_SANITIZE_MAGIC_QUOTES Aplica la función addslashes. FILTER_SANITIZE_SPECIAL_CHARS Escapa caracteres HTML y caracteres con ASCII menor a 32. <?php 2.FILTER_VALIDATE_IP Valida la variable como una dirección IP. números y !#$%&‟*+-/=?^_`{|}~@. FILTER_SANITIZE_STRING Elimina etiquetas.. Validar y sanear un número entero Vamos a ver un sencillo ejemplo de validación de un número entero.+!*‟(). FILTER_SANITIZE_STRIPPED Alias del filtro anterior. FILTER_SANITIZE_URL Elimina todos los caracteres excepto números. $var = 123. nos devolverá FALSE. opcionalmente elimina o codifica caracteres especiales. FILTER_SANITIZE_NUMBER_FLOAT Elimina todos los caracteres excepto números. FILTER_VALIDATE_REGEXP Valida la variable contra una expresión regular enviada en la variable de opciones. +. 3.

if(filter_var($var. 3. quitamos todo lo que no tiene cabida en nuestro campo. <php $var = '1. 4. $var = 1. 2. con este filtro tenemos una opcion interesante que nos permite especificar cual es el caracter que separa los decimales. <?php $var = 'dos'. if(filter_var($var. <php 2. En el siguiente ejemplo se muestra como validar un entero que esté dentro de un rango específico: view plaincopy to clipboardprint? 1. FILTER_VALIDATE_INT) === false){ echo 'Valor incorrecto'. . FILTER_VALIDATE_INT. 2. $options) === false){ echo 'Valor incorrecto'. 7. para los que no entiendan. echo filter_var($var. 3. $var = 'uno23'. 6. } A la hora de validar un entero. view plaincopy to clipboardprint? 1.3. FILTER_SANITIZE_NUMBER_INT). echo filter_var($var. podemos jugar con las opciones para hacer ajustar el comportamiento de la función a nuestras necesidades. Sin embargo. 4. view plaincopy to clipboardprint? 1. echo filter_var($var.')).view plaincopy to clipboardprint? 1. Validar y sanear un número float La validación simple de un float es practicamente igual que la de un entero: view plaincopy to clipboardprint? 1. 3. FILTER_VALIDATE_FLOAT). 4. 6. 5. 5. es decir. El código anterior nos devuelve ‟23′ debido a que elimina todos los caracteres no numéricos de la cadena. $options). 2. 7. FILTER_VALIDATE_FLOAT. 3. $options = array('options'=>array('decimal'=>'. 'max_range' => 20)). $options = array('min_range'=>10. 3. sanear significa limpiar. 4. 4. <?php 2. }else{ echo 'Valor correcto'. } Ahora vamos a ver como sanear un entero.3'. }else{ echo 'Valor correcto'.

Esta funcion nos permite obtener una variable externa por su nombre. filtrándola si es necesario. 4. 2. 3. //Resultado: "Ontuts &amp. view plaincopy to clipboardprint? 1. echo filter_var($text. $text = '"Ontuts & Cokidoo"'. 3. 4. FILTER_FLAG_STRIP_HIGH Elimina caracteres cuyo valor ASCII sea mayor a 127. FILTER_SANITIZE_STRING. echo filter_var($text. FILTER_FLAG_ENCODE_AMP Codifica ampersands (&). 4. <php $text = '<p>"Hola mundo!"</p>'. FILTER_SANITIZE_STRING. FILTER_FLAG_STRIP_LOW Elimina caracteres cuyo varlor ASCII sea menor a 32. FILTER_FLAG_NO_ENCODE_QUOTES). FILTER_FLAG_NO_ENCODE_QUOTES | FILTER_FLAG_ ENCODE_AMP). FILTER_FLAG_ENCODE_LOW Codifica caracteres cuyo valor ASCII sea mennor a 32. //Resultado: "Hola mundo!" view plaincopy to clipboardprint? 1. <php $text = '<p>"Hola mundo!"</p>'. A continuación algunos ejemplos: view plaincopy to clipboardprint? 1. <php 2.Hola mundo!&#34.Saneamiento de textos La limpieza de los textos es algo muy importante y por ello tenemos varios flags que modifican el comporamiento del filtro FILTER_SANITIZE_STRING: FILTER_FLAG_NO_ENCODE_QUOTES No codificará las comillas simples ni dobles. echo filter_var($text. FILTER_FLAG_ENCODE_HIGH Codifica caracteres cuyo valor ASCII sea mayor a 127. FILTER_SANITIZE_STRING). 5. 2. . //Resultado: &#34. 3. Cokidoo" Obtener variables externas Además de la función filter_var. existe una muy interesante llamada filter_input.

'component' => array('filter' => FILTER_VALIDATE_INT. cosa que antes se hacía bastante engorrosa. 'options' => array('min_range' => 1. ). 7. 11. Es decir. $_COOKIE…) las obtengamos a través de esta función para ahorrarnos disgustos. if($page !== null && $page !== false){ //El parametro ha sido enviado y es un entero }else{ //El parametro no se ha enviado o no es un entero } Esta función nos devolverá NULL si la variable no ha sido enviada. 'max_range' => 10) 7. 10. 5. $_POST. 6. 'page'. ). <php $page = filter_input(INPUT_GET. que nos llegan desde el lado del cliente ($_GET. El primer parámetro indica de que ámbito será extraída la variable. <?php 2. Esta función devuelve un array del tipo clave/valor que contiene los nombres de los campos junto con su filtrado. FILTER_VALIDATE_INT).Es importante que todas las variables externas. La definición de los campos con sus validaciones se hace mediante un array como se muestra en el siguiente ejemplo: view plaincopy to clipboardprint? 1. FALSE si la variable ha sido enviada pero no validada o el valor de la variable si ha sido enviada y validada. 4. PHP nos brinda una gran librería con la cual podemos llevar a cabo validaciones y saneamientos en cuestión de segundos. y puede ser uno de los siguientes valores:      INPUT_GET INPUT_POST INPUT_COOKIE INPUT_SERVER INPUT_ENV Otra función relacionada y muy interesante es filter_input_array que nos permite definir una serie de validaciones para múltiples campos. es decir. 8. $myinputs = filter_input_array(INPUT_POST. 6. 'product_id' => FILTER_SANITIZE_ENCODED. 'flags' => FILTER_REQUIRE_ARRAY. Conclusión Como puedes observar. 3. 4. 'versions' => FILTER_SANITIZE_ENCODED 9. 2. $args). 8. En el siguiente ejemplo vemos como obtener un parámetro GET de la petición con esta función: view plaincopy to clipboardprint? 1. . 5. podemos hacer la validación todos los campos de un formulario en un solo paso. $args = array( 3.

Creando una capa de conexión abstracta a base de datos con PHP Introducción Hasta hace muy poco. necesitamos crear una clase base que defina qué es un proveedor y qué funciones va a poder llevar a cabo. por lo tanto. se extenderá de dicha clase. Parámetros en consultas: se gestionarán de forma fácil los parámetros que se envían en las consultas. luego. La verdad. usaba una librería de PHP llamada ADOdb Lite para abstraerme un poco de la base de datos en mis proyectos. Instancia única: nuestro código será construído usando el patrón de diseño Singleton. Mediante la librería nativa de PHP Mysqli podemos ejecutar procedimientos definidos previamente en nuestra base de datos. Hasta que un día me encontré con la librería Mysqli (MySQL Improved Extension) que es una librería nativa de PHP que nos permite hacer muchas más cosas. si no que la comencé a usar porque siempre uso procedimientos y funciones de MySQL en mis modelos y las funciones de la librería nativa de PHP no me permitían hacer llamadas a estos (no se ahora. A mí ya me ha pasado en un proyecto tener que cambiar de PostgresSQL a MySQL. se puede hacer desde la cosa más sencilla (lo que vamos a ver en este tutorial) hasta cosas realmente complejas como doctrine. Desde hace unos pocos años la empecé a utilizar y desde entonces la carpeta adodb_lite se iba copiando de proyecto en proyecto casi de manera inconsciente. a cambiar código por todos lados. no me importa escribir un poco más de código mientras tenga control total con la aplicación. Paso 1: Crear la estructura de nuestro proveedor Como ya he comentado anteriormente.Ahora no hay excusa que valga para asegurarnos de que nuestros datos estén bien limpios y seguros. cada tipo de proveedor que necesitemos. Autocargar en array: si ejecutamos consultas de extracción de filas. nuestra capa será abstracta al tipo de proveedor de bases de datos. nunca sabes cuándo puedes cambiar de tipo de servidor (base de datos). lo que nos garantizará que sólo se establece una conexión con la base de datos. ¿para qué más?. Supongo que es cuestión de gustos y del tipo proyecto…a mí personalmente me gustan las cosas sencillas y potentes. Por desgracia fue en mi primer proyecto y todavía no sabía estas cosas. Bueno pues… ¡vamos a ello! ¿Qué vamos a hacer? Antes de nada os explicaré un poco qué características va a tener nuestra capa de comunicación:     Abstracción del tipo de proveedor: esto es algo básico. Para cumplir con esto. En realidad. Lo vemos con este ejemplo: . ejecutar procedimientos de la base de datos . que en esto. antes no). Fue en ese momento cuando decidí borrar la librería AdoDB Lite para crear mi propio código de comunicación con la base de datos. ¿vosotros que preferís?. no era por ser vago y no querer desarrolar mi propio código. nuestra capa nos devolverá dicha información cargada en un array. entre otras.

2. public abstract function escape($var). //Obtiene el texto del error 10. //Obtiene el número del error 8. $pass. 12. tenemos que crear el proveedor. { 3. 10. public abstract function isConnected(). 7. public abstract function getError(). public abstract function query($q). 8. 9. que es el básico: ejecución de consultas y control de errores. una vez entendida la teoría. $user. public abstract function fetchArray($resource). 1. 15. protected $resource. tanto nos dá si $ob es de la clase MysqlProvider o SQLServerProvider. 13. //Comprueba si está conectado 16. Oracle… no tendremos que rescribir todo el código creado anteriormente. 3. 5. 13. 11. Simplemente se define una clase abstracta para definir lo que todos sus descendientes deben de implementar. De modo que si hacemos algo como $ob->foo(). vamos a definir realmente nuestra clase base para los proveedores. sabemos que dicho método tiene que existir y que en teoría debería hacer lo mismo en un motor u otro de base de datos. no importa cuál sea el motor de base de datos: MySQL. vosotros lógicamente lo podréis ampliar cuanto queráis. Para ello tenemos que heredar de la clase anterior e implementar los métodos: . 5. 6. Paso 2: Crear nuestro proveedor Una vez creada la base.1. 19. public abstract function getErrorNo(). //Guarda internamente el objeto de conexión 4. Al crear nuestra propia capa de comunicación con la base de datos. $dbname). será abstracta a nuestro motor. abstract class DatabaseProvider 2. los métodos son todos abstractos para que se implementen en sus clases hijas. yo he incluído el siguiente esquema. 17. 11. Pues bien. ¿no?. public abstract function connect($host. 4. //Escapa los parámetros para prevenir inyección 18. 7. //Convierte en array la fila actual y mueve el cursor 14. abstract class DatabaseProvider{ /*Definicion de que métodos y propiedades van a tener todos los proveedores*/ } class MySqlProvider extends DatabaseProvider { /*Implementación de los métodos definidos en la clase base*/ } class SQLServerProvider extends DatabaseProvider { /*Implementación de los métodos definidos en la clase base*/ } No hace falta entender mucho de POO para entederlo. 9. así nos aseguramos de que todos los proveedores que hagamos tengan la misma estructura. } Como podéis ver. //Se conecta según los datos especificados 6. //Envía una consulta 12. PostgreSQL.

private function replaceParams($coincidencias){} 15. 5. Esto es porque aún nos falta por crear una nueva clase que se encargará de enviar las consultas y gestionar las conexiones y respuestas del servidor. public function execute($q.$q). public function isConnected(){ 20. private function prepare($sql. return mysqli_query($this->resource. public function getErrorNo(){ 8. return $this->resource. } . } 19. Paso 3: Crear nuestra capa de comunicación Lo hecho hasta ahora está muy bien. public function getError(){ 11. //Funcion del Singleton que devuelve o crea la instancia 12. return mysqli_errno($this->resource). //Envia la consulta al servidor 18. 21. pero lo único que nos permite es abstraer el tipo de proveedor. public function executeScalar($q. private static $_con. 9. { 3. public function connect($host. //Usado para las callbacks. private function sendQuery($q. //Constructor privado 10. $pass. $user. Os pongo la estructura de la clase para que le echéis un ojo: 1. $pass. $params){} 17. 6. //Ejecuta una consulta. 5. } 7. private function __construct($provider){} 11. $params){} 19. 9. todas las funciones definidas en nuestra clase base. $params=null){} 21. //Almacena internamente el proveedor 4. class MySqlProvider extends DatabaseProvider 2. return mysqli_error($this->resource). } 22. $dbname). 7. return mysqli_real_escape_string($this->resource. } 10. } Me he enfocado en el proveedor de MySQL. } 25. Como podéis ver. public function fetchArray($result){ 17. //Funcion callback. el resto de puntos que he comentado al inicio todavía están si cumplir. //Se encarga de poner los parámetros en su sitio 16. se explica luego 14. public static function getConnection($provider){} 13. private $provider. que supongo que es el más usado. } 13. public function query($q){ 14. 24. se explica luego 6. $this->resource = new mysqli($host. //Ejecuta una consulta y devuelve un array con las filas 22. 15. 18. por lo que se puede decir que funciona a modo de wrapper. //Almacena la instancia para el Singleton 8. public function escape($var){ 23. extrayendo solo la primera columna de la primera fila 20. class DatabaseLayer 2. return !is_null($this->resource). return mysqli_fetch_array($result). tienen su correspondiente función en la librería Mysqli.$var). { 3. private $params. $dbname){ 4.1. } 16. $params=null){} 23. 12. $user.

Patrón singleton La implementación de este patrón nos asegura que solo un objeto de dicha clase es creado.Antes de continuar. } else{ $class = __CLASS__. return self::$_con. 15. En el constructor creamos una conexión a la base de datos y se comprueba si se ha establecido. 17. private function sendQuery($q. 14. } . Veamos un ejemplo: 1. Ahora vamos por pasos. /*Controlar errores*/ 6. private function __construct($provider){ 2. rellenendo lós métodos con la funcionalidad requerida."). self::$_con = new $class($provider). 19. 4. $params){ 2. 8. 11. } } sendQuery Este método se encarga de enviar las consultas y comprobar errores: 1. en este caso getConnection. 10. 9. array(5. 13. return $result. throw new Exception("El proveedor especificado no ha sido implentado o añadido. if(!class_exists($provider)){ 3. "tuPassword". $params). 12. 4. $bd->execute("SELECT * FROM foo WHERE id=?". 5. 18. if($this->provider->getErrorNo()){ 5. 16."usuarioBaseDatos". $query = $this->prepare($q. "tuBaseDatos"). } 7. 20. Para que el código permanezca limpio y poder escapar los parámetros (para prevenir inyecciones maliciosas). } $this->provider = new $provider. Para ello tenemos que poner el constructor como privado y llamarlo desde una función estática. $this->provider>connect("localhost". de forma que serán insertados en la consulta reemplazando al caracter „?‟. $result = $this->provider->query($query). if(!$this->provider->isConnected()){ /*Controlar error de conexion*/ } } public static function getConnection($provider){ if(self::$_con){ return self::$_con. 8. 6. los parámetros serán especificados en un array. 7. array(5)) 2. 1. 3. $bd>executeScalar("SELECT id FROM foo WHERE id=? AND name like '%?%'")."ivan")). es importante ver cuál será el formato de las consultas.

'. que nos permite ejecutar una funcion para cada coincidencia encontrada. 6. return $b. 5. En este caso la callback es replaceParams (la cual definiremos a continuación). $params). if(!is_object($result)){ 5. $params[$i] = $this->provider->escape($params[$i]). 10. 14. $params[$i]). $params[$i] = "NULL". 9. ya que es el que se encarga de limpiar los parámetros para prevenir errores típicos e insertarlos en su sitio correspondiente. } 15. if(is_bool($params[$i])){ 4. $sql). 3.prepare Este método privado es importante. mientras que la segunda simplemente devuelve la primera columna de la primera fila: 1. $q = preg_replace_callback("/(\?)/i". 5. 8. 16."replaceParams"). if(!is_null($result)){ 4.$i<sizeof($params). $params){ 2. elseif(is_numeric($params[$i])) 9. 20.'. for($i=0. $this->params = $params. $params=null){ 2. 10. } 6. return $row[0]. private function prepare($sql. elseif(is_double($params[$i])) 7. public function executeScalar($q. 18. Para reemplazar se usa una expresión regular mediante la funcion preg_replace_callback(). Para hacer esto se usa la función next() que va moviendo el cursor del array para cada coincidencia. 17. else{ 8. } . return $result. 19. } execute y executeScalar Estas son las funciones que vamos a usar públicamente para realizar consultas y son muy parecidas. array($this. } 7. next($this->params). 4. $i++){ 3.$this->provider->escape($params[$i]). private function replaceParams($coincidencias){ 2.'. else 13. 1. $params[$i] = $params[$i]? 1:0. } replaceParams Esta es la función llamada por preg_replace_callback() y que se encarga de devolver el parámetro especificado para cada interrogante (?) encontrado en la consulta. 12. $row = $this->provider->fetchArray($result). $b=current($this->params). elseif(is_null($params[$i])) 11. $result = $this->sendQuery($q. 3. 1. $params[$i] = str_replace('. return $q."'". Tan solo se diferencian en que la primera recorre el resultado cargándolo en un array. $params[$i] = "'".

} 12. 4.email FROM users WHERE name like ? LIMIT 20". puede estar el boton en la misma ventana.getElementById(“btnImprimir”). if(is_object($result)){ 17. 18. no es necesario abrir otra ventana. $arr = array(). } 23. ¡Un saludo y hasta el próximo tutorial! Imprimir sin botones javascript Para imprimir la pagina sin que aparezca el boton imprimir.11. 3. } 21.array(true))). return $arr. $params=null){ 15. 16. return null. 20. 25. 22.style. pero yo hago lo siguiente: mando a llamar una funcion en JavaScript y en esa funcion esta el truco: <input name=”btnImprimir” id=”btnImprimir” type=”button” value=”Imprimir” onClick=”imprime()”> Y el codigo de la funcion imprime() es: function imprime(){ //desaparece el boton document. while($row = $this->provider->fetchArray($result)){ 19. 13. //Imprime un valor númerico 6. echo($db->executeScalar("SELECT count(*) FROM users WHERE active=?". } 14. 5. siempre y cuando sepáis lo que estás haciendo. No olvidéis dejar un comentario para cualquier duda.display=‟none‟ //se imprime la pagina . } Paso 4: Probarlo Una vez hecho todo lo anterior la forma de uso es muy sencilla: 1. Como siempre. public function execute($q. return null. //Como parámetro va el nombre del proveedor que queréis cargar $db = DatabaseLayer::getConnection("MySqlProvider"). 2. //Imprimiría la estructura del array print_r($db>execute("SELECT id. $result = $this->sendQuery($q. $arr[] = $row. con el que pretendo demostraros que no hay que tener miedo a crear tus propias librerías. fallo o valoración. Reflexión final Antes de despedirme recordaros que os podéis descargar el código completo en la zona superior de la página. $params). 24.array("ontuts%"))). espero que la información compartida en este tutorial os resulte útil.

hay que poner id=”flotante” style=”display:none.getElementById(“flotante2″). div2 = document. tenga un nombre diferente.style.print() //reaparece el boton document.getElementById(“flotante”).style.display=”none”. div2.display=‟inline‟ } Ocultar y mostrar capas ( div . div2 = document.getElementById(“btnImprimir”).” o en el div o en table. div2. div.getElementById(“flotante2″).style.display = “”. table ) javascript Funciona tanto para un div como para un table.display = “”.display=”none”.style. El ejemplo está puesto para flotante y flotante2.getElementById(“flotante”). <script> //codigo en javascript para ocultar y mostrar capas function mostrardiv() { div = document.window. div. } function ocultardiv() { div = document. a mí personalmente me gusta que cada id.style. } </script> Luego creamos los botones para mostra u ocultar <table border=”0″> <tr> <td> <input type=”button” value=”Mostrar” onclick=”mostrardiv()” /> <input type=”button” value=”Ocultar” onclick=”ocultardiv()” /> </td> </tr> </table> .