CTF Stripe 2012 : Solution du level 5
Thursday, August 30, 2012 10:55:00 AM
J'ai perdu un temps précieux sur ce niveau alors que c'était tout bête :-/ Cela dis je ne sais pas pour combien de personnes ça a sauté aux yeux dès le début. Dans la page du level on a un formulaire très simple :Many attempts have been made at creating a federated identity system for the web (see OpenID, for example). However, none of them have been successful. Until today.
The DomainAuthenticator is based off a novel protocol for establishing identities. To authenticate to a site, you simply provide it username, password, and pingback URL. The site posts your credentials to the pingback URL, which returns either "AUTHENTICATED" or "DENIED". If "AUTHENTICATED", the site considers you signed in as a user for the pingback domain.
You can check out the Stripe CTF DomainAuthenticator instance here: [lien retiré] We've been using it to distribute the password to access Level 6. If you could only somehow authenticate as a user of a level05 machine...
To avoid nefarious exploits, the machine hosting the DomainAuthenticator has very locked down network access. It can only make outbound requests to other stripe-ctf.com servers. Though, you've heard that someone forgot to internally firewall off the high ports from the Level 2 server.
<form action="" method="POST"> <p>Pingback URL: <input type="text" name="pingback" /></p> <p>Username: <input type="text" name="username" /></p> <p>Password: <input type="password" name="password" /></p> <p><input type="submit" value="Submit"></p> </form>Une fois soumis, le serveur envoit le username et le password via REST (comprendre un HTTP POST classique) vers l'adresse de pingback. Si cette page de pingback retour la chaine AUTHENTICATED (sans lettre de chaque côté) alors on est authentifié comme un utilisateur du domaine correspondant à l'url de pingback.
Exemple : j'upload un fichier contenant ".AUTHENTICATED." sur le serveur du level 2. Je place l'url du fichier dans le champ pingback et je renseigne un utilisateur et un mot de passe bidon. J'obtiens alors "You are authenticated as bidule@level02.stripe-ctf.com".
Toute la difficulté du level se tient dans les expressions régulières qui limitent les serveurs à utiliser.
La première (ALLOWED_HOSTS = /\.stripe-ctf\.com$/) nous permet d'accéder aux divers serveurs de Stripe.
Mais seulement la seconde plus restrictive (PASSWORD_HOSTS = /^level05-\d+\.stripe-ctf\.com$/) nous permet d'obtenir le password pour débloquer le niveau.
La partie du code qui nous concerne :
post '/*' do
pingback = params[:pingback]
username = params[:username]
password = params[:password]
pingback = "http://#{pingback}" unless pingback.include?('://')
host = URI.parse(pingback).host
unless host =~ ALLOWED_HOSTS
return "Host not allowed: #{host}" \
" (allowed authentication hosts are #{ALLOWED_HOSTS.inspect})"
end
begin
body = perform_authenticate(pingback, username, password)
rescue StandardError => e
return "An unknown error occurred while requesting #{pingback}: #{e}"
end
if authenticated?(body)
session[:auth_user] = username
session[:auth_host] = host
return "Remote server responded with: #{body}." \
" Authenticated as #{username}@#{host}!"
else
session[:auth_user] = nil
session[:auth_host] = nil
sleep(1) # prevent abuse
return "Remote server responded with: #{body}." \
" Unable to authenticate as #{username}@#{host}."
end
end Quand on fait un GET, la vraible host est extraite de la session et si elle match l'expression régulère PASSWORD_HOSTS alors on obtient le password.J'ai joué longtemps avec la ligne URI.parse(pingback).host afin de voir si je pouvais former une adresse qui satisfait tous les critères et dont j'ai le contrôle... en vain.
Pour résoudre ce level, il fallait penser comme dans le film Inception (un rêve dans un rêve etc) ou Existenz (si vous préférez) ou récursivité (si ça vous parle plus). Il fallait aussi trouver que le tableau params récupère aussi bien les variables envoyées par POST que par GET.
Comme solution, on passe l'url de pingback suivante dans le formulaire :
https://level05-2.stripe-ctf.com/dossier-du-level5/?pingback=https://level02-3.stripe-ctf.com/dossier-du-level2/uploads/mon-fichiertxt&username=plop&password=plop
avec un user/mdp bidon.
Le serveur du level5 envoie ces données à lui-même. Il extrait les données et fait une nouvelle requête vers le serveur du level02 qui répond AUTHENTICATED grâce à notre fichier.
Cette réponse remonte d'un cran dans les requêtes pour parvenir à la première instance du level 5. On a bien le message AUTHENTICATED venant du serveur du level 5. Le password est alors affiché.
Je me demande si le cerveau humain n'a pas quelques problèmes avec la notion de récursivité

