Solución al reto zeropwn #wgsbd2
Esta prueba, como parecía de las difíciles no la intenté hasta que vi que pusieron una pista :). Y vaya pista, el código fuente del programa que actuaba de servidor y un fichero de texto con muuuuuchas palabras. El típico fichero diccionario de claves.
Lo que teníamos era un servicio escuchando por el puerto 8008 en wargame.securitybydefault.com. Cuando nos conectábamos a dicho puerto recibíamos algo como:
El servidor nos está mandando algo codificado en Base64.
Si lo decodificamos, obtenemos:
Si miramos el código fuente del servidor, vemos que los campos qop, charset y algorithm son constantes. El único campo que es variable es nonce, que es el tiempo (en segundos) en el que el servidor nos manda el mensaje. Como veremos más adelante, será el único valor que tenemos que sacar de la primera respuesta que recibimos.
Una vez el servidor nos manda los datos arriba mencionados, espera respuesta por nuestra parte. De nuevo, mirando el código fuente del servidor, vemos que lo que espera es una cadena de texto codificada a Base64 con la siguiente estructura:
Expliquemos cada campo:
El username debe ser zero_cool, este dato se nos daba como parte inicial del reto.
El realm lo podemos sacar de la respuesta inicial (……), aunque al ser un valor constante, lo usaremos como tal en nuestro script.
El campo nonce como dije antes es variable y lo tenemos que sacar de la primera respuesta del servidor. Este es el código que usaremos para extraer dicho dato:
Los campos cnonce y nc son dos datos que manda el cliente y que el servidor usa como tales, así que podemos mandarle lo que queramos. Los únicos requisitos son que cnonce debe ser del tipo numérico y nc del tipo texto.
Los campos qop, digest-uri y charset, de nuevo deben tener los valores que mostramos más arriba, puesto que son los valores que espera el servidor.
Y por último tenemos el campo response, que es el que tiene más miga de todos y es donde vamos a mandar nuestro password. Para calcular este campo tomé prestada la función que usa el servidor :) para comparar lo que mandamos. La función tiene esta pinta:
Como podemos ver, tenemos todos los parámetros que la función espera, menos la contraseña (pass). Aquí es donde entra en juego el fichero de passwords que os comenté al principio.
Veamos un ejemplo de la cadena que tendríamos que mandar con el password 12345 y nonce = 1311128886:
Y si el password es incorrecto el servidor respondería con:
Y si fuera correcto:
Donde las Xs será nuestro token codificado en Base64.
En resumidas cuentas, lo que tenemos que hacer es:
require ’nokogiri' require “digest/md5” require “socket” require “base64” require “timeout”
$server = “wargame.securitybydefault.com” $port = “8008” $login_user = “zero_cool” $login_pass = “12345” $realm = “war.game.sbd” $qop = “auth” $charset = “utf-8” $digest_uri = “xmpp/war.game.sbd” $cnonce = 1 $nc = “1”
def sasl_md5_chall(uname, pass, realm, nonce, cnonce, nc, digest_uri, qop)
hA1data = Digest::MD5.digest("#{uname}:#{realm}:#{pass}")
hA1data = hA1data + ":#{nonce}:#{cnonce}".force_encoding("UTF-8")
hA1 = Digest::MD5.hexdigest(hA1data)
hA2 = Digest::MD5.hexdigest("AUTHENTICATE:#{digest_uri}")
hash = Digest::MD5.hexdigest("#{hA1}:#{nonce}:#{nc}:#{cnonce}:#{qop}:#{hA2}")
return hash
end
def check(pass) s = TCPSocket.open($server, $port) response = s.gets response += s.gets
xml = Nokogiri::HTML(response)
chal = xml.xpath("//challenge").text
dec_chal = Base64.decode64(chal)
nonce = dec_chal.split(",")[0].split("=")[1].gsub("\"","")
hashed_resp = sasl_md5_chall($login_user, pass, $realm, nonce, $cnonce, $nc, $digest_uri, $qop)
resp_back = "username=\"zero_cool\",realm=\"war.game.sbd\",nonce=\"#{nonce}\",cnonce=\"#{$cnonce}\",nc=\"#{$nc}\",qop=\"auth\",digest-uri=\"xmpp/war.game.sbd\",response=\"#{hashed_resp}\",charset=\"utf-8\""
resp_back2 = "<response>"+Base64.encode64(resp_back)+"</response>"
s.write resp_back2 + "\n"
response = ""
while l = s.gets
response += l
end
return response
end
f = File.new(“password.txt”, “r”) while (line = f.gets) $user_pass = line.chop puts “Trying: #{$user_pass}” result = check(line.chop) puts result if result.include?(‘success’) then puts result break end end f.close Para concluir, decir que el password correcto era: unix y la repuesta recibida fue:
Y si decodificamos la cadena de texto obtenemos:
Here is your damn flag: I_LoVe_Angelina_J0Lie_
Buscar
Entradas Recientes
- Kubernetes para profesionales
- Posts
- Agenda: OpenExpo Europe 2022 llega el 30 de junio en formato presencial
- Libro 'Manual de la Resilencia', de Alejandro Corletti, toda una referencia para la gestión de la seguridad en nuestros sistemas
- Mujeres hackers en ElevenPaths Radio
- Creando certificados X.509 caducados
- Generador de imágenes Docker para infosec
- asn-search, herramienta de reconocimiento
- El país de la [des]conexión. Parte de lo que no se vio
- Escribiendo plugins para kubectl