Pulsa ESC para cerrar

Curso WordpressCurso Wordpress

Revisa un plugin .zip antes de instalarlo en WordPress

Instalar un plugin premium descargado de un sitio que no es el del autor (o un «club GPL») es una de las formas más comunes de infectar un WordPress. En un servidor que administro, una tienda terminó con una puerta trasera por un plugin así, y el problema pasó dos meses sin que nadie lo notara.

Por eso armé revisar-zip.sh: revisa el .zip antes de instalarlo, sin que llegue a ejecutarse en el sitio.

Antes de empezar

  • Necesitas un VPS o servidor dedicado con cPanel/WHM y acceso root (desde WHM → Terminal basta).
  • Los scripts se probaron en AlmaLinux 10 con cPanel 138, PHP 8.3 de EasyApache (/opt/cpanel/ea-php83) y WP-CLI instalado en /usr/local/bin/wp. Si tu servidor usa otra versión de PHP, cambia la ruta ea-php83 en el script.
  • Pruébalos primero en un horario tranquilo y lee lo que hace cada uno antes de ejecutarlo. Se comparten tal cual, sin garantía: adáptalos a tu servidor.

Qué hace

  • Crea en cada cuenta de cPanel una carpeta revisar-plugins (al lado de public_html, fuera de la web y con permisos 700) donde subir los zip.
  • Descomprime el zip con el usuario nobody en una carpeta temporal, porque todavía no es de confianza, y la borra al terminar.
  • Busca código malicioso conocido: ejecutar lo que envía un visitante, eval escondido, código ofuscado.
  • Busca archivos raros: .phtml, .phar, .suspected, PHP ocultos y enlaces simbólicos.
  • Muestra señales a revisar: intercepción de conexiones, funciones que ejecutan comandos, creación de usuarios, respaldos de originales (.bak, .backup) y las fechas de los archivos.
  • Lo analiza con ImunifyAV, el antivirus gratuito de cPanel, si está instalado.
  • Busca las marcas de clubes o sitios de plugins no oficiales que tú definas en la variable MARCAS.

Instalación

Entra a WHM → Terminal (como root) y pega este bloque completo de una vez. Crea el archivo /root/revisar-zip.sh, le da permisos y comprueba que quedó bien escrito:

cat > /root/revisar-zip.sh <<'EOF'
#!/bin/bash
# Revisa plugins o temas en .zip SIN instalarlos.
# Cada cuenta tiene una carpeta revisar-plugins (al lado de public_html). Se sube el zip ahi
# con el Administrador de archivos del cPanel de cualquier cuenta y se corre:
#   /root/revisar-zip.sh                  revisa todos los zip de todas las carpetas revisar-plugins
#   /root/revisar-zip.sh nombre.zip       busca ese zip en todas las carpetas revisar-plugins
#   /root/revisar-zip.sh /ruta/archivo.zip  revisa un zip en cualquier otra ubicacion
# Marcas de clubes GPL o sitios de plugins no oficiales que quieras detectar (separadas por |)
MARCAS="nombre-del-club|dominio-del-club\.com"
# crea la carpeta revisar-plugins en las cuentas que no la tengan
for H in /home/*/public_html; do
  U=$(stat -c %U "$H"); C="/home/$U/revisar-plugins"
  [ "$U" = "root" ] && continue
  [ -d "$C" ] || { mkdir "$C" && chown "$U:" "$C" && chmod 700 "$C"; }
done
analizar(){
Z="$1"
# se descomprime con el usuario nobody en una carpeta temporal: el zip no es de confianza
T=$(mktemp -d /var/tmp/revzip-XXXXXX); chown nobody "$T"; chmod 711 "$T"
cp "$Z" "$T/paquete.zip"; chown nobody "$T/paquete.zip"; mkdir "$T/x"; chown nobody "$T/x"
sudo -u nobody -- unzip -qq -o "$T/paquete.zip" -d "$T/x" >/dev/null 2>&1; [ $? -gt 1 ] && { echo "No se pudo descomprimir (zip danado o con contrasena)"; rm -rf "$T"; return; }
find "$T/x" -maxdepth 3 -type f -iname '*.zip' | while read -r z2; do sudo -u nobody -- unzip -qq -o "$z2" -d "${z2%.*}" >/dev/null 2>&1; done
X="$T/x"; MAL=0
F=$(grep -rlE '^[ /*#]*(Plugin|Theme) Name:' "$X" --include=*.php --include=style.css 2>/dev/null | awk -F/ '{print NF"\t"$0}' | sort -n | head -1 | cut -f2-)
echo; echo "===== $Z: $(find "$X" -type f | wc -l) archivos"
[ -n "$F" ] && echo "Contiene: $(grep -m1 -E '(Plugin|Theme) Name:' "$F" | sed 's/.*Name:[ \t]*//' | tr -d '\r')  version $(grep -m1 -E '^[ /*#]*Version:' "$F" | sed 's/.*Version:[ \t]*//' | tr -d '\r')"
echo "--- Marcas del club";                     grep -rlE "$MARCAS" "$X" | sed "s|$X/||"
R=$(grep -rnoE ".{0,50}(eval\s*\(\s*(base64_decode|gzinflate|gzuncompress|str_rot13|\\\$_(POST|GET|REQUEST|COOKIE))|(assert|system|passthru|shell_exec|exec|popen|proc_open)\s*\(\s*\\\$_(POST|GET|REQUEST|COOKIE|SERVER)|base64_decode\s*\(\s*\\\$_(POST|GET|REQUEST|COOKIE)|\\\\x65\\\\x76\\\\x61\\\\x6c).{0,50}" "$X" --include=*.php | grep -v json_decode | sed "s|$X/||" | head -15)
echo "--- Codigo malicioso conocido";           [ -n "$R" ] && { echo "$R"; MAL=1; }
R=$(find "$X" \( -name "*.suspected" -o -name "*.phtml" -o -name "*.phar" -o -name "*.pht" -o -name ".*.php" \) -o -type l | sed "s|$X/||")
echo "--- Archivos raros o enlaces";            [ -n "$R" ] && { echo "$R"; MAL=1; }
echo "--- Intercepta conexiones (normal en los del club por la licencia)"; grep -rl "pre_http_request" "$X" --include=*.php | sed "s|$X/||" | head
echo "--- Funciones que ejecutan comandos o codigo (en plugins grandes suele ser normal)"; grep -rnoE ".{0,40}(\beval\s*\(|passthru\s*\(|shell_exec\s*\(|\bsystem\s*\(|proc_open\s*\(|\bpopen\s*\(|create_function).{0,40}" "$X" --include=*.php | sed "s|$X/||" | head -10
echo "--- Codigo ofuscado";                     grep -rnoE ".{0,40}(gzinflate|str_rot13|'g'\.'zi'|'base'\.'6').{0,40}" "$X" --include=*.php | sed "s|$X/||" | head -10
echo "--- Crea usuarios o roles";               grep -rlE "wp_create_user|wp_insert_user|add_role\s*\(|wp_set_auth_cookie" "$X" --include=*.php | sed "s|$X/||" | head
echo "--- Respaldos de originales (senal de que alguien modifico el plugin)"; find "$X" -type f \( -name "*.bak" -o -name "*.backup" -o -name "*.orig" -o -name "*.old" \) | sed "s|$X/||"
echo "--- Fechas de los archivos (las distintas al resto delatan cambios)"; find "$X" -type f -printf "%TY-%Tm-%Td\n" | sort | uniq -c | sort -rn | head -5
echo "--- ImunifyAV (tarda hasta un par de minutos)"
if command -v imunify-antivirus >/dev/null; then
  imunify-antivirus malware on-demand start --path="$X" >/dev/null 2>&1; sleep 45
  for i in $(seq 1 12); do imunify-antivirus malware on-demand status 2>/dev/null | grep -qi running || break; sleep 10; done
  R=$(imunify-antivirus malware malicious list 2>/dev/null | grep -oE "$X/[^ ]+" | sort -u | sed "s|$X/||")
  [ -n "$R" ] && { echo "$R"; MAL=1; } || echo "sin detecciones"
fi
rm -rf "$T"
echo; [ $MAL = 0 ] && echo "RESULTADO: sin hallazgos graves, se puede instalar" || echo "RESULTADO: HAY HALLAZGOS, NO instalar sin revisarlo"
}
if [ -z "$1" ]; then
  L=$(ls -1 /home/*/revisar-plugins/*.zip /home/*/revisar-plugins/*.ZIP 2>/dev/null)
  [ -n "$L" ] || { echo "No hay zip en ninguna carpeta revisar-plugins."; echo "Sube el zip a /home/CUENTA/revisar-plugins con el Administrador de archivos del cPanel de cualquier cuenta."; exit 0; }
  echo "Zip encontrados:"; echo "$L"
  echo "$L" | while IFS= read -r f; do analizar "$f"; done
elif [ -f "$1" ]; then
  analizar "$1"
else
  L=$(ls -1 /home/*/revisar-plugins/"$1" 2>/dev/null)
  [ -n "$L" ] || { echo "No encuentro '$1' en ninguna carpeta revisar-plugins. Zip disponibles:"; ls -1 /home/*/revisar-plugins/*.zip 2>/dev/null || echo "(ninguno)"; exit 1; }
  echo "$L" | while IFS= read -r f; do analizar "$f"; done
fi
EOF
chmod 700 /root/revisar-zip.sh && bash -n /root/revisar-zip.sh && echo SCRIPT_OK

Si al final aparece SCRIPT_OK, el script está listo. Si no aparece, el bloque se pegó incompleto o con comillas cambiadas: vuelve a pegarlo.

Edita la línea MARCAS= con los nombres o dominios que quieras detectar (por ejemplo, el nombre del club donde compras plugins), separados por |.

Cómo usarlo

  1. Crea las carpetas (solo la primera vez y al crear cuentas nuevas): corre el script una vez, aunque no haya ningún zip. Luego comprueba con ls -ld /home/*/revisar-plugins.
  2. Sube el zip: en el cPanel de la cuenta, Administrador de archivos → carpeta revisar-plugins → Cargar.
  3. Revísalo con una de estas formas:
/root/revisar-zip.sh                          # todos los zip de todas las carpetas revisar-plugins
/root/revisar-zip.sh nombre-del-plugin.zip    # busca ese zip en todas las cuentas
/root/revisar-zip.sh /ruta/archivo.zip        # un zip en cualquier otra ubicación

Tarda cerca de un minuto por zip, sobre todo por el análisis de ImunifyAV. Si el nombre tiene espacios, va entre comillas.

Cómo leer el resultado

  • «RESULTADO: sin hallazgos graves, se puede instalar»: súbelo desde WordPress → Plugins → Añadir nuevo → Subir plugin, y después borra el zip de revisar-plugins.
  • «RESULTADO: HAY HALLAZGOS, NO instalar»: no lo instales hasta entender qué encontró.
  • En plugins de clubes GPL es normal ver la marca del club, «Intercepta conexiones» (así reemplazan la licencia) y a veces archivos .backup. «Funciones que ejecutan comandos» también aparece en plugins grandes y legítimos. Lo preocupante es el código malicioso, los archivos raros o una detección de ImunifyAV.

Ningún análisis es perfecto: este script atrapa lo conocido y lo evidente. La mejor protección sigue siendo comprar el plugin al autor o usar la versión gratuita de wordpress.org.