Write-up of the challenge “Cryptography – Toil33t” of Nuit du Hack 2016 CTF qualifications.
The weekend of 04/01/2016 is pre-qualification for the Nuit du Hack 2016 as a Jeopardy CTF. Having had the opportunity and the time to participate with some colleagues and friends, here’s a write-up resolution of the challenges which we could participate.
- Category: Cryptography
- Name: 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
This cryptographic challenge concretely illustrates the “cut and paste attack ” on the cipher AES-ECB. The idea is to manipulate the blocks of encrypted data (cut & paste) to forge a different encrypted data with our expectations.
When we go to the URL of the challenge, we are invited to register us:
No control over the field values are applied, inscribe us with a login, password and email with the same value “x”:
Your registration is successful, now observed in the various requests the presence of a session cookie:
session=e7a0bb1bd4f4473106e24b374ac5fa5a799a5dc4824d8f51e2a78524b1020705a6eaf0fe5db99c6755c21f277aff95020ea7708a8f28694887deb53b8ecd855b1fe1ccac92e372290593e12777d8260f;
In addition, AJAX GET request is made from the home page to “/session,” to see if ads are to be displayed on the main page. This simple GET request (with the session cookie) returns JSON in the following format:
{ "email": "x", "is_admin": false, "show_ad": false, "username": "x" }
Interesting the field “is_admin” to “false”. We have to change this value to “true” to reach the admin page. Currently an attempt to access this page responds:
The challenge home page informs that the exchanges are “secure” via Rijndael + 256ROT13, we can deduce that the value of the session cookie is calculated as:
session_cookie = 256ROT13 ( AES_ECB ( json ) )
JSON data is encoded to form the session cookie. Re-inscribe us in just modifying a parameter, the email = “z”:
New JSON data :
{ "email": "z", "is_admin": false, "show_ad": false, "username": "x" }
New session cookie :
session=e7a0bb1bd4f4473106e24b374ac5fa5a799a5dc4824d8f51e2a78524b1020705a6eaf0fe5db99c6755c21f277aff95020ea7708a8f28694887deb53b8ecd855bf1c9c8bf9c982612c9b86320a36e61d0;
Comparing both session cookies from the two accounts (x / x / x and x / x / z), we see that only the last block is different, and the block size of the AES-ECB is 16:
(x / x / x) e7a0bb1bd4f4473106e24b374ac5fa5a 799a5dc4824d8f51e2a78524b1020705 a6eaf0fe5db99c6755c21f277aff9502 0ea7708a8f28694887deb53b8ecd855b 1fe1ccac92e372290593e12777d8260f (x / x / z) e7a0bb1bd4f4473106e24b374ac5fa5a 799a5dc4824d8f51e2a78524b1020705 a6eaf0fe5db99c6755c21f277aff9502 0ea7708a8f28694887deb53b8ecd855b f1c9c8bf9c982612c9b86320a36e61d0
We conclude that the encrypted data meets JSON syntax similar to:
session_cookie = 256ROT13 ( AES_ECB ( '{"username": "x", "show_ad": false, "is_admin": false, "email": "x"}' ) )
By distributing equal size to the JSON data from their respective block, we deduce:
e7a0bb1bd4f4473106e24b374ac5fa5a [{"username": "x"] (1a) 799a5dc4824d8f51e2a78524b1020705 [, "show_ad": fal] (2a) a6eaf0fe5db99c6755c21f277aff9502 [se, "is_admin": ] (3a) 0ea7708a8f28694887deb53b8ecd855b [false, "email": ] (4a) 1fe1ccac92e372290593e12777d8260f ["x"}] (5a)
The idea will now be playing on the value of the “username” with registration, in order to forge a line containing only “true,” to follow up the line “3a”. So “is_admin: true” will be forged.
By registering with a “username” equal to:
username = [xxtrue, ]
We gain the session cookie :
9d287ba085f621f4d11632215d255584e3264500a02a33efe37921dbd92324aa5d998eabd9fd50d71639ffd19fadaeb23164706a5409b80800d7b98a576d11546cb3b01c5a57390325c7f6a18a4183c2340e00870031efaef32faae48f1dacc3
Distribution :
9d287ba085f621f4d11632215d255584 [{"username": "xx] (1b) e3264500a02a33efe37921dbd92324aa [true, ] (2b) 5d998eabd9fd50d71639ffd19fadaeb2 [", "show_ad": fa] (3b) 3164706a5409b80800d7b98a576d1154 [lse, "is_admin":] (4b) 6cb3b01c5a57390325c7f6a18a4183c2 [ false, "email":] (5b) 340e00870031efaef32faae48f1dacc3 [ "x"}] (6b)
The line “2b” perfectly matches our expectations.
We must now forge a suitable block for email:
username = [xxxxxxxxxx]
Cookie :
9d287ba085f621f4d11632215d255584853ce76be7095f7a6006aade6d87574f3758e2604a11b75246b4f7fd9d14df598ac9ee8270fdde8d052e27be7d93dd3a5e3e792d648734eb774d0263b4a5b30b
Distribution :
9d287ba085f621f4d11632215d255584 [{"username": "xx] (1c) 853ce76be7095f7a6006aade6d87574f [xxxxxxxx", "show] (2c) 3758e2604a11b75246b4f7fd9d14df59 [_ad": false, "is] (3c) 8ac9ee8270fdde8d052e27be7d93dd3a [_admin": false, ] (4c) 5e3e792d648734eb774d0263b4a5b30b ["email": "x"}] (5c)
We have now all the “material” to forge an administration cookie from valid blocks and to obtain our desired JSON:
{"username": "x", "show_ad": false, "is_admin": true, "email": "x"}
A block is independent of the previous block, they are interchangeable as the decrypted data translated in JSON is with a valid syntax (cut and paste attack). It therefore takes the blocks that are of interest for previous requests to forge one you want, the 256ROT13 is obvious (1a, 2a, 3a, 2b and 5c) :
e7a0bb1bd4f4473106e24b374ac5fa5a [{"username": "x"] (1a) 799a5dc4824d8f51e2a78524b1020705 [, "show_ad": fal] (2a) a6eaf0fe5db99c6755c21f277aff9502 [se, "is_admin": ] (3a) e3264500a02a33efe37921dbd92324aa [true, ] (2b) 5e3e792d648734eb774d0263b4a5b30b ["email": "x"}] (5c)
Test of our new cookie on the page “/session” to see how it is decoded:
e7a0bb1bd4f4473106e24b374ac5fa5a799a5dc4824d8f51e2a78524b1020705a6eaf0fe5db99c6755c21f277aff9502e3264500a02a33efe37921dbd92324aa5e3e792d648734eb774d0263b4a5b30b
Bingo! Just visit the admin page with the new cookie value:
Flag : NDH{22cf96f723f08382606119fe574953b9}
Greeting to nj8, St0rn, Emiya, Mido, downg(r)ade, Ryuk@n and rikelm, 😉 // Gr3etZ
Sources & ressources :