Román Cortés

Inyecciones SQL

27 de Octubre del 2008

En el post anterior sobre seguridad web hablé sobre las inyecciones de código e incluí un ejemplo ejemplo de inyección SQL. Hoy voy a ampliar la información sobre este tipo de inyección, explicando los métodos para evitarla.

Una de las formas de evitar la inyección SQL es validar los datos enviados por el usuario antes de realizar cualquier acceso a la base de datos; es decir, comprobar que los valores estén dentro del rango permitido por la web. Un ejemplo concreto sería una web que dispusiese de varias secciones de noticias, agrupadas en varias categorías, y que cada sección mostrase un listado de dichas noticias en su categoría.

Para ello podríamos tener una tabla noticias en la que uno de los campos fuese categoria. Supongamos que dicha categoría pudiese tomar 3 valores, por ejemplo; internacional, nacional y deportes. En este caso, el dato recibido desde el usuario sería la categoría a la que quiere acceder (podría llegar desde cualquier vía, como ejemplo por GET: www.dominio.com/noticias.php?categoria=deportes) . Si simplemente tomamos dicho dato y hacemos un SELECT * FROM noticias WHERE categoria=’$categoria’, tenemos la posibilidad de una inyección, así que antes de realizar el acceso a la base de datos, comprobaríamos que $categoria fuese uno de los 3 posibles valores, y de no ser así, terminar la ejecución del script, o mostrar una página 404 o a una página por defecto (el listado de categorías de nuevo, por ejemplo. Yo recomiendo la opción del 404, aunque no por motivos de seguridad… otro día escribiré un artículo al respecto).

No siempre se pueden validar los datos de esta forma, en bastantes ocasiones el rango permitido es demasiado amplio y/o no es fijo (por ejemplo, si se tratase de una búsqueda en los títulos de las noticias, el acceso a la base de datos sería necesario). Para este tipo de casos, mucho más frecuentes, se pueden usar dos alternativas: sanear los datos o usar sentencias preparadas.

Sanear los datos

Se trata de agregar a los datos enviados por el usuario los caracteres necesarios de escape para que la base de datos diferencie entre la sentencia SQL y los datos. Un ejemplo concreto: supongamos una tabla llamada grupos de grupos musicales en la que tengo un campo nombre. Quiero insertar al grupo Guns N’ Roses. Dado que contiene una comilla no podría hacerlo directamente, así que tendría que agregar el caracter de escape (contrabarra en MySQL) antes de la comilla, es decir: Guns N\’ Roses.

PHP dispone de varias funciones para ello. Una de ellas es general y no depende de ninguna base de datos en concreto; addslashes. Las otras son dependientes de la base de datos que usemos, en MySQL en concreto es mysql_real_escape_string.

El uso de addslashes es muy frecuente y aparece en muchos ejemplos en internet, pero tiene tres posibles problemas, dos de seguridad y uno de integridad de datos. Uno de ellos es que si la directiva de PHP magic_quotes_sybase está activada y lo usamos con una base de datos que no sea Sybase, entonces los caracteres no serán reemplazados correctamente (en principio esta directiva no tiene por qué estar activada si tenemos el hospedaje más común en php/mysql, pero si no se comprueba no se puede saber cual es su estado). Otro problema es que addslashes no contempla todos los tipos de escape que MySQL requiere para ficheros binarios, no es un problema de seguridad como tal, pero sería un problema de integridad de datos a la hora de insertar, por ejemplo, ficheros directamente en la base de datos.

Por último, existe un problema con addslashes cuando una tabla en la base de datos está creada para ciertos tipos de codificación de caracteres multibyte, por ejemplo para el chino simplificado GBK. Addslashes funciona byte por byte, agregando la contrabarra siempre que encuentre un valor de byte que se corresponda con los que escapa. En GBK existen caracteres que ocupan más de un byte y cuyo último byte se corresponde con la contrabarra, por ejemplo: ¿\  (nota: si estos dos caracteres se mostrasen en GBK, se vería un caracter chino). Si aplico addslashes a ¿’ obtengo ¿\’ . Al realizar un acceso a la base de datos en la tabla GBK, leerá un caracter multibyte y una comilla, y aquí tenemos el problema. Si la codificación es latin-1 o UTF-8 no hay problema alguno.

Mysql_real_escape_string, pese a tener menos problemas que addslashes, también está sujeto a los problemas de codificación multibyte. En las versiones más recientes de MySQL ya está corregido (a partir de 5.0.22), pero en las anteriores trabajar con codificaciones multibyte que no sea UTF-8 puede dar problemas similares al descrito en el párrafo anterior.

