2026年11月12日
2026年11月12日
Block PHP Execution in the WordPress Uploads Directory
This article is a localized edition of the Japanese original. Read the Japanese original
Introduction
wp-content/uploads/ is the directory where WordPress stores images and other media. If PHP files can be placed in this directory and executed, an attacker can upload a web shell (backdoor) and run arbitrary code.
Symptoms and Causes
- A malware scan found PHP files inside
wp-content/uploads/ - A security plugin warned that PHP can be executed in the uploads directory
- A suspicious file was found at a path such as
wp-content/uploads/2024/01/shell.php - A web shell was planted by exploiting a vulnerability in a file upload feature
How to Fix It
Step 1: Find existing suspicious PHP files
# Search for every PHP file in the uploads directory
find /var/www/html/wp-content/uploads -name "*.php" -type f
find /var/www/html/wp-content/uploads -name "*.phtml" -type f
find /var/www/html/wp-content/uploads -name "*.php5" -type f
# Check PHP files created or modified recently (within 7 days)
find /var/www/html/wp-content/uploads -name "*.php" -mtime -7
# Inspect file contents (patterns typical of malware)
grep -rl "eval(base64_decode" /var/www/html/wp-content/uploads/
grep -rl "system\|exec\|passthru\|shell_exec" /var/www/html/wp-content/uploads/
# Delete suspicious PHP files
find /var/www/html/wp-content/uploads -name "*.php" -delete
find /var/www/html/wp-content/uploads -name "*.phtml" -delete
Step 2: Block PHP execution on Apache (.htaccess)
# Create wp-content/uploads/.htaccess
cat > /var/www/html/wp-content/uploads/.htaccess << 'EOF'
# Completely block execution of PHP files
<FilesMatch "\.(php|phtml|php3|php4|php5|php7|phar|shtml)$">
Order Deny,Allow
Deny from all
</FilesMatch>
# Turn off the PHP engine itself (when using mod_php)
<IfModule mod_php.c>
php_flag engine off
</IfModule>
# Also block CGI execution
Options -ExecCGI
RemoveHandler .php .phtml .php5 .phar
# Also disable directory listing
Options -Indexes
EOF
chmod 644 /var/www/html/wp-content/uploads/.htaccess
# Check after configuring
# Create a test PHP file and try to access it
echo "<?php echo 'test'; ?>" > /var/www/html/wp-content/uploads/test_block.php
curl -o /dev/null -s -w "%{http_code}" https://example.com/wp-content/uploads/test_block.php
# A 403 means it worked
rm /var/www/html/wp-content/uploads/test_block.php
Step 3: Block PHP execution on Nginx
# /etc/nginx/sites-available/example.com
server {
# ... existing settings ...
# Block PHP execution in the uploads directory
location ~* /wp-content/uploads/.*\.(php|phtml|php5|phar)$ {
deny all;
access_log off;
log_not_found off;
}
# Place this before the PHP-FPM location block
location ~ \.php$ {
# Normal PHP handling
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
# Test the Nginx configuration and apply it
sudo nginx -t && sudo systemctl reload nginx
# Check
curl -o /dev/null -s -w "%{http_code}" https://example.com/wp-content/uploads/shell.php
# A 403 means it worked
Step 4: Block PHP uploads in WordPress too
// functions.php: block uploading PHP files in the first place
// (1) Add PHP extensions to the blocked upload list
add_filter('upload_mimes', function(array $mimes): array {
// Remove PHP-related MIME types (just in case)
unset($mimes['php'], $mimes['phtml'], $mimes['phar']);
return $mimes;
});
// (2) Detect and reject PHP in uploaded files
add_filter('wp_handle_upload_prefilter', function(array $file): array {
$dangerous_extensions = ['php', 'phtml', 'php3', 'php4', 'php5', 'phar', 'shtml'];
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (in_array($ext, $dangerous_extensions, true)) {
$file['error'] = 'Uploading PHP files is not allowed.';
}
// Check the MIME type as well
if (str_contains($file['type'], 'php')) {
$file['error'] = 'Uploading PHP files is not allowed.';
}
return $file;
});
Step 5: Set up regular monitoring
# Script that regularly checks for PHP files in uploads
cat > /usr/local/bin/check-uploads-php.sh << 'EOF'
#!/bin/bash
UPLOADS_DIR="/var/www/html/wp-content/uploads"
ADMIN_EMAIL="admin@example.com"
PHP_FILES=$(find "$UPLOADS_DIR" -name "*.php" -o -name "*.phtml" -o -name "*.phar" 2>/dev/null)
if [ -n "$PHP_FILES" ]; then
echo "Warning: PHP files were detected in the uploads directory:
$PHP_FILES" | mail -s "[URGENT] A web shell may have been planted on WordPress" "$ADMIN_EMAIL"
# Automatic deletion (use with care)
# find "$UPLOADS_DIR" -name "*.php" -delete
fi
EOF
chmod +x /usr/local/bin/check-uploads-php.sh
# Check every hour
echo "0 * * * * root /usr/local/bin/check-uploads-php.sh" > /etc/cron.d/wp-uploads-php-check
Caveats
php_flag engine offin.htaccessonly works when you usemod_php. If you usephp-fpm(FastCGI), handle it with the<FilesMatch>directive or an Nginx location block- On Nginx, if you do not place the uploads block rule before
location ~ \.php$, the latter may take precedence and the PHP file can still run. Always put the block rule first - Some plugins (for example, Slider Revolution) may place
.phpfiles inside uploads. Check what a file is for before deleting it, and remove plugins you no longer need before taking action
Summary
To block PHP execution in the uploads directory: (1) find and delete existing suspicious files with find wp-content/uploads -name ".php"; (2) on Apache, add <FilesMatch "\.(php|phar)$"> Deny from all to uploads/.htaccess; (3) on Nginx, set location ~ /wp-content/uploads/.*\.php$ to deny all; (4) reject PHP uploads themselves with the wp_handle_upload_prefilter hook; (5) monitor uploads for PHP files with an hourly cron job.
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)