PHP mail() Command Injection to Remote Code Execution (RCE)

Author: NullSecurityX
Published: February 7, 2026
Source: https://nullsecurityx.medium.com/php-mail-command-injection-to-remote-code-execution-rce-b2991e929ca3

Summary

This writeup walks through a long-standing class of vulnerability in PHP web applications: passing unsanitized user input into the fifth argument ($additional_parameters) of the built-in mail() function. Because PHP’s mail() shells out to the system Mail Transfer Agent (typically sendmail), attacker-controlled data in that argument is appended to the sendmail command line, turning a mundane contact form into a command-execution primitive. The author demonstrates, on a deliberately vulnerable test app, how injected sendmail options escalate to arbitrary file write (a web shell and full Remote Code Execution) and arbitrary file read. This is a technique/vulnerability-class explanation rather than a report on a specific product or assigned CVE; no CVE identifiers, vendors, or version numbers are named in the article.

Technical Details

PHP’s mail() has the signature mail($to, $subject, $message, $additional_headers, $additional_parameters). The first three arguments are required; the last two are optional. The fifth argument, $additional_parameters, is passed straight through to the underlying MTA binary as extra command-line options. Internally mail() invokes sendmail to deliver the message, so anything placed in that argument becomes part of the sendmail command line.

The article’s vulnerable example builds a standard contact form (index.php) that POSTs name, email, subject, and message to a handler (send.php). The handler constructs the call as:

mail($to, $subject, $body, $headers, "-f $sender ");

Here $sender is taken directly from user input and interpolated into the -f (envelope-from) option with no validation or escaping. The root cause is that user-controlled data reaches the system-level mail binary’s argument list. An attacker can break out of the intended -f value and append additional sendmail switches. The switches that make this dangerous include:

  • -C — specify an alternative configuration file
  • -O — override an internal option (e.g. QueueDirectory)
  • -X — write a log of the mail transaction to a file of the attacker’s choosing

These are sendmail options, not PHP options, which is why input sanitization aimed only at PHP or at header injection misses them.

Impact

When the fifth argument of mail() carries unsanitized user input and the MTA is sendmail (or a compatible binary honoring these switches), an attacker can achieve, depending on configuration and filesystem permissions: arbitrary file read, arbitrary file write, command injection, and Remote Code Execution.

RCE via -X log write. Because -X makes sendmail write a transaction log to an attacker-named path, choosing a .php path inside the web root and embedding PHP in the email body causes the server to create a web-accessible PHP file whose contents include the injected code. The article’s demonstration payload, placed in the email field, is:

hacker@bughaneacademy.com -OQueueDirectory=/var/www/html/mailrce/ -X/var/www/html/mailrce/shell.php

with the message body containing a minimal web shell:

<?php system($_GET["cmd"]); ?>

After the mail is processed, shell.php exists in the web root as a log file that also contains executable PHP. Requesting http://target/mailrce/shell.php?cmd=id runs the command — full RCE. The log file has effectively become a web shell.

Arbitrary file read via -C. Pointing -C at an arbitrary file makes sendmail attempt to parse it as a configuration file; its contents (or the resulting parse errors) end up in the transaction log, which -X can direct to a web-readable location. The article’s read payload is:

ozan@bughaneacademy.com -C/var/www/html/mailrce/config.php -OQueueDirectory=/var/www/html/mailrce/ -X/var/www/html/mail/read.txt

Fetching http://target/mail/read.txt then discloses the contents of the targeted file, disclosing sensitive data such as configuration secrets. This turns the mail feature into an indirect arbitrary-file-read primitive.

The demonstration is performed on the author’s own lab application (the bughaneacademy.com addresses and /var/www/html/mailrce/ paths are from that controlled test environment), illustrating the general risk to any PHP application with this pattern rather than a live third-party target.

Mitigation

The article does not reference a vendor patch (there is no single vendor — this is an application-code defect). The defensive guidance follows from the root cause:

  • Never pass user input into the fifth ($additional_parameters) argument of mail(). If an envelope-from must be set from user data, validate it strictly against a safe email pattern and pass it through escapeshellarg() — though the safest choice is to not derive MTA arguments from user input at all.
  • Prefer a hardened mail library (e.g. PHPMailer or Symfony Mailer configured for SMTP) over the raw mail() function, so mail is sent over SMTP rather than by shelling out to a local MTA command line.
  • Sanitize and validate all contact-form fields (recipient, subject, sender, headers) and reject characters that enable header or argument injection.
  • Defense in depth: run PHP with sendmail_path locked down, ensure the web server cannot write PHP files into web-served directories, and disable execution of files in upload/log directories.
  • Detection: alert on sendmail being invoked with -C, -O, or -X options originating from web requests, and on the appearance of new .php files in web-accessible paths.

The broader takeaway from the author: treat every system-level call as a potential command-execution vector, and never assume the mail() function is harmless.

References

Leave a Comment