Sentencias preparadas

La mejor forma de evitar los problemas descritos anteriormente, es usando de las sentencias preparadas. Mediante las sentencias preparadas, separamos la consulta SQL de los datos, enviándolos de forma separada. Para usar sentencias preparadas en PHP con MySQL, se requiere la extensión mysqli, que no se encuentra instalada por defecto (aunque estará disponible normalmente en la mayoría de los hostings). Mediante mysqli_prepare podemos crear nuestras sentencias preparadas.

Para no tener problemas con caracteres multibyte que no sean UTF-8, además de usar mysqli con sentencias preparadas, hay que comprobar que la versión de MySQL sea reciente. Si la versión no es reciente, entonces no dispondrá de soporte para sentencias preparadas y mysqli hará una simulación de dichas sentencias usando mysql_real_escape_string, teniendo exactamente el mismo problema que si se usase directamente.

Un problema reciente con MySQL

Desde hace pocas semanas se ha mostrado un nuevo problema con MySQL. Cuando realizamos un SELECT con una comparación de cadenas, los caracteres de espacio al final de la comparación no son tenidos en cuenta. Por otro lado, si en un INSERT los datos para un campo de texto ocupa más espacio que el tamaño máximo para dicho campo, será recortado. Supongamos web con registro de usuarios y una tabla usuarios con nombre (de 15 caracteres) y password:

Cuando un usuario solicita registrarse, primero compruebo que el nombre de usuario no exista. Si no existe, realizo el insert en la base de datos.

Supongamos que ya existe un usuario con nombre admin. Alguien intenta registrarse con nombre “admin          1″ (admin seguido de 10 espacios y un 1). Al realizar el select para comprobar si existe el usuario, dado que el tamaño máximo para nombre es 15 y mi entrada tiene 16 caracteres, no devolverá ningún resultado, por tanto supondrá que dicho usuario no existe, y procederá a la inserción. En la inserción, el dato será recortado, quedando como admin seguido de 10 espacios.

Al registrarme en la web, podré usar directamente “admin”, sin espacios pero con la clave que ya conozco, dado que un select no tendrá en cuenta los espacios. Si más tarde el script hace otra consulta a la tabla de usuarios usando sólo el nombre “admin”, el resultado que devolverá será el primero que encuentre, es decir, el original.

Bien, para que todo esto resulte un problema, se tienen que dar una serie de circunstancias muy concretas, pero es un riesgo de seguridad en cualquier caso. Las medidas a tomar son las de medir la longitud máxima por cada dato que envía el usuario.

Nota: si el nombre de usuario es primary key no hay problema, dado que MySQL no permitiría la inserción como ocurriría en un campo que no lo fuese. Por otro lado, si no es primary key, pero no tenemos en cuenta el nombre de usuario sino su id durante el resto de la ejecución del script, tampoco sería un problema.

Para finalizar, magic_quotes_gpc

PHP tiene una opción (magic_quotes_gpc) mediante la cual los datos recibidos desde el usuario (get/post/cookie) son pasados por addslashes antes de la ejecución del script. Esta opción está desaconsejada y en la versión 6 de PHP ha sido eliminada, pero aún así, por motivos de compatibilidad con código ya existente, los servidores de hosting suelen seguir teniéndola activada.

En el caso de que esté activada, para no tener datos escapados por duplicado, tendremos que comprobar si están activadas mediante get_magic_quotes_gpc, y en el caso de que lo estén, realizar un stripslashes para eliminar el escape. Si usamos consultas preparadas usaremos los datos tras estos pasos directamente, y si no las usamos, las volveremos a escapar mysql_real_escape_string.

La técnica de sprites CSS

26 de Octubre del 2008

Empiezo a recuperarme de la gripe que ha hecho que no escriba durante la última semana, así que vuelvo a la carga con el blog.

Revisando mis posts anteriores he descubierto que se me olvidó hablar de los sprites css en mi post Webs más rápidas. Es una técnica que consiste en agrupar varias imágenes en una sola de mayor tamaño, para luego volver a separarlas en imágenes individuales mediante CSS. La ventaja es que reduciremos el número de archivos de nuestra web, lo que probablemente mejorará la velocidad de carga (explicación detallada en el post de Webs más rápidas).

Uno de los ejemplos de uso de esta técnica lo aplica Google en sus resultados de búsqueda (ejemplo). Esta es la imagen que agrupa los sprites (imágenes más pequeñas):

(He agregado un borde en gris para que se aprecien mejor los bordes de la imagen)

