ICMTC CTF 2024 (Web Exploitation)

Anas Ibrahim
8 min readJun 27, 2024



بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيمِ

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: After opening it, I found a highlighted PHP source code. Let’s analyze the code.

if(isset($_GET['p1'])) {
if ($_GET['p1'] == "1e3" && $_GET['p1'] !== "1e3") {
if(md5($_GET['p2']) == '0e734627975726664180692657231716') {
else {
print("Try Harder!");


This PHP code checks if the GET parameter p1is equal to 1e3and 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

Compare Me Flag

Hidden in Plain

The challenge link: , 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

Login Page

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:

$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.phpsource code file, which contained the following:

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.

guest request

I decoded it as base64 and found this serialized object:


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:


After encoding it:

Admin Cookie
Hidden in Plain Flag

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.

mdtopdf library vulnerable to a cve

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

OTP page

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.

OTP bypass

We were redirected to the /convert endpoint, and now it’s time for the RCE exploit.

markdown to pdf

I tried the payload in the POC, expecting to get a PDF with the output of the command in the POC.


The result:

server error

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:

RCE payload (webhook ping)

We got a request to our webhook.

Now we should get a reverse shell. Here is the payload I used:

Reverse shell

And successfully got a reverse shell

Searching flag

The flag

PDF Generator 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.

Registration and Login pages

After logging in, I was redirected to the profile.php endpoint, where I found a function to update the username.

Update 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:


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:”


“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.”


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:


Encode it as a URL so that the final profile_datavalue is as follows:

Flag Returned on username value

Finally, I have finished the write-up about solving the four web security challenges. I hope you find it enjoyable, see you in the finals.

The End


Facebook | LinkedIn