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_