Como podéis ver, tenemos el logotipo de Google y varios botones que se usan en diferentes secciones de la página de resultados. Existen al menos dos formas de usar estos sprites, es decir, de separar las imágenes. Una de estas formas es usando un div al que le definamos width y height del tamaño del sprite, overflow como hidden, una background-image que se corresponda con el fichero de imagen que contiene los sprites y un background-position definido en píxeles. Estos píxeles se contarán de forma negativa desde la coordenada superior izquierda. Podría ser también cualquier otro tipo de tag, por ejemplo un a, y en ese caso, al ser un elemento inline deberíamos definirlo como block para que funcione correctamente en todos los navegadores como elemento pulsable.

Por otro lado está el método que usa Google. Se trata de usar dos elementos embebidos. El primer elemento (por ejemplo, un div) define el tamaño en píxeles del sprite y overflow hidden. El segundo elemento es un img, que como src tiene a la imagen grande y como estilo position: relative, top y left en píxeles. Los desplazamientos también se harán de forma negativa al igual que en el anterior.

Esta última posibilidad tenía problemas con la primera beta de IE8, pero en la segunda beta (la más reciente cuando escribo este post) ya parece estar corregido el bug.

Google lo hizo bien… pero se puede mejorar

Las páginas de Google son siempre bastante ligeras y rápidas de carga, está claro que es uno de los puntos fuertes que tienen en su buscador y que trabajan bastante en ello. El uso de sprites CSS es un buen ejemplo de optimización al respecto; menor tiempo en la solicitud de archivos, menor número de paquetes enviados y recibidos (y por tanto menor cantidad de datos enviados y recibidos), etc.

En cualquier caso, su implementación no es la mejor posible. Dejando de lado la semántica (que brilla en su implementación por la ausencia), tiene un grave problema de accesibilidad; en concreto en la forma en la que han implementado la evaluación con estrellas:

Por cada producto evaluado, se muestra una puntuación de entre 0 y 5 estrellas, incluyendo saltos de media en media entrella. Para cada una de las estrellas han usado un sprite (un <p><img></p>). A cada una de las imágenes les han puesto un alt, de esta forma, al pasar sobre cualquier punto de la puntuación se muestra con texto la puntuación (desde IE). El problema es que en un navegador en braille o audio, cada puntuación será repetida 5 veces.

De entre las posibles opciones de mejora, he optado por una que fuese más accesible, con mejor semántica, ocupase menos espacio, fuese más simple de implementar y no tuviese ninguna desventaja. Aquí os la muestro:

Conociendo la forma en la que png comprime las imágenes, es fácil de entender que las repeticiones suelen comprimirse muy bien y apenas incrementan el tamaño del archivo. En este caso con 40 bytes más, tengo una formación de estrellas y una imagen levemente más grande. Nótese que el resto de imágenes permanecen idénticas.

De la forma en la que he agrupado las estrellas, tomando rectángulos de 5 estrellas de largo por 1 de ancho, puedo mostrar todas las posibilidades de puntuación diferentes. Así, un solo sprite será suficiente para toda la línea de estrellas, y por tanto un solo alt.

Al necesitar sólo un sprite por puntuación, y pensando en un listado de 10 productos (10 puntuaciones), reduzco el número de sprites al 20%, es decir, elimino 40 sprites. Esto hace un total de aproximadamente 5kb sin compresión. Por otro lado, aumento el código CSS en aproximadamente 350 bytes sin compresión. En total, para conexiones sin compresión, el tamaño en el que reduciría mi método la carga de la página sería de algo más de 4kb.

La mayoría de los navegadores actuales soportan compresión, así que este último dato es de poca relevancia. Mediante compresión zip, la diferencia es mucho menos apreciable; reducción de ~300 bytes en el html, adición de ~180 bytes en css y ~40 bytes en el png. Total: ~80 bytes más ligero con mi método.

Nota: los datos de reducción de tamaño sólo los he incluido de forma anecdótica, la única ventaja real de mi implementación es la de accesibilidad.

Inyecciones de código

19 de Octubre del 2008

Uno de los problemas de seguridad más frecuentes con los que se enfrentan las webs y sus desarrolladores son las inyecciones de código. Las inyecciones pueden ocurrir siempre que un lenguaje de programación intermedio (por ejemplo, PHP) procesa datos recibidos por el usuario para generar código en otro lenguaje (por ejemplo, SQL o HTML).

El código malicioso que causa la inyección se encuentra en los datos enviados por el usuario, es por ello que:

