Entradas etiquetadas como ‘SQL’
Concurrencia optimista en bases de datos

Red de datos
Para solventar esta traba a la hora de actualizar una base de datos se pueden esgrimir dos recursos o modelos: la concurrencia pesimista y la concurrencia optimista. Los objetos que manipulan datos en los distintos lenguajes de programación están preparados para manejar ambas técnicas, por lo que no nos va a resultar nada complicado aplicar una u otra. Nosotros apostamos desde siempre por la concurrencia optimista, sobre todo en determinados entornos, pero explicaremos brevemente en que consisten los dos procedimientos.
La concurrencia pesimista implica bloquear filas, o registros, en el origen de datos para impedir que otros usuarios modifiquen la información, de tal forma que el usuario actual resulte afectado. En un modelo pesimista, cuando un usuario realiza una acción que hace que se aplique un bloqueo, otros usuarios no pueden realizar acciones que entrarían en conflicto con ese bloqueo hasta que el propietario del mismo lo libere. Este modelo se utiliza principalmente en aquellos entornos en los que hay mucha contención de datos, de manera que el costo de protegerlos mediante bloqueos es menor que el costo de deshacer transacciones si se producen conflictos de concurrencia.
La concurrencia pesimista puede resultar útil en ambientes donde los tiempos de bloqueo son cortos, por ejemplo la actualización automática de registros, según determinadas reglas, mediante software. Sin embargo, es una técnica que consume recursos del servidor, necesita de una conexión persistente con el gestor de datos y no resulta nada escalable cuando los usuarios, interactuando con los datos, hacen que los registros queden bloqueados durante períodos de tiempo relativamente largos (mientras se modifican las líneas de una factura, por ejemplo).
Por el contrario, utilizando el sistema de concurrencia optimista no se bloquean filas cuando se lee, sino que se realizan copias locales desconectadas de los datos y, una vez se hayan editado, se vuelca la nueva información sobre la base de datos original. En este caso se produce el problema al que aludíamos al principio, ya que en el tiempo que nosotros hemos estado modificando los datos, otro usuario ha podido acceder al registro y haber realizado sus propias modificaciones. Es, entonces, la aplicación informática la que debe determinar si la información se han modificado o no desde que se leyó. Esta técnica, aunque parezca más farragosa de implementar, mejora el rendimiento del sistema y la velocidad de acceso de todos usuarios, ya que el origen de datos no se encuentra nunca bloqueado y puede servir información continuamente y bajo demanda.
En el momento en que un equipo intente modificar determinados registros y detecte que han sido ya modificados con respecto a la información original que tenía desde que se leyó, en el modelo de concurrencia optimista se considera que hay una infracción. La pericia del programador consistirá en detectar dichas infracciones y saber actuar a tal efecto, dependiendo del proceso que se esté realizando y de las circunstancias puntuales. Esto es, una infracción puede resolverse sobrescribiendo los nuevos datos o manteniendo las modificaciones sin realizar. En función de las características del proyecto y de las necesidades del cliente habrá que hacer una u otra cosa.
Vamos a ver un ejemplo práctico haciendo uso de una pequeña tabla de tres campos y un único registro. La tabla original podría ser como la siguiente:
| IdCliente | Apellido | Nombre |
| 101 | Martínez | Cris |
Imaginemos que un Usuario_1 lee la fila anterior de la base de datos a las 12:00 AM. Veamos una representación gráfica de los tres valores que nos ocupan: el valor original (valor cuando se leyó el registro), el valor actual (el que modifica Usuario_1 en local) y el valor en la base de datos (el valor que se encuentra registrado en la tabla).
| Nombre de columna | Valor original | Valor actual | Valor en la base de datos |
| IdCliente | 101 | 101 | 101 |
| Apellido | Martínez | Martínez | Martínez |
| Nombre | Cris | Cris | Cris |
En este caso los tres valores coinciden, porque Usuario_1 todavía no ha hecho ninguna modificación, simplemente ha extraído la información.
A las 12:01 AM, un nuevo Usuario_2 recurre al servidor para leer la misma fila, y a las 12:03 AM edita el campo Nombre (cambia “Cris” por “Cristina”) y actualiza la base de datos. Nuestra representación visual del baile de cadenas de texto sería ahora la que sigue:
| Nombre de columna | Valor original | Valor actual | Valor en la base de datos |
| IdCliente | 101 | 101 | 101 |
| Apellido | Martínez | Martínez | Martínez |
| Nombre | Cris | Cristina | Cris |
La actualización se realiza correctamente porque los valores contenidos en la base da datos en el momento de renovar la fila coinciden con los valores originales de tenía Usuario_2 (“Cris”, en la base de datos, es igual que el dato “Cris” del momento de la lectura); no existe ninguna infracción para Usuario_2.
Vamos a imaginar ahora que Usuario_1 termina de realizar sus modificaciones a las 12:05 AM (cambia “Cris”, su lectura, por “María Cristina”). Veamos la representación:
| Nombre de columna | Valor original | Valor actual | Valor en la base de datos |
| IdCliente | 101 | 101 | 101 |
| Apellido | Martínez | Martínez | Martínez |
| Nombre | Cris | María Cristina | Cristina |
Al intentar actualizar se va a encontrar con una infracción de la concurrencia optimista, ya que el valor actual de la base de datos (“Cristina”) no coincide con el valor que él esperaba para ese campo (“Cris”) porque Usuario_2 lo había modificado previamente. Es ahora el momento en el que hay que tomar la decisión de sobrescribir los cambios realizados o, por el contrario, cancelar la actualización. Como antes decíamos, la elección de una u otra acción dependerá de la situación en concreto y de las querencias del cliente.
Existen varias técnicas para determinar una infracción de concurrencia optimista a la hora de actualizar una base de datos. Una de ellas consiste en incluir una columna de marca de tiempo en la tabla. Las bases de datos suelen ofrecer funcionalidad de marca de tiempo que puede utilizarse para identificar la fecha y la hora en que se actualizó el registro por última vez. Mediante esta técnica, decimos, se incluye una columna de marca de tiempo en la definición de la tabla y, siempre que se actualiza el registro, se actualiza la marca de tiempo, de manera que queden reflejadas la fecha y la hora actuales. Al hacer una prueba para ver si hay infracciones de la concurrencia optimista, la columna de marca de tiempo se devuelve con cualquier consulta del contenido de la tabla. Cuando se intenta realizar una actualización, se compara el valor de marca de tiempo de la base de datos con el valor de marca de tiempo original contenido en la fila modificada. Si coinciden, se realiza la actualización y se edita la columna de marca de tiempo con la hora actual, con el objeto de reflejar la actualización. Si no coinciden, se ha producido una infracción de la concurrencia optimista.
Otra técnica para probar si hay alguna infracción relacionada con la concurrencia optimista consiste en comprobar que todos los valores de columna originales de una fila siguen coincidiendo con los existentes en la base de datos.
Veamos la siguiente consulta SQL contra una base de datos:
SELECT Col1, Col2, Col3 FROM Tabla1
UPDATE Table1 Set Col1 = @NuevoValorCol1,
Set Col2 = @NuevoValorCol2,
Set Col3 = @NuevoValorCol3
WHERE Col1 = @ViejoValorCol1 AND
Col2 = @ViejoValorCol2 AND
Col3 = @ViejoValorCol3 Lo que se hace es seleccionar (SELECT) los campos, o columnas, que se van a modificar para, después, actualizar (UPDATE), con los nuevos valores (@NuevoValorColX), todos aquellos donde (WHERE) los valores originales (@ViejoValorColX) coincidan con los valores actuales en base de datos (ColX).
La teoría es sencilla, pero la implementación puede llegar a complicarse bastante, no por la técnica en sí, sino por la obligación de tomar una decisión en cada uno de los momentos. Será ya la intuición del desarrollador la que indique qué solución tomar en cada caso de infracción. Lo que sí debe quedar claro es que, excepto en determinadas y muy puntuales ocasiones, la concurrencia optimista siempre debe preferirse por encima de la pesimista. Es mejor resolver mal una infracción (cosa que puede corregirse a posteriori) que mantener una red de datos a un 40% de su capacidad total de gestión por acciones de bloqueo tras bloqueo, lentitud, falta de escalabilidad y desidia del usuario que es incapaz de trabajar en esas condiciones.
Todos aman a Cassandra

