2026年11月12日
2026年11月12日
Bloquear la ejecución de PHP en el directorio uploads de WordPress
Este artículo es una edición localizada del original en japonés. Leer el original en japonés
Introducción
wp-content/uploads/ es el directorio donde WordPress guarda las imágenes y otros archivos multimedia. Si en este directorio hay archivos PHP y se pueden ejecutar, un atacante puede subir una web shell (puerta trasera) y ejecutar código arbitrario.
Síntomas y causas
- Un análisis de malware detectó archivos PHP dentro de
wp-content/uploads/ - Un plugin de seguridad avisó de que «PHP se puede ejecutar en el directorio uploads»
- Se encontró un archivo sospechoso en una ruta como
wp-content/uploads/2024/01/shell.php - Se instaló una web shell aprovechando una vulnerabilidad de la función de subida de archivos
Pasos para solucionarlo
Paso 1: detecta los archivos PHP sospechosos que ya existan
# Busca todos los archivos PHP dentro del directorio uploads
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
# Comprueba los archivos PHP creados o modificados recientemente (en los últimos 7 días)
find /var/www/html/wp-content/uploads -name "*.php" -mtime -7
# Revisa el contenido de los archivos (patrones típicos de 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/
# Elimina los archivos PHP sospechosos
find /var/www/html/wp-content/uploads -name "*.php" -delete
find /var/www/html/wp-content/uploads -name "*.phtml" -delete
Paso 2: bloquea la ejecución de PHP en Apache (.htaccess)
# Crea wp-content/uploads/.htaccess
cat > /var/www/html/wp-content/uploads/.htaccess << 'EOF'
# Prohíbe por completo la ejecución de archivos PHP
<FilesMatch "\.(php|phtml|php3|php4|php5|php7|phar|shtml)$">
Order Deny,Allow
Deny from all
</FilesMatch>
# Desactiva el propio motor de PHP (si usas mod_php)
<IfModule mod_php.c>
php_flag engine off
</IfModule>
# Prohíbe también la ejecución de CGI
Options -ExecCGI
RemoveHandler .php .phtml .php5 .phar
# Desactiva también el listado de directorios
Options -Indexes
EOF
chmod 644 /var/www/html/wp-content/uploads/.htaccess
# Comprobación tras la configuración
# Crea un archivo PHP de prueba e intenta acceder a él
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
# Si devuelve 403, ha funcionado
rm /var/www/html/wp-content/uploads/test_block.php
Paso 3: bloquea la ejecución de PHP en Nginx
# /etc/nginx/sites-available/example.com
server {
# ... configuración existente ...
# Prohíbe la ejecución de PHP en el directorio uploads
location ~* /wp-content/uploads/.*\.(php|phtml|php5|phar)$ {
deny all;
access_log off;
log_not_found off;
}
# Debe escribirse antes del location de PHP-FPM
location ~ \.php$ {
# Procesamiento normal de PHP
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;
}
}
# Comprueba la configuración de Nginx y aplícala
sudo nginx -t && sudo systemctl reload nginx
# Comprobación
curl -o /dev/null -s -w "%{http_code}" https://example.com/wp-content/uploads/shell.php
# Si devuelve 403, ha funcionado
Paso 4: bloquea también la subida de PHP desde WordPress
// functions.php: prohíbe la propia subida de archivos PHP
// ① Añade las extensiones PHP a la lista de subidas prohibidas
add_filter('upload_mimes', function(array $mimes): array {
// Elimina los tipos MIME relacionados con PHP (por precaución)
unset($mimes['php'], $mimes['phtml'], $mimes['phar']);
return $mimes;
});
// ② Detecta y rechaza los archivos PHP subidos
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'] = 'Está prohibido subir archivos PHP.';
}
// Comprueba también por tipo MIME
if (str_contains($file['type'], 'php')) {
$file['error'] = 'Está prohibido subir archivos PHP.';
}
return $file;
});
Paso 5: configura una supervisión periódica
# Script que supervisa periódicamente los archivos PHP dentro de 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 "Aviso: se han detectado archivos PHP en el directorio uploads:
$PHP_FILES" | mail -s "[URGENTE] Posible web shell instalada en WordPress" "$ADMIN_EMAIL"
# Borrado automático (úsalo con cuidado)
# find "$UPLOADS_DIR" -name "*.php" -delete
fi
EOF
chmod +x /usr/local/bin/check-uploads-php.sh
# Comprobación cada hora
echo "0 * * * * root /usr/local/bin/check-uploads-php.sh" > /etc/cron.d/wp-uploads-php-check
Precauciones
php_flag engine offen el.htaccesssolo es válido si usasmod_php. Si usasphp-fpm(FastCGI), resuélvelo con la directivao con un bloque location de Nginx- En Nginx, si no escribes la regla de bloqueo de uploads antes de
location ~ \.php$, esta última puede tener prioridad y permitir la ejecución. Coloca siempre la regla de bloqueo por encima - Algunos plugins (por ejemplo, Slider Revolution) pueden colocar archivos
.phpdentro de uploads. Antes de borrarlos, comprueba para qué se usan y actúa después de eliminar los plugins que no necesites
Resumen
Para bloquear la ejecución de PHP en el directorio uploads: ① detecta y elimina los archivos sospechosos que ya existan con find wp-content/uploads -name ".php"; ② en Apache, añade a uploads/.htaccess; ③ en Nginx, usa deny all con location ~ /wp-content/uploads/.*\.php$; ④ rechaza la propia subida de PHP con el hook wp_handle_upload_prefilter; ⑤ supervisa los archivos PHP de uploads con un cron cada hora.
¿Dudas sobre WordPress? Quien publica este sitio está en Japón y responde en japonés. Contacto (en japonés)