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 off en el .htaccess solo es válido si usas mod_php. Si usas php-fpm (FastCGI), resuélvelo con la directiva o 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 .php dentro 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 Deny from all 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)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