Sus ojos te embrujarán
Una base de datos distribuida es, en realidad, un conjunto de bases de datos relacionadas de manera lógica y repartidas en diferentes ubicaciones geográficas del planeta. Por supuesto, todas ellas están conectadas entre sí mediante redes de comunicaciones y comparten información general de los datos que albergan, aunque no todas guarden la misma información. La ventaja de estos procedimientos es que permiten diversificar el flujo de transacciones entre los diferentes nodos, liberando de cargas innecesarias al resto.
Imaginemos, por poner un ejemplo, que disponemos de una base de datos relacional SQL clásica en la que existen diversas tablas con la información de alumnos y profesores. Si realizamos una consulta para extraer los datos de un alumno, la base de datos contestará sin ningún problema, aunque sólo haya necesitado acceder a la tabla de alumnos. Si lo que deseamos es averiguar qué profesor es el tutor de determinado alumno, la base de datos nuevamente responderá correctamente, aunque en este caso habrá de acceder a dos tablas relacionadas. En este segundo caso hemos aprovechado la capacidad del gestor de datos al 100%, sin embargo, en el primero de los casos, una base de datos que tiene dos tablas sólo ha necesitado abrir una de ellas, ergo estamos desperdiciando tiempo y recursos.
Imaginemos ahora que montamos la misma estructura sobre un sistema de bases de datos distribuidas. Dividimos los datos en dos partes lógicas, esto es, alumnos y profesores. Cada parte se corresponderá con una base de datos independiente y alojada en un servidor dedicado. Nosotros estamos conectados directamente al nodo de alumnos, por ser el que más utilizamos, por lo que todas nuestras consultas se considerarán locales y sólo trabajará esa base de datos, sin necesidad de recurrir a la otra. Si en un momento dado necesitamos información sobre profesores, el propio nodo local de alumnos iniciará una transacción global para recuperar la información.
En entornos de datos en los que las transacciones pueden ser, tranquilamente, varios billones al día, se hace necesaria la implementación de un sistema distribuido, porque resultaría una auténtica locura cargar a una sola base de datos con todo ese trabajo, aunque estuviera duplicada en varios servidores que se replicaran automáticamente. Digamos que es una forma de distribuir el trabajo; en lugar de hacerlo todo yo, yo me encargo de esto y tú de aquello, y cuando nos haga falta nos juntamos.
Y parece que Cassandra es la niña bonita con la que todos quieren bailar. Cassandra nació en las entrañas de Facebook, y fue creada precisamente para intentar solucionar los problemas de eficiencia de una red social que crecía de forma imparable y que cada vez necesitaba más recursos. Precisamente, el punto débil de las bases de datos relacionales es el consumo de recursos, y los principales interesados en reinventar y revolucionar son los grandes jugadores de Internet.
Google ya había hecho algo parecido antes, un proyecto denominado BigTable que llevó a cabo para sus propias necesidades. Sin embargo, la empresa de la gran G cada vez se parece más a un monopolio de “lo gratis”, y, a pesar de hacer públicos muchos pormenores de su sistema, por motivos de competitividad mantiene en celoso secreto los detalles más importantes. La baza de Facebook fue liberar el código de Cassandra (en 2008) y ofrecerlo a la comunidad internauta para su uso y abuso.
El proyecto Cassandra lo mantiene hoy día la Fundación Apache, y parece que resuelve a las mil maravillas los problemas de prestaciones y, sobre todo, de escalabilidad de anteriores sistemas. La característica más importante de este proyecto es que fue diseñado desde un principio para escalar de una manera natural y prácticamente automática. Hemos de pensar que empresas como Google o Facebook necesitan añadir cada cierto tiempo nuevas máquinas y nuevos servidores en tanto en cuanto crece la demanda de sus servicios. Cassandra permite escalar casi por si sola con el único hecho de agregar mas máquinas a un cluster. Es uno de los primeros sistemas de esta magnitud que escala de manera lineal, conforme se le agregan mas máquinas, y de una forma muy sencilla. La Fundación Apache asegura que ya existen instalaciones de hasta 150 máquinas en paralelo y 100 TB de datos en producción.
Las bases de datos tradicionales tienen un tanto a su favor, y es que su estructura hace que los datos sean consistentes, es decir, cualquier usuario que acceda en cualquier momento dispondrá de la misma información. Cassandra funciona, por defecto, mediante un sistema distribuido de “modelo eventualmente consistente”, esto es, los cambios se van propagando poco a poco entre todos los nodos del cluster; si se produce una consulta, al usuario se le ofrece lo que haya en ese momento, mientras llegan las últimas actualizaciones.
Este sistema es, por tanto, ideal para redes sociales como Facebook o motores de búsqueda como Google, ya que no importa si algunos usuarios reciben una actualización un poco después que otros, porque lo importante es que todos “eventualmente” tengan todas las actualizaciones. Obviamente este modelo no podría aplicarse a otros entornos, como por ejemplo el de las aplicaciones bancarias y financieras, ya que en estos casos todas las transacciones deben confirmarse y, además, han de ser consistentes al 100% en todo momento.
A día de hoy, no sólo Facebook utiliza su Cassandra, empresas como Digg, Cisco, IBM, Cloudkick u Ooyala se sirven de ella para sus proyectos (muchos migraron de pataleta cuando Oracle adquirió MySQL). Incluso Twitter aseguró su intención de migrar a Cassandra en breve, pero de esto ya hace tiempo y parece que todavía el cambio no se ha hecho efectivo, porque la web del pajarito sigue fallando más que una escopeta de feria; no en vano toda su infraestructura se apoya sobre una base de datos MySQL, o sea que, para el meneo que tiene Twitter, sería como tirar de Access 97.
¿Qué tendrá Cassandra que todos la aman?
Inyecciones SQL (¡a que te pincho!)