- Hay que desconfiar siempre de *todos* los datos recibidos. Se pueden recibir datos de muy diversas formas; vía post, get, cookies, url… Ningún dato externo es un dato fiable.
- Es necesario validar todos los datos recibidos con los que nuestro código vaya a interactuar.

EJEMPLO CLÁSICO DE INYECCIÓN
(en PHP y MySQL)

Para una web con un formulario de login para usuarios, tenemos dos campos: usuario y password. Nuestro código en PHP recibe ambos datos vía post y trata de comprueba si el usuario existe en la base de datos de la siguiente forma:

$usuario=$_POST[’usuario’];
$password=$_POST[’password’];
$sql=”SELECT * FROM usuarios WHERE usuario=’$usuario’ AND password=’$password’”;
$result=mysql_query($sql);

En principio este código parece correcto, pero podría dar lugar a una inyección. Por ejemplo, el usuario introduce en el campo usuario a y en el campo password a’ OR ‘1=1. Como resultado, la consulta $sql sería:

SELECT * FROM usuarios WHERE usuario=’a’ AND password=’a’ OR ‘1=1′

Dado que [expresión] OR ‘1=1′ siempre devuelve verdadero, nuestro sistema aceptaría el login con el primer usuario que devolviese la base de datos. Y eso no es todo; en MySQL, sólo se puede ejecutar una línea de sql en cada mysql_query(), pero en otras bases de datos es posible ejecutar varias separadas por comas, lo que podría dar lugar a inyecciones que, usando el punto y coma, creasen nuevas sentencias para eliminar una tabla, por ejemplo.

TIPOS DE INYECCIONES

El problema de las inyecciones no se limita al SQL; existen numerosos tipos de inyecciones de código, entre ellas de SQL, HTML/Javascript/Embed, Email… además de cierto tipo de inyecciones que no son de código pero pueden afectar, por ejemplo, al sistema de archivos. Detallaré los más frecuentes en los siguientes artículos.

En el próximo post sobre seguridad ampliaré las inyecciones SQL. Creo que es importante que lo haga de forma extensa, dado que recientemente han aparecido nuevos riesgos y aún no están lo suficientemente documentados (Wikipedia por ejemplo aún no los contempla).

-

Por si os gustó la ilustración de la jeringuilla y queréis reutilizarla para cualquier fin, os la dejo en .png.

Introducción a la seguridad web

17 de Octubre del 2008

Tal y como me habéis pedido, voy a escribir sobre seguridad web, orientada al desarrollo. Como es un tema muy extenso lo haré en varios posts y hoy comienzo con una pequeña introducción.

Lo primero que cabría indicar es que no hay ninguna web *totalmente* segura. Cualquier sistema conectado a internet está sujeto a posibles ataques desde dicho medio, algunos de ellos pueden ser contrarrestados y otros no. Por tanto, a partir de ahora, cuando escriba “web segura” se debería entender como web mínimamente segura.

Estos posibles ataques (también llamados riesgos de seguridad) pueden ocurrir a cualquier nivel y de muy diversas formas. El hardware en el que se hospeda una web, el sistema operativo, el software instalado, el código de la web en sí e incluso el diseño de internet como red son vulnerables. Dado que me centraré sólo en el desarrollo web, en los próximos posts sólo escribiré riesgos en ese ámbito, pero en esta introducción voy a dar algunos consejos generales para el resto:

- Mantener el sistema operativo y el software del servidor actualizado con versiones estables.
- Usar contraseñas fuertes (con suficiente longitud, que incluyan mayúsculas, minúsculas y números) para todo, no compartirlas y cambiarlas cada cierto tiempo.
- Realizar copias de seguridad periodicas.
- Sólo instalar software en el que se pueda confiar mínimamente. No todo el software de código abierto es mínimamente seguro. Un ejemplo concreto: Wordpress puede ser mínimamente seguro, pero eso no implica que todos los plugins para Wordpress lo sean. Una búsqueda en Google sobre algún software concreto y sus problemas de seguidad nos pueden ayudar a hacernos una idea.
- Comprobar bien el ordenador desde el que se accede con contraseña al servidor y mantenerlo libre de virus, spywares y malwares.
- Usar un firewall en el servidor

Si tenéis una cuenta de hosting (hospedaje) compartido, bastantes de estos consejos no los podéis aplicar directamente, dado que serían responsabilidad de la empresa de hosting, pero sí que podéis revisar el software instalado, comprobar vía Google si tiene problemas graves de seguridad, y de ser así, sugerir un cambio o actualización a dicha empresa.

Por hoy creo que esto es todo. En el próximo post hablaré de uno de los problemas más frecuentes en el desarrollo web: las inyecciones.

