How We Audited a High-Traffic WordPress Enterprise Stack on Google Cloud and Mitigated Remote Code Execution (RCE) via insecure file uploads
Initial Reconnaissance and Attack Vector Identification
Our engagement began with a deep dive into the existing WordPress enterprise stack deployed on Google Cloud Platform (GCP). The primary concern was a recent spike in suspicious outbound network traffic and intermittent application slowdowns, hinting at a potential compromise. The architecture involved a multi-instance WordPress setup behind a Google Cloud Load Balancer, utilizing Cloud SQL for the database and Cloud Storage for media uploads. The initial reconnaissance phase focused on identifying potential entry points, with a particular emphasis on custom plugins and themes, as these are frequent sources of vulnerabilities in WordPress deployments.
A critical area of investigation was the file upload functionality. Many WordPress sites, especially enterprise-level ones, integrate with various third-party services or have custom features that involve file uploads. Insecure handling of these uploads is a well-documented vector for Remote Code Execution (RCE). We specifically looked for endpoints that allowed arbitrary file type uploads without proper validation or sanitization.
Deep Dive into File Upload Handler Logic
We identified a custom-built plugin responsible for handling user-submitted documents, which were intended to be stored in Cloud Storage. The plugin’s upload handler, `upload_handler.php`, was a prime candidate for scrutiny. A manual code review, augmented by static analysis tools, revealed a significant flaw:
The plugin accepted files with arbitrary extensions and performed a rudimentary check on the MIME type, but crucially, it did not properly validate the file content or rename the uploaded file in a secure manner before storing it. The core of the vulnerability lay in the following snippet:
// Simplified excerpt from upload_handler.php
if (isset($_FILES['user_document']) && $_FILES['user_document']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_document']['tmp_name'];
$file_name = basename($_FILES['user_document']['name']);
$file_ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
// Basic MIME type check (highly insecure)
$allowed_mime_types = ['image/jpeg', 'image/png', 'application/pdf'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $file_tmp_path);
finfo_close($finfo);
if (in_array($mime_type, $allowed_mime_types)) {
// Insecure storage: uses original filename and doesn't sanitize path
$target_dir = '/var/www/html/uploads/'; // Example path, could be cloud storage mount
$target_file = $target_dir . $file_name;
if (move_uploaded_file($file_tmp_path, $target_file)) {
// Log success, etc.
echo "File uploaded successfully.";
} else {
echo "Error moving file.";
}
} else {
echo "Invalid file type.";
}
}
The primary issue here is the reliance on `basename($_FILES[‘user_document’][‘name’])` for the target filename and the lack of any mechanism to prevent directory traversal or execution of uploaded scripts. An attacker could upload a file named `../../../../../../../../var/www/html/wp-content/uploads/evil.php` (or a similar path leading to a web-accessible directory) with a seemingly valid MIME type (e.g., by crafting a malicious file that `finfo` misidentifies or by exploiting a weakness in `finfo` itself). If the uploaded file’s content was a PHP script, it would then be executable.
Exploitation Scenario: Achieving RCE
To confirm the vulnerability, we simulated an attack. The goal was to upload a PHP webshell and then execute it. The webshell, named `shell.php`, contained simple code to execute arbitrary commands:
<?php
if(isset($_REQUEST['cmd'])){
echo "<pre>";
$cmd = ($_REQUEST['cmd']);
system($cmd);
echo "</pre>";
die;
}
?>
The attacker would then craft an HTTP POST request to the vulnerable upload endpoint. The key was to manipulate the `name` parameter of the file upload to point to a web-accessible directory and append the `.php` extension. Assuming the `uploads` directory was web-accessible and the plugin’s `target_dir` was incorrectly configured or exploitable via path traversal, an attacker could attempt to upload `shell.php` to a location like `wp-content/uploads/`. The `move_uploaded_file` function, when used with a user-controlled filename that isn’t properly sanitized, becomes the pivot point.
A more sophisticated attack might involve uploading a file with a double extension (e.g., `shell.php.jpg`) and relying on server misconfigurations (like Apache’s `AddHandler` or Nginx’s `fastcgi_split_path_info`) to execute it as PHP. However, in this specific case, the direct path traversal vulnerability was sufficient. The attacker would upload `shell.php` using a POST request, ensuring the `name` field in the multipart form data was set to a path that bypasses the intended `target_dir` and lands in a web-executable location, for example, `../../../../wp-content/uploads/shell.php`.
After a successful upload, the attacker would access the webshell via a URL like `https://your-wordpress-site.com/wp-content/uploads/shell.php?cmd=ls -la` to verify command execution.
Mitigation Strategy: Secure File Uploads
The immediate mitigation involved disabling the vulnerable plugin. However, for a permanent solution, a robust file upload handler was developed. This handler incorporates several layers of security:
- Strict File Type Validation: Instead of relying solely on MIME types (which can be spoofed), we validate the file extension against a strict whitelist and, more importantly, perform content-based validation using libraries like `fileinfo` (PHP) or by inspecting file headers.
- Secure Filename Generation: Uploaded files are never stored with their original filenames. Instead, a cryptographically secure random string is generated as the filename, with the original extension appended only after validation.
- Restricted Upload Directories: Uploads are directed to a dedicated, non-web-accessible directory. If web access is required for certain file types (e.g., images), a separate mechanism with strict access controls and potentially a CDN is used.
- Content Sanitization: For certain file types (e.g., images), libraries like GD or Imagick are used to re-process and re-save the file, stripping out any potentially malicious embedded code.
- Server-Side Scanning: Integration with tools like ClamAV for real-time malware scanning of uploaded files.
Here’s an example of a more secure file upload handler in PHP:
// Secure upload handler example
function secure_upload_file($file_input_name, $destination_dir, $allowed_extensions = [], $allowed_mime_types = []) {
if (!isset($_FILES[$file_input_name]) || $_FILES[$file_input_name]['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'message' => 'File upload error or no file selected.'];
}
$file_tmp_path = $_FILES[$file_input_name]['tmp_name'];
$original_filename = $_FILES[$file_input_name]['name'];
$file_size = $_FILES[$file_input_name]['size'];
// 1. Basic size check
if ($file_size > MAX_FILE_SIZE_IN_BYTES) { // Define MAX_FILE_SIZE_IN_BYTES
return ['success' => false, 'message' => 'File is too large.'];
}
// 2. Extension validation
$file_ext = strtolower(pathinfo($original_filename, PATHINFO_EXTENSION));
if (!empty($allowed_extensions) && !in_array($file_ext, $allowed_extensions)) {
return ['success' => false, 'message' => 'Invalid file extension.'];
}
// 3. MIME type validation (using finfo for better accuracy)
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $file_tmp_path);
finfo_close($finfo);
if (!empty($allowed_mime_types) && !in_array($mime_type, $allowed_mime_types)) {
// Further checks might be needed here, e.g., checking against file content headers
return ['success' => false, 'message' => 'Invalid MIME type.'];
}
// 4. Secure filename generation
$new_filename_base = bin2hex(random_bytes(16)); // Generate a random filename
$new_filename = $new_filename_base . '.' . $file_ext;
$target_file_path = rtrim($destination_dir, '/') . '/' . $new_filename;
// Ensure destination directory is writable and exists
if (!is_dir($destination_dir) || !is_writable($destination_dir)) {
// Attempt to create if not exists and permissions allow
if (!is_dir($destination_dir)) {
if (!mkdir($destination_dir, 0755, true)) {
return ['success' => false, 'message' => 'Destination directory is not writable or could not be created.'];
}
} else {
return ['success' => false, 'message' => 'Destination directory is not writable.'];
}
}
// 5. Move the file
if (move_uploaded_file($file_tmp_path, $target_file_path)) {
// Optional: Further processing like image re-compression, virus scanning
// e.g., if (is_image($target_file_path)) { recompress_image($target_file_path); }
// e.g., if (scan_for_malware($target_file_path)) { unlink($target_file_path); return ['success' => false, 'message' => 'Malware detected.']; }
return ['success' => true, 'message' => 'File uploaded successfully.', 'filename' => $new_filename, 'filepath' => $target_file_path];
} else {
return ['success' => false, 'message' => 'Failed to move uploaded file.'];
}
}
// Usage example:
// $upload_result = secure_upload_file(
// 'user_document',
// '/var/www/secure_uploads/', // Ensure this directory is NOT web-accessible
// ['pdf', 'jpg', 'png'],
// ['application/pdf', 'image/jpeg', 'image/png']
// );
//
// if ($upload_result['success']) {
// echo "Uploaded: " . $upload_result['filename'];
// } else {
// echo "Error: " . $upload_result['message'];
// }
GCP Configuration Hardening
Beyond application-level fixes, we reviewed the GCP infrastructure configuration. Key hardening steps included:
- Cloud Storage Bucket Permissions: Ensured that the Cloud Storage bucket used for media uploads was configured with strict IAM policies, preventing public read/write access unless explicitly required and controlled. For non-web-accessible uploads, the bucket was configured with no public access.
- Network Security: Implemented VPC firewall rules to restrict inbound traffic to necessary ports (e.g., 80, 443) and outbound traffic to only essential services. This limits the blast radius if a compromise occurs.
- Compute Engine Instance Security: Ensured Compute Engine instances were running minimal necessary services, had regular security patching applied via OS Patch Management, and used service accounts with the least privilege necessary.
- Web Application Firewall (WAF): Configured Google Cloud Armor to provide an additional layer of defense against common web attacks, including those that might attempt to exploit file upload vulnerabilities. Rules were tailored to block requests with suspicious patterns in URLs or request bodies.
For instance, a typical Cloud Armor security policy might include rules like:
# Example Cloud Armor rule to block common PHP upload attempts
- priority: 100
action: block
match:
versionedExpr: 'SRC_IPS_V1'
config:
srcIpRanges:
- '*'
content:
- 'mimeType=multipart/form-data'
- 'requestUri.path.endsWith(".php")'
- 'requestBody.contains("
It's crucial to note that WAF rules are often signature-based and can be bypassed. They serve as a valuable *additional* layer, not a replacement for secure coding practices.
Post-Mitigation Monitoring and Verification
Following the implementation of the secure upload handler and GCP hardening, a rigorous monitoring and verification phase was initiated. This involved:
- Log Analysis: Continuous monitoring of web server access logs, application logs, and Cloud Audit Logs for any suspicious upload attempts or unusual activity.
- Vulnerability Scanning: Regular automated vulnerability scans (both external and internal) to detect any newly introduced or remaining security weaknesses.
- Penetration Testing: Periodic penetration tests specifically targeting the file upload functionality and other critical application areas.
- Performance Monitoring: Ensuring that the security measures did not introduce significant performance degradation, which could impact user experience and operational costs.
The successful mitigation of the RCE vulnerability through insecure file uploads underscores the importance of a defense-in-depth strategy, combining secure coding practices at the application layer with robust infrastructure security configurations on cloud platforms.