Inyección SQL
El hacking ya no es lo que era en los noventa. Antes, la seguridad era mínima y los administradores de sistemas eran los mismos chupatintas que lo mismo te configuraban un proxy que te atendían en la ventanilla. Los sistemas operativos tenían más agujeros que un queso de esos con agujeros y, para más inri, las actualizaciones brillaban por su ausencia
Casi nadie tenía Internet, y los cuatro frikis que compramos un módem por aquello de chatear, nos las vimos y nos las deseamos para configurarlo y conectarlo a la Red, cortando la línea de teléfono a tus padres durante tardes interminables y escurriendo el bulto cuando llegaba la factura de Telefónica. Eso sí, la sensación de satisfacción que se te quedaba en el cuerpo cuando conseguías desenmarañar aquel entramado de siglas era indescriptible.
Nos cobraban 5.000 pesetas mensuales por la conexión, teléfono a parte, y existía algo que se llamaba Infovía y que muchos de los que lean esto ni sabrán lo que fue. Pululaban todavía las BBS, el chat se hacía desde un cliente de IRC y las páginas web daban verdadera pena.
Aparecieron los e-zines (fanzines digitales) y corrían de mail en mail como antaño sus hermanos mayores de papel de mano en mano. Descubrí SET antes de que fuera SET, cuando se llamaba Saqueadores a secas. Descubrí también 7a69, Raza Mexicana, Raregazz y otros de los que no recuerdo el nombre. Había uno muy divertido que no tenía como tema principal el hacking ni la informática (y que tampoco me acuerdo cómo se llamaba) que enseñaba desde cómo fabricar una bomba a las mejores maneras para asesinar a un gato. Aquella época era así; el destape digital.
Y entones fue cuando me empecé a aficionar al hacking, al cracking, al phreaking y al virii, que eran los cuatro pilares básicos que todo buen conocedor del underground informático debía manejar, o al menos alguno de ellos pero en plan gurú, si no eras directamente un lammer o un puto newbie.
La filosofía de los hackers de aquella época se ha perdido. El objetivo claro que se perseguía entonces era la superación personal y el ansia por adquirir los conocimientos que permitieran dominar aquella nueva tecnología que tanto futuro parecía tener. Hoy los buenos hackers son putas al servicio de gobiernos o de empresas de seguridad. Alguno de los de antes quedará por ahí, seguro, pero el espíritu ya no es el mismo, se ha desinflado.
El hacking, además, se ha vuelto complicado y peligroso. Hoy los administradores de sistemas han pasado de la ESO (algunos), los sistemas operativos son muchísimo más seguros y los usuarios están mucho más concienciados del concepto de seguridad (más o menos). Los bugs o agujeros de seguridad se resuelven y parchean en horas, y cualquier software se actualiza de manera automática prácticamente a diario.
Sin embargo, de vez en cuando, siguen apareciendo técnicas novedosas que producen un revolvimiento de tripas en el underground informático y una sudoración fría en los lobbys económicos y políticos del sistema. Algo así como un resurgir del lodo, descuellando por unos instantes. Por supuesto, al descubridor de tal o cual vulnerabilidad se le eleva a los altares del movimiento subterráneo y se le santifica a lo Kevin Mitnick.
En los últimos años a mí se me han puesto los pelos de punta en tres de ocasiones: una con la aparición del ataque NetBIOS, otra con la aparición del XSS y otra con la aparición de las inyecciones SQL. Y a estas últimas nos vamos a referir en continuación. ¿Por qué? Por nada en especial.
Para los neófitos: SQL es un lenguaje estructurado que se utiliza para realizar consultas contra bases de datos, ya sean consultas de actualización, de selección o de eliminación.
Las bases de datos cada vez son más importantes en nuestra vida. Todo está ordenado y clasificado en determinado campo de determinado registro de determinada tabla de determinada base de datos. Y, claro, en Internet, que es un espejo casi en tiempo real del mundo no digital, las bases de datos crecen como champiñones a diestro y siniestro. Cualquier página web que visitemos dispondrá de un foro, de una agenda de eventos, de un registro de usuarios, de un catálogo de productos, etcétera. Pues bien, todo eso, y mucho más, necesita de una base de datos para poder funcionar.
Una inyección SQL no es otra cosa que un ataque, debido a una vulnerabilidad corregible, a la base de datos de una aplicación en el nivel de validación. Para que me entienda todo el mundo, el nivel de validación se refiere a esos cuadraditos de texto donde yo escribo mi nombre de usuario y mi contraseña cuando accedo a una web en la que estoy registrado. En esos mismos cuadraditos dispongo yo de la posibilidad de escribir código SQL que la base de datos que haya detrás se trague e interprete y provocar un auténtico desastre.
Vamos a ver un ejemplo práctico. Imaginemos la típica página de validación, donde me piden mi nombre y mi contraseña, para entrar a comprar a una tienda virtual en la que estoy registrado. Cuando yo escribo el nombre, la contraseña y pulso el botón de aceptar, el software comprueba que los datos sean correctos. Para ello realiza un consulta a la base de datos en SQL que puede ser algo como lo siguiente:
"SELECT * FROM TablaUsuarios WHERE Nombre = '" + NombreUsuario + "' AND Contrasena = '" + ContrasenaUsuario + "';"
Esto selecciona (SELECT) todos (*) los registros de (FROM) la tabla TablaUsuarios donde (WHERE) el campo Nombre sea igual (=) a la variable NombreUsuario (su valor se asigna de lo que nosotros hallamos escrito) y (AND) el campo Contrasena sea igual a la variable ContrasenaUsuario. Si yo escribo “Jonathan” y “Tambor1900″ respectivamente en el campo de texto para nombre y contraseña, el resultado de la cadena anterior sería el siguiente:
"SELECT * FROM TablaUsuarios WHERE Nombre = 'Jonathan' AND Contrasena = 'Tambor1900';"
Ahora viene la magia. En el campo de texto para el nombre, en lugar de un nombre, voy a escribir esto: ' OR '1'='1
Y en el campo para la contraseña lo mismo, mismito. La cadena SQL que se envía a la base de datos quedaría así:
"SELECT * FROM TablaUsuarios WHERE Nombre = '' OR '1'='1' AND Contrasena = '' OR '1'='1';"
Es decir, selecciona todos los registros de la tabla TablaUsuarios donde el campo Nombre sea igual a nada o 1 sea igual a 1 y el campo Contrasena sea igual a nada o 1 igual a 1. ¡Abracadabra! Es posible que no exista un nombre o una contraseña vacía, pero siempre 1 va a ser igual a 1, por lo tanto, hasta la cocina del sitio web como si fuéramos un usuario correctamente registrado.
Vamos a hacer más pupita. Vamos a escribir en el campo del nombre lo mismo que antes y en el de la contraseña lo que esto: '; DROP TABLE TablaUsuarios
Con lo que la cadena final queda así:
"SELECT * FROM TablaUsuarios WHERE Nombre = '' OR '1'='1' AND Contrasena = ''; DROP TABLE TablaUsuarios;"
Las sentencias SQL siempre terminan en punto y coma (;) y pueden separarse entre sí por el mismo signo de puntuación. En el anterior ejemplo, comprobaría correctamente que 1 es igual a 1, como hemos comentado antes, para, después, cargarse sin miramientos la tabla TablaUsuarios (DROP TABLE TablaUsuarios).
Evidentemente el daño puede ser mayor que borrar una tabla (suponemos que el administrador tiene copias de seguridad), ya que podemos hacernos con todos los datos sensibles y privados de los usuarios del sitio, hacer compras en su nombre, acceder a sus cuentas bancarias y mil miles de cosas más.
Muchos habrán pensado ya a estas alturas que para inyectar este tipo de códigos SQL es necesario conocer el nombre de las variables (NombreUsuario y ContrasenaUsuario) y el nombre de la tabla (TablaUsuario) que el programador ha utilizado. Y efectivamente así es. Pero para conseguir estos datos podemos recurrir a la técnica de hacer “cascar” la consulta y que produzca un error. Por ejemplo, si en el nombre de usuario escribimos dos comillas simples ('') y en la contraseña cualquier cosa, la cadena SQL resultante sería la siguiente:
"SELECT * FROM TablaUsuarios WHERE Nombre = ''' AND Contrasena = 'Tambor1900';"
Aquí hay más comillas de las que el intérprete SQL de la base de datos puede reconocer, y esto devolvería un error de acceso a la base de datos. Dependiendo de qué servidor web aloje esta página y cómo esté configurado, es más que posible que dicho error aparezca en pantalla (en formato HTML) especificando concretamente dónde y por qué se ha producido, y describiéndonos a las mil maravillas la cadena de conexión y todas sus variables, nombres de campos y tablas. Un placer. Gracias mil.
Una vez expuesta la vulnerabilidad y cómo explotarla, es de caballeros explicar también cómo corregir este defecto. He de decir que muchísimas páginas web en Internet no tienen este tema solucionado; allá ellos. Sólo es necesario visitar sitios web y probar (a base de comillas aquí, comillas allí) hasta que encontremos uno vulnerable. Ya digo que los hay por doquier.
Existen varios métodos para solucionar este problema, pero el más sencillo y eficaz es el que consiste en evitar la introducción de comillas simples en las cajas de texto sustituyéndolas por espacios, por ejemplo. He aquí una forma de hacerlo en VBScript en una página ASP:
CadenaSQL = "SELECT * FROM TablaUsuarios WHERE Nombre = '" & Replace(NombreUsuario, "'", " ") & "' AND Contrasena='" & Replace(ContrasenaUsuario, "'", " ") & "'"
O en JavaScript en una página PHP:
CadenaSQL = SELECT * FROM TablaUsuarios WHERE Nombre = '" & NombreUsuario.replace( "'", " "); & "' AND Contrasena='" & ContrasenaUsuario.replace("'", " "); & "'"
Otro factor importante en cuanto a la seguridad es limitar al máximo los permisos del usuario que ejecuta estas sentencias para evitar posibles problemas. Por ejemplo utilizando un usuario distinto para las sentencias SELECT, DELETE o UPDATE y asegurándonos de que cada ejecución de una sentencia ejecute una sentencia del tipo permitido.
Una solución definitiva sería trabajar con procedimientos almacenados. El modo en el que se pasan los parámetros a los procedimientos almacenados evita que la inyección SQL pueda ser usada. También deberíamos validar los datos que introduce el usuario teniendo en cuenta, por ejemplo, la longitud de los campos y el tipo de datos aceptados. Si programamos en ASP.NET, además, utilizaremos siempre que sea posible las clases de System.Web.Security.FormsAuthentication para que los usuarios entren en nuestras aplicaciones web.
Es tan jugoso explotar una vulnerabilidad como conseguir eliminarla. Los hackers del pasado son los expertos en seguridad del presente. Pero, por favor, no se me vendan a las multinacionales, que no queda muy underground que digamos.

