> For the complete documentation index, see [llms.txt](https://bunring.gitbook.io/ctf-writeups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bunring.gitbook.io/ctf-writeups/try-hack-me/2025/crypto-failures.md).

# Crypto Failures

{% embed url="<https://tryhackme.com/room/cryptofailures>" %}

## Recon

We start with a Nmap scan that reveals two open ports: port `22 (SSH)`and port `80 (a web server)`.

{% code overflow="wrap" %}

```bash
┌──(kali㉿kali)-[~]
└─$ nmap -p- crypto.thm -T4               
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-03-03 11:15 IST
Stats: 0:01:54 elapsed; 0 hosts completed (1 up), 1 undergoing Connect Scan
Nmap scan report for crypto.thm (10.10.56.57)
Host is up (0.15s latency).
Not shown: 65529 closed tcp ports (conn-refused)
PORT      STATE    SERVICE
22/tcp    open     ssh
80/tcp    open     http

Nmap done: 1 IP address (1 host up) scanned in 897.81 seconds
                                                                                                                                                                                                                                            
┌──(kali㉿kali)-[~]
└─$ nmap -sC -sV -sT -p 22,80 crypto.thm -T4
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-03-03 11:34 IST
Stats: 0:00:06 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Nmap scan report for crypto.thm (10.10.56.57)
Host is up (0.15s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 57:2c:43:78:0c:d3:13:5b:8d:83:df:63:cf:53:61:91 (ECDSA)
|_  256 45:e1:3c:eb:a6:2d:d7:c6:bb:43:24:7e:02:e9:11:39 (ED25519)
80/tcp open  http    Apache httpd 2.4.59 ((Debian))
|_http-title: Did not follow redirect to /
|_http-server-header: Apache/2.4.59 (Debian)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.90 seconds
```

{% endcode %}

Upon visiting the page, we find that we are logged in as a guest. The site mentions an SSO cookie, which is claimed to be secured using military-grade encryption.

<figure><img src="/files/OyoRmOmEji3EscgAM6DR" alt=""><figcaption></figcaption></figure>

We find the cookie as follows:

<figure><img src="/files/cwcA9YbXxRMcCCyn0zom" alt=""><figcaption></figcaption></figure>

In the source code, we discover a comment stating that **.bak** files still need to be removed. Additionally, the word **crypt** is emphasized in bold.

<figure><img src="/files/blsU1E7Dx1iuYWq3GhRl" alt=""><figcaption></figcaption></figure>

We intercept a request to the index page using Burp and test if **index.php** can be accessed directly. The request is successful, and we receive the index page.

<figure><img src="/files/xHYxCUzt6Q6DjzhRjZ2R" alt=""><figcaption></figcaption></figure>

By requesting **`index.php.bak`**, we retrieve the source code of the index page, including the logic for generating the SSO cookie. Unfortunately, **config.php** is not available as a backup; otherwise, we might have gained access to the encryption key.

<figure><img src="/files/ZmSCYZX6VH1U2zwSnSEx" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" %}

```php
HTTP/1.1 200 OK
Date: Mon, 03 Mar 2025 06:11:27 GMT
Server: Apache/2.4.59 (Debian)
Last-Modified: Tue, 18 Jun 2024 06:23:20 GMT
ETag: "7bb-61b241e413aab"
Accept-Ranges: bytes
Content-Length: 1979
Connection: close
Content-Type: application/x-trash

<?php
include('config.php');

function generate_cookie($user,$ENC_SECRET_KEY) {
    $SALT=generatesalt(2);
    
    $secure_cookie_string = $user.":".$_SERVER['HTTP_USER_AGENT'].":".$ENC_SECRET_KEY;

    $secure_cookie = make_secure_cookie($secure_cookie_string,$SALT);

    setcookie("secure_cookie",$secure_cookie,time()+3600,'/','',false); 
    setcookie("user","$user",time()+3600,'/','',false);
}

function cryptstring($what,$SALT){

return crypt($what,$SALT);

}


function make_secure_cookie($text,$SALT) {

$secure_cookie='';

foreach ( str_split($text,8) as $el ) {
    $secure_cookie .= cryptstring($el,$SALT);
}

return($secure_cookie);
}


function generatesalt($n) {
$randomString='';
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
for ($i = 0; $i < $n; $i++) {
    $index = rand(0, strlen($characters) - 1);
    $randomString .= $characters[$index];
}
return $randomString;
}



function verify_cookie($ENC_SECRET_KEY){


    $crypted_cookie=$_COOKIE['secure_cookie'];
    $user=$_COOKIE['user'];
    $string=$user.":".$_SERVER['HTTP_USER_AGENT'].":".$ENC_SECRET_KEY;

    $salt=substr($_COOKIE['secure_cookie'],0,2);

    if(make_secure_cookie($string,$salt)===$crypted_cookie) {
        return true;
    } else {
        return false;
    }
}


if ( isset($_COOKIE['secure_cookie']) && isset($_COOKIE['user']))  {

    $user=$_COOKIE['user'];

    if (verify_cookie($ENC_SECRET_KEY)) {
        
    if ($user === "admin") {
   
        echo 'congrats: ******flag here******. Now I want the key.';

            } else {
        
        $length=strlen($_SERVER['HTTP_USER_AGENT']);
        print "<p>You are logged in as " . $user . ":" . str_repeat("*", $length) . "\n";
	    print "<p>SSO cookie is protected with traditional military grade en<b>crypt</b>ion\n";    
    }

} else { 

    print "<p>You are not logged in\n";
   

}

}
  else {

    generate_cookie('guest',$ENC_SECRET_KEY);
    
    header('Location: /');


}
?>
```

{% endcode %}

If the **secure\_cookie** is not set, it is generated by **`generate_cookie()`**. The cookie follows the structure:

**`user:UserAgent:ENC_SECRET_KEY`**

This means we have control over the **User-Agent** parameter, but we'll explore that later.

The **`generate_cookie()`** function first calls **`generatesalt()`**, which creates a random 2-byte salt using an alphanumeric character set. This salt, along with the cookie string, is then passed to the **`make_secure_cookie`** function.

{% code overflow="wrap" %}

```php
function generate_cookie($user,$ENC_SECRET_KEY) {
    $SALT=generatesalt(2);
    
    $secure_cookie_string = $user.":".$_SERVER['HTTP_USER_AGENT'].":".$ENC_SECRET_KEY;

    $secure_cookie = make_secure_cookie($secure_cookie_string,$SALT);

    setcookie("secure_cookie",$secure_cookie,time()+3600,'/','',false); 
    setcookie("user","$user",time()+3600,'/','',false);
}
```

{% endcode %}

The **`make_secure_cookie()`** function applies the **`crypt()`** function to the provided cookie string in chunks of 8 characters, including the generated salt.

```php
function cryptstring($what,$SALT){

return crypt($what,$SALT);

}

function make_secure_cookie($text,$SALT) {

$secure_cookie='';

foreach ( str_split($text,8) as $el ) {
    $secure_cookie .= cryptstring($el,$SALT);
}
```

The verification process extracts the salt from the first two characters of the stored cookie using:

```php
substr($_COOKIE['secure_cookie'], 0, 2)
```

This extracted salt is then used to regenerate the secure cookie, which is compared against the provided one to verify its validity.

```php
function verify_cookie($ENC_SECRET_KEY){


    $crypted_cookie=$_COOKIE['secure_cookie'];
    $user=$_COOKIE['user'];
    $string=$user.":".$_SERVER['HTTP_USER_AGENT'].":".$ENC_SECRET_KEY;

    $salt=substr($_COOKIE['secure_cookie'],0,2);

    if(make_secure_cookie($string,$salt)===$crypted_cookie) {
        return true;
    } else {
        return false;
    }
}
```

Once a valid cookie is verified, access is granted or denied based on the **username**. The **username** is determined by the **user** cookie. If the username is set to **`admin`**, a flag is displayed.

{% code overflow="wrap" %}

```php
if ($user === "admin") {
   
        echo 'congrats: ******flag here******. Now I want the key.';

            } else {
        
        $length=strlen($_SERVER['HTTP_USER_AGENT']);
        print "<p>You are logged in as " . $user . ":" . str_repeat("*", $length) . "\n";
	    print "<p>SSO cookie is protected with traditional military grade en<b>crypt</b>ion\n";    
    }
```

{% endcode %}

## Initial Access

Since we can extract the salt from the hash and know that the **crypt()** function operates on 8-character chunks of the cookie string, impersonating the **admin** user becomes straightforward.

To do this:

1. Extract the salt from the existing **secure\_cookie**.
2. Compute the hash of **admin:** using the same salt.
3. Replace the first hashed chunk of the cookie with this new hash.
4. Set the **user** cookie to **admin**.

This allows us to bypass authentication and access the flag.

{% code title="admin\_cookie.php" overflow="wrap" lineNumbers="true" %}

```php
<?php

$cookie = "YOUR_COOKIE";


$salt = substr($cookie,0,2);

$text = "guest:Mo";
$guest_part = crypt($text, $salt);

$admin_text = "admin:Mo";
$admin_part = crypt($admin_text, $salt);


$modified_cookie = str_replace($guest_part, $admin_part, $cookie);

print($modified_cookie);

?>

```

{% endcode %}

<figure><img src="/files/B4mBw59kUgcnif3Jo0cW" alt=""><figcaption></figcaption></figure>

Next, we replace the **secure\_cookie** with our forged one and set the **user** cookie to **admin**. After reloading the page, we successfully log in as **admin** and retrieve the first flag.

<figure><img src="/files/PTHnosJsCW4kJsU1i7tz" alt=""><figcaption></figcaption></figure>

## Obtaining The Encryption Key

To extract the **`secret key`**, we take advantage of our control over the **User-Agent** and the fact that the hash operates in 8-character chunks.

#### Problem with Bruteforcing the Entire Key

* The full brute-force approach would require **`65⁸`** permutations per chunk, which is infeasible.
* Instead, we reduce the unknown portion to a **single byte** at a time.

#### Exploiting Controlled Input

1. Since we control **User-Agent**, we craft a cookie string where we already know **7 out of 8 bytes** in a chunk.
2. We then brute-force the **remaining single byte** until we find a match in the generated **secure\_cookie**.
3. When the resulting hash appears in the **secure\_cookie**, we know that the unknown byte is part of the **secret key**.

#### Step-by-Step Process

1. **Craft the attack string** using User-Agent padding:
   * Example: `guest:AAAAAAAAAA:_`
   * **Chunk A:** `guest:AA` (first 8-character block)
   * **Chunk B:** `AAAAAA:_` (7 known bytes + 1 unknown character from the key)
2. **Brute-force the unknown character**:
   * Generate hashes for all possible last bytes (`_` in **Chunk B**).
   * When a hash matches the corresponding chunk in the cookie, we recover that byte of the key.
3. **Iterate and reduce padding**:
   * Use the previously found characters to refine the attack.
   * Slowly reveal the entire key by repeating the process with adjusted padding.

By continuously reducing the padding and updating our **test string**, we fully recover the **secret key**.

{% code title="extract\_key.php" overflow="wrap" lineNumbers="true" %}

```php
<?php

// Target URL
$target_url = "http://crypto.thm/index.php";

// Possible characters
$charset = implode('', array_merge(
    range('a', 'z'),     // Lowercase letters
    range('A', 'Z'),     // Uppercase letters
    range('0', '9'),     // Digits
    str_split("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") // Special symbols
));


// Initial User-Agent
$user_agent = str_repeat("A", 137); // Adjusted length
$known_prefix = "guest:" . $user_agent . ":ac65ed4a"; // Base structure
$test_string = substr($known_prefix, -8);

// Function to fetch secure_cookie with a specific User-Agent
function get_secure_cookie($user_agent) {
    global $target_url;

    $context = stream_context_create([
        "http" => [
            "method" => "GET",
            "header" =>
                "Host: cryptofailures.thm\r\n" .
                "User-Agent: $user_agent\r\n" .
                "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\r\n" .
                "Accept-Language: en-US,en;q=0.5\r\n" .
                "Accept-Encoding: gzip, deflate, br\r\n" .
                "Connection: close\r\n" .
                "Upgrade-Insecure-Requests: 1\r\n"
        ]
    ]);

    // Fetch response
    $response = file_get_contents($target_url, false, $context);

    // Extract "secure_cookie" from headers
    foreach ($http_response_header as $header) {
        if (stripos($header, "Set-Cookie: secure_cookie=") !== false) {
            preg_match('/secure_cookie=([^;]+)/', $header, $matches);
            return $matches[1] ?? null;
        }
    }
    return null;
}

// Start brute-force process
$found_text = "";
while (true) {
    echo "\nCurrent known part: {$found_text}\n";

    // Get new secure_cookie for the current prefix
    $secure_cookie = get_secure_cookie($user_agent);
    $user_agent = substr($user_agent, 1);
    if (!$secure_cookie) {
        die("❌ Failed to retrieve secure_cookie!\n");
    }

    echo "✅ Retrieved (Decoded) secure_cookie: $secure_cookie\n";

    // Extract salt
    $salt = substr($secure_cookie, 0, 2);

    // Brute-force the next character
    $found_char = null;
    foreach (str_split($charset) as $char) {
        $test_string_temp = substr($test_string, 1) . $char;
	print($test_string_temp . "\n");
        
        $hashed_test = crypt($test_string_temp, $salt);
        if (str_contains($secure_cookie, $hashed_test)) {
            echo "✅ Found character: $char\n";
            $found_text .= $char;
            $test_string = $test_string_temp;
        }
    }
}

?>

```

{% endcode %}

Unfortunately, the script crashes after running for some time.

Since we know the length of the leaked string and have the last 8 characters, we modify our script to resume from that point.

```
// Initial User-Agent
$user_agent = str_repeat("A", 137); // Adjusted length
$known_prefix = "guest:" . $user_agent . ":ac65ed4a"; // Base structure
$test_string = substr($known_prefix, -8);
```

We combine both extracted parts, reconstructing the full key, and retrieve the final flag.

<figure><img src="/files/mohGarulTUk3FVIuv9J0" alt=""><figcaption></figcaption></figure>