Problema de seguridad en Wordpress

16 de Octubre del 2008

Las últimas versiones de Wordpress han incluido seguridad adicional mediante 3 contraseñas (o llaves, keys) que se encuentran en el fichero de configuración wp-config.php:

define(’AUTH_KEY’, ‘put your unique phrase here’);
define(’SECURE_AUTH_KEY’, ‘put your unique phrase here’);
define(’LOGGED_IN_KEY’, ‘put your unique phrase here’);

Veo 2 problemas principales por los que esta seguridad adicional no debe estar surtiendo efecto alguno en bastantes instalaciones:

- El instalador de Wordpress, al crear automáticamente el fichero wp-config.php no solicita la inserción de dichas llaves, las deja tal cual, con “put your unique phrase here”.

- Si la configuración se realiza a mano, se puede olvidar y dejar la frase por defecto, y Wordpress lo acepta sin problema alguno.

Por tanto, probablemente una gran cantidad de instalaciones de Wordpress tengan las llaves por defecto, y ese es el equivalente a no tener en absoluto estas nuevas medidas de seguridad.

Dado que no he estudiado al completo el código de Wordpress (y que dios me libre de tener que hacerlo), no estoy totalmente seguro de la utilidad de dichas llaves. Supongo que servirán para alguna forma de MAC (código de autenticación de mensajes), pero no se si la adición de seguridad es para resolver algún tipo de problema real y ya existente, si es un aumento de seguridad ante algún posible ataque teórico practicable o no. De las tres posibilidades:

- Si es para solventar un problema ya existente, estamos ante un fallo de seguridad grave; Wordpress no debería permitir que se ejecutase con las claves por defecto.

- Si es para solventar un problema teórico pero practicable, espero que se tomen medidas en próximas versiones para que forzosamente haya que cambiar las claves por defecto.

- Por último, si es un problema teórico pero impracticable (a veces se toman demasiadas medidas de seguridad), entonces que se queden las llaves por defecto no es un problema de seguridad, pero sí un problema de gasto de recursos innecesarios.

Bien… por si las moscas, sólo puedo recomendaros que si usáis las últimas versiones editéis vuestro wp-config.php y agreguéis 3 contraseñas seguras… y darle así un uso a mi generador de contraseñas seguras.

Tutoriales

15 de Octubre del 2008

Hoy pensaba escribir un artículo/tutorial sobre KHMACs (códigos de autenticación de mensajes mediante hash con contraseña). No hay apenas información en castellano al respecto (¡ni siquiera en Wikipedia!) y la verdad es que son herramientas muy útiles y potentes para el desarrollo web, así que supuse que sería un buen artículo.

Al intentar estructurar el artículo, me di cuenta de que era demasiado complejo, muchos términos de seguridad y criptografía, así que pensé que sería mejor empezar con un artículo de introducción a la seguridad web y el uso de criptografía para ello.

Tras pensar esto me pregunté que si mis lectores estáis interesados o no en el tema. Para salir de dudas, he decidido preguntaros:
¿De qué temas os gustaría que escribiese artículos y/o tutoriales?

Algunas posibilidades:

- Desarrollo de webs seguras
- Introducción al diseño gráfico y tipografía
- CSS y HTML
- Photoshop
- PHP
- Javascript
- Fotografía digital
- Pintura digital

Tengo algunos conocimientos de varios temas más, pero supongo que no encajan demasiado bien en mi blog.

Espero que os animéis a escribir. Sería información útil también el nivel que os gustaría para cualquier tema, y si preferís explicaciones detalladas o series de ejemplos concretos. Gracias de antemano.

McCain+Obama=McCama

14 de Octubre del 2008

Más iconos

14 de Octubre del 2008

Siguiendo la serie de iconos iniciada con los iconos de compresión, hoy publico dos más; una carpeta y una hoja en blanco.

Tal y como me aconsejó sNebel en su blog, incluyo los ficheros en psd: carpeta y hoja en blanco.

Continuaré publicando iconos a medida que los vaya finalizando.

Un dibujito

13 de Octubre del 2008

Últimamente he dedicado muy poco tiempo al arte y ya me apetecía hacer algún dibujito. La imagen que véis es el resultado. Está a medio camino entre la ilustración artística y la pintura digital; es una mezcla de varios trabajos que tenía sin publicar.

Lo he titulado La Chenille Inquiétante en honor a Asger Jorn.

Como el resto de mis obras no comerciales, si os gusta y os interesa usarlo para cualquier fin, no es necesario que me pidáis permiso. Tampoco es necesario que se me de crédito (aunque se agradece).