jueves, 21 de octubre de 2010

Inyección SQL en la web del metro de Madrid (2/2)

Hace algo más de un mes os comentaba sobre una vulnerabilidad de inyección de código SQL en la web del metro de Madrid, pues bien, recientemente se han puesto en contacto conmigo para indicarme que ya estaba resuelto el problema aunque tengo mis dudas al respecto.

La vulnerabilidad se encontraba en la parte de la web encargada de mostrar los planos de las estaciones.


Al acceder al plano de una estación se abre una nueva ventana en cuya url hay una serie de parámetros (tipo, idParada e idioma)


En este caso el parámetro vulnerable era idParada que se concatena a la sentencia SQL sin ningún tipo de filtro por lo que al añadirle la típica comilla simple se producia un error de SQL que era mostrado en pantalla, lo cual tampoco debería ocurrir pero parece esta activado el modo debug.


En el mensaje de error se puede ver como esta construida la consulta SQL por lo que es facil modificarla para añadir la información que nos interese. También aparece la ruta completa del fichero que ha producido el error (full path disclosure) que como ya he comentado en alguna ocasión puede ser de gran utilidad con vistas a llegar a tener acceso al fichero.

Para modificar la consulta y añadir la información que deseada es posible utilizar UNION, por ejemplo http://prs.metromadrid.es/metro/metronet/mapa.aspx?tipo=ESTACION&idParada=-1 union select 1,1,'Nombre'&idioma=es es decir, añadir un registro con coordenadas 1,1 y con el texto Nombre como nombre de la supuesta estación. También se establece idParada a -1 para conseguir que la consulta solo devuelva un registro, el nuestro, con lo que se pretende que en el mapa solo se visualizase nuestra estación falsa.


Efectivamente, se consigue que se muestre una única estación en las coordenadas 1,1 pero no se muestra el texto "Nombre" tal y como se había pensado lo cual es un problema ya que pese a que existía inyección sql no se podía mostrar información en pantalla... ¿o si?

Una de las curiosidades de SQL es que cuando se produce un error de tipos SQL informa de que el valor "x" no se puede convertir al tipo "y", por ejemplo, si intentamos convertir el texto "Prueba" a un número SQL mostrará un error diciendo que "Prueba" no se puede convertir a int ¿como podemos aprovechar esto? La consulta original devuelve las coordenadas x e y de la estación indicada, estos valores son numéricos por lo que si intentamos mostrar un valor de tipo texto en cualquiera de estos campos se producirá un error de tipos y nos mostrará el texto que ha producido el error, por ejemplo, la URL http://prs.metromadrid.es/metro/metronet/mapa.aspx?tipo=ESTACION&idParada=-1 union select 'Forzar error',1,'Nombre'&idioma=es intenta asignar como coordenada x el valor "Forzar error" lo que produce el siguiente mensaje de error.


Aqui podemos ver que se muestra el texto "Forzar error" que es lo que hemos especificado en la consulta sql inyectada por lo que ya tenemos una forma de devolver información. Por ejemplo, si quisieramos saber que versión de SQL se esta utilizando podriamos utilizar la siguiente url http://prs.metromadrid.es/metro/metronet/mapa.aspx?tipo=ESTACION&idParada=-1 union select @@version,1,'Nombre'&idioma=es


Como era de suponer se trata de SQL Server 2005. Es curioso que SQL, en su afan de ayudar, muestra los mensajes de error de forma muy detallada pero, en este caso, toda esa información se puede volver en su contra y permite acceder a información que no deberia ser visualizada.

Para conocer las bases de datos existentes en el servidor podemos acceder a la tabla sysdatabases que se encuentra en la base de datos master de SQL Server. Para ello accederemos a la URL http://prs.metromadrid.es/metro/metronet/mapa.aspx?tipo=ESTACION&idParada=-1 union select NAME,1,'Nombre' from master..sysdatabases&idioma=es y nuevamente se muestra el mensaje de error de conversión de tipos donde podemos ver el nombre de una de las bases de datos existentes.



Aunque en este caso no nos sirve de mucho ya que la base de datos que se muestra es master que ya sabiamos que existía. El problema es que solo se muestra el primer registro que produce el error así que, o bien añadimos una clausula WHERE para evitar la base de datos con nombre master y volvemos a ejecutar la consulta o buscamos una forma alternativa de mostrar la información de varios registros en un único campo y como es de suponer, me inclino por esta última opción.

Vamos a aprovechar la potencia del lenguaje XML. SQL Server incluye la posibilidad de devolver la información de una consulta como XML en texto plano, es decir, devolver un único registro con un único campo de tipo texto que contendrá el XML resultante. Para indicarle a SQL que nos devuelva el resultado en XML solo hay que incluir FOR XML RAW al final de la sentencia SELECT.
En nuestro caso queremos que uno de nuestros campos que supuestamente debe devolver la coordenada de la estación devuelva un XML con la información de las bases de datos existentes así que la URL resultante seria: http://prs.metromadrid.es/metro/metronet/mapa.aspx?tipo=ESTACION&idParada=-1 union select (select NAME from master..sysdatabases FOR XML RAW),1,'Nombre'&idioma=es


Podemos ver como se devuelve el texto en formato XML y que se muestra el nombre de varias bases de datos (master, tempdb, model, msdb, metromadridmetromadridBack) aunque la mayor parte son propias de SQL Server y las únicas "interesantes" son metromadrid y metromadridBack, esta última tiene pinta de ser una copia de seguridad.

Una vez hecho esto es facil acceder a las tablas existentes en el servidor (mediante sysobjects) así como a la información de las mismas. También se podria pensar en utilizar xp_cmdshell para ejecutar comandos en el servidor pero parece que eso si esta bloqueado y el usuario de base de datos no se tiene permiso para ejecutar ese tipo de procedimientos.

Como podeis ver es una forma curiosa de acceder a la información pero igualmente válida que puede darle muchos dolores de cabeza a los administradores de la web si alguien mal intencionado modificase la información del servidor.


Detección del problema: 04/09/2010
Comunicado a la empresa: 04/09/2010
Resolución: 18/10/2010

No hay comentarios:

Publicar un comentario