Présentation d’un write-up de résolution du challenge « Cryptography – Toil33t » des qualifications du CTF de la Nuit du Hack 2016.
Le weekend du 01/04/2016 se déroulait les pré-qualifications pour la Nuit du Hack 2016 sous forme d’un CTF Jeopardy. Ayant eu l’occasion et le temps d’y participer avec quelques collègues et amis, voici un write-up de résolution d’un des challenges auquel nous avons pu participer.
- Catégorie : Cryptography
- Nom : Toil33t
- Description : Hey ! I really want to buy a toil33t, however it is not available yet! 🙁 Can you access to the administrative page and take one for me?
- URL : http://toil33t.quals.nuitduhack.com
- Points : 400
Ce challenge de cryptographie illustre concrètement une attaque « cut and paste » concernant le chiffrement par blocs AES-ECB. L’idée est de manipuler les blocs du chiffré (cut & paste) afin de forger un tout autre chiffré répondant à nos attentes.
Lorsque l’on se rend sur l’URL du challenge, nous sommes invité à nous inscrire :
Aucun contrôle sur les valeurs des champs n’est appliqué, inscrivons-nous avec un login, password et email avec une même valeur « x » :
Notre inscription étant un succès, on observe à présent dans les diverses requêtes la présence d’un cookie de session :
session=e7a0bb1bd4f4473106e24b374ac5fa5a799a5dc4824d8f51e2a78524b1020705a6eaf0fe5db99c6755c21f277aff95020ea7708a8f28694887deb53b8ecd855b1fe1ccac92e372290593e12777d8260f;
De plus, une requête AJAX GET est faites depuis la page d’accueil vers « /session », afin de vérifier si des pubs doivent être affichées sur l’accueil. Cette simple requête GET (accompagnée du cookie de session) retourne du JSON au format suivant :
{ "email": "x", "is_admin": false, "show_ad": false, "username": "x" }
Intéressant ce « is_admin » à « false ». Serait-ce lui qu’il faudrait passer à « true » pour atteindre la page d’administration ? Actuellement une tentative d’accès à cette page nous répond :
La page d’accueil du challenge informe que les échanges sont « sécurisés » via Rijndael + 256ROT13, on peut donc déduire que la valeur du cookie de session est calculée ainsi :
session_cookie = 256ROT13 ( AES_ECB ( json ) )
Le JSON est donc encodée pour former le cookie de session. Ré-inscrivons nous en modifiant juste un paramètre, l’email = « z » :
Le JSON résultant :
{ "email": "z", "is_admin": false, "show_ad": false, "username": "x" }
Et le cookie de session :
session=e7a0bb1bd4f4473106e24b374ac5fa5a799a5dc4824d8f51e2a78524b1020705a6eaf0fe5db99c6755c21f277aff95020ea7708a8f28694887deb53b8ecd855bf1c9c8bf9c982612c9b86320a36e61d0;
En comparant les deux cookies de session des deux comptes créés (x/x/x et x/x/z), on observe que seul le dernier bloc diffère, et que la taille des blocs de l’AES-ECB est de 16 :
(x / x / x) e7a0bb1bd4f4473106e24b374ac5fa5a 799a5dc4824d8f51e2a78524b1020705 a6eaf0fe5db99c6755c21f277aff9502 0ea7708a8f28694887deb53b8ecd855b 1fe1ccac92e372290593e12777d8260f (x / x / z) e7a0bb1bd4f4473106e24b374ac5fa5a 799a5dc4824d8f51e2a78524b1020705 a6eaf0fe5db99c6755c21f277aff9502 0ea7708a8f28694887deb53b8ecd855b f1c9c8bf9c982612c9b86320a36e61d0
On en conclu que les données JSON chiffrées respectent une syntaxe similaire à :
session_cookie = 256ROT13 ( AES_ECB ( '{"username": "x", "show_ad": false, "is_admin": false, "email": "x"}' ) )
En répartissant à taille égale les données JSON par rapport à leur bloc respectif, on en déduit :
e7a0bb1bd4f4473106e24b374ac5fa5a [{"username": "x"] (1a) 799a5dc4824d8f51e2a78524b1020705 [, "show_ad": fal] (2a) a6eaf0fe5db99c6755c21f277aff9502 [se, "is_admin": ] (3a) 0ea7708a8f28694887deb53b8ecd855b [false, "email": ] (4a) 1fe1ccac92e372290593e12777d8260f ["x"}] (5a)
L’idée va être à présent de jouer sur la valeur du « username » à l’inscription, afin de forger une ligne contenant uniquement « true, » pour faire suite à la ligne « 3a ». Ainsi, « is_admin:true » sera forgé.
En s’inscrivant avec un « username » égal à :
username = [xxtrue, ]
On obtient le cookie de session :
9d287ba085f621f4d11632215d255584e3264500a02a33efe37921dbd92324aa5d998eabd9fd50d71639ffd19fadaeb23164706a5409b80800d7b98a576d11546cb3b01c5a57390325c7f6a18a4183c2340e00870031efaef32faae48f1dacc3
Soit la répartition :
9d287ba085f621f4d11632215d255584 [{"username": "xx] (1b) e3264500a02a33efe37921dbd92324aa [true, ] (2b) 5d998eabd9fd50d71639ffd19fadaeb2 [", "show_ad": fa] (3b) 3164706a5409b80800d7b98a576d1154 [lse, "is_admin":] (4b) 6cb3b01c5a57390325c7f6a18a4183c2 [ false, "email":] (5b) 340e00870031efaef32faae48f1dacc3 [ "x"}] (6b)
La ligne « 2b » correspond parfaitement à nos attentes.
Il nous faut maintenant forger un bloc convenable pour l’email :
username = [xxxxxxxxxx]
Cookie :
9d287ba085f621f4d11632215d255584853ce76be7095f7a6006aade6d87574f3758e2604a11b75246b4f7fd9d14df598ac9ee8270fdde8d052e27be7d93dd3a5e3e792d648734eb774d0263b4a5b30b
Répartition :
9d287ba085f621f4d11632215d255584 [{"username": "xx] (1c) 853ce76be7095f7a6006aade6d87574f [xxxxxxxx", "show] (2c) 3758e2604a11b75246b4f7fd9d14df59 [_ad": false, "is] (3c) 8ac9ee8270fdde8d052e27be7d93dd3a [_admin": false, ] (4c) 5e3e792d648734eb774d0263b4a5b30b ["email": "x"}] (5c)
On a à présent tout le « matériel » pour forger un cookie d’administration à partir de blocs valides et permettant d’obtenir notre JSON souhaité :
{"username": "x", "show_ad": false, "is_admin": true, "email": "x"}
Un bloc ne dépend pas du bloc précédent, ils sont interchangeables tant que les données déchiffrées se traduisent par du JSON syntaxiquement valide (cut and paste attack). On prend donc les blocs qui nous intéressent des requêtes précédentes pour forger celui souhaité, le 256ROT13 n’entrant pas en jeu (1a, 2a, 3a, 2b et 5c) :
e7a0bb1bd4f4473106e24b374ac5fa5a [{"username": "x"] (1a) 799a5dc4824d8f51e2a78524b1020705 [, "show_ad": fal] (2a) a6eaf0fe5db99c6755c21f277aff9502 [se, "is_admin": ] (3a) e3264500a02a33efe37921dbd92324aa [true, ] (2b) 5e3e792d648734eb774d0263b4a5b30b ["email": "x"}] (5c)
On test notre nouveau cookie sur la page « /session » pour voir comment il est décodée :
e7a0bb1bd4f4473106e24b374ac5fa5a799a5dc4824d8f51e2a78524b1020705a6eaf0fe5db99c6755c21f277aff9502e3264500a02a33efe37921dbd92324aa5e3e792d648734eb774d0263b4a5b30b
Bingo ! Plus qu’à consulter la page d’administration avec cette nouvelle valeur de cookie :
Flag : NDH{22cf96f723f08382606119fe574953b9}
Salutations à nj8, St0rn, Emiya, Mido, downg(r)ade, Ryuk@n et rikelm, on remet ça quand vous voulez 😉 // Gr3etZ
Sources & ressources :