---
title: PHP2Shell: qué hacer si tu web usa PHP o WordPress
url: https://jarroba.com/php2shell-que-hacer/
author: Ramón Invarato
date: 2026-10-26
modified: 2026-10-26
language: es
license: CC BY-NC-SA 3.0 (https://creativecommons.org/licenses/by-nc-sa/3.0/) — attribution: "PHP2Shell: qué hacer si tu web usa PHP o WordPress", Ramón Invarato, jarroba.com
categories: Tecnologias Web
tags: CVE-2026-109618, CVE-2026-109619, php, PHP2Shell, seguridad, WordPress
---

# PHP2Shell: qué hacer si tu web usa PHP o WordPress

> PHP2Shell: qué versiones de PHP están corregidas, cómo saber cuál sirve tu web, si estuviste expuesto y qué revisar y rotar después.

El 21 de octubre de 2026 el proyecto PHP publicó dos avisos de seguridad críticos, CVE-2026-109618 y CVE-2026-109619, a los que se conoce como PHP2Shell por la [publicación de Wiz Research](https://www.wiz.io/blog/php2shell-php-rce), uno de los equipos que los descubrieron. Los dos permiten ejecutar código en el servidor sin usuario ni contraseña, con la cuenta con la que corre la web, y los dos tienen una nota CVSS de 9,2 sobre 10 ([aviso de CVE-2026-109618](https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w), [aviso de CVE-2026-109619](https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp)).

 

Lo que los hace distintos de los avisos de cada semana es dónde están: en PHP, no en WordPress, ni en un plugin, ni en tu código. En el caso confirmado con Apache y `mod_php`, el fallo salta mientras PHP prepara la petición, «antes de que empiece a ejecutarse ningún código de la aplicación», según el aviso de CVE-2026-109618. Da igual que uses WordPress, Drupal, Laravel o un PHP escrito a mano.

 

En jarroba.com nos llegó la alerta el 23 de octubre. Lo primero fue situar el fallo: una petición a una web pasa por varias capas antes de llegar a los datos, y PHP2Shell está en la de PHP, justo antes de WordPress. Por eso nada de lo que pongas en WordPress lo frena.

 

[![Diagrama de capas: el visitante y el atacante envían peticiones que pasan por internet, un cortafuegos web o CDN opcional y el servidor web hasta PHP. Dentro de PHP, el primer paso, preparar la petición (cabeceras, cuerpo, formularios, subidas y sesiones), está marcado en rojo: ahí salta PHP2Shell, antes de que se ejecute WordPress. Debajo, WordPress con su login, doble factor, captcha y plugins de seguridad, que llegan tarde, y los datos con wp-config.php. Al lado, lo que consigue el atacante: ejecutar código con la cuenta de la web, leer wp-config.php y la base de datos, leer memoria de otras peticiones y dejar puertas traseras.](https://jarroba.com/wp-content/uploads/2026/10/capas-www.jarroba.com_.svg)](https://jarroba.com/wp-content/uploads/2026/10/capas-www.jarroba.com_.svg)

 

## Qué versiones están corregidas

 

| Rama de PHP | Versión con el arreglo |
| --- | --- |
| 8.5 | 8.5.12 |
| 8.4 | 8.4.27 |
| 8.3 | 8.3.36 |
| 8.2 | 8.2.35 |
| 8.1 y anteriores | ninguna oficial (ver debajo) |

 

Las versiones salen de los dos avisos, y el proyecto PHP publicó las cuatro el 21 de octubre, con un [aviso propio](https://www.php.net/archive/2026.php#2026-10-21-5) que resume el problema y las medidas.

 

Las ramas 8.1 y anteriores ya no tienen soporte, y no habrá versión nueva para ellas. Por la gravedad del fallo, el equipo de seguridad de PHP sí publicó el parche en código fuente para [7.4](https://github.com/php/php-src/tree/PHP-7.4-CVE-2026-109618-109619), [8.0](https://github.com/php/php-src/tree/PHP-8.0-CVE-2026-109618-109619) y [8.1](https://github.com/php/php-src/tree/PHP-8.1-CVE-2026-109618-109619), pero solo sirve a quien compila PHP. Si tu PHP viene de una distribución de Linux, puede que ya esté corregido (lo explica la sección siguiente); si no, toca cambiar de rama.

 

Cuidado

 

Los dos avisos coinciden: actualizar es **la única** protección completa. Las medidas de configuración que proponen reducen el riesgo, pero no lo eliminan.

 

## La versión que de verdad sirve tu web

 

Parece fácil: miras la versión de PHP y la comparas con la tabla. En la práctica hay tres trampas.

 

**La cabecera no siempre lo dice todo.** PHP anuncia la versión completa en la cabecera `X-Powered-By` ([`SAPI_PHP_VERSION_HEADER`](https://github.com/php/php-src/blob/php-8.3.36/main/SAPI.h#L288) en su código), pero hay alojamientos que la recortan o la quitan. La nuestra decía `X-Powered-By: PHP/8.3`, y con eso no se sabe si es la 8.3.31 o la 8.3.36.

 

**La consola puede ser otra.** En un alojamiento compartido, el `php -v` que ves por SSH no tiene por qué ser el que ejecuta la web: en muchos paneles cada sitio elige su versión, y la consola puede usar otra.

 

**El número de una distribución no es el de PHP.** Algunas distribuciones de Linux corrigen sus paquetes sin cambiar de versión. En Ubuntu 24.04, `php -v` sigue diciendo 8.3.6, una versión que en la tabla sale vulnerable, y el paquete está corregido desde `8.3.6-0ubuntu0.24.04.13`; en Ubuntu 22.04, el 8.1 lo está desde `8.1.2-1ubuntu2.28` ([seguimiento de Ubuntu](https://ubuntu.com/security/CVE-2026-109618)). Si tu PHP viene de una distribución, lo que manda es la versión del paquete y el aviso de seguridad de esa distribución, no la tabla de arriba.

 

Lo más fiable, en orden:

 
1. En WordPress, *Herramientas → Salud del sitio → Información → Servidor*: es la versión con la que corre la web.

 

[![Escritorio de WordPress con Herramientas y Salud del sitio marcados en el menú lateral y, en la sección Servidor, la fila «Versión de PHP» recuadrada en rojo, con el valor 8.3.36](https://jarroba.com/wp-content/uploads/2026/10/php2shell-salud-del-sitio-www.jarroba.com_.png)](https://jarroba.com/wp-content/uploads/2026/10/php2shell-salud-del-sitio-www.jarroba.com_.png)

 
1. En el panel del alojamiento, la versión configurada para ese sitio.
2. Por SSH, la versión y la fecha de compilación (la primera línea de la salida):

 

```console
$ php -v
PHP 8.3.36 (cli) (built: Oct 21 2026 14:05:40) (NTS)
```

 
1. Si usas Docker, la del contenedor que está sirviendo la web, no la de tu máquina ni la de la imagen que tengas descargada ([`docker exec`](https://docs.docker.com/reference/cli/docker/container/exec/)):

 

```console
$ docker exec <contenedor> php -v
```

 

Una etiqueta como `php:8.3` o `latest` no es una versión: hay que volver a descargar la imagen y reconstruir.

 

## ¿Estuve expuesto antes del parche?

 

Saber que **ahora** estás bien no responde a la pregunta que importa: qué versión tenía tu web los días anteriores. El proveedor no te lo dice, y los registros de acceso no guardan la versión de PHP.

 

En WordPress hay una forma de saberlo sin tocar nada. Cada vez que alguien abre el escritorio (o *Salud del sitio*), WordPress comprueba si su versión de PHP está al día y guarda la respuesta una semana, en una opción cuyo nombre lleva el MD5 de la versión (`wp_check_php_version()`, en [`wp-admin/includes/misc.php`](https://github.com/WordPress/wordpress-develop/blob/7.1.3/src/wp-admin/includes/misc.php)). Si la versión cambió en esa semana, quedan las dos.

 

```console
$ wp transient list --network --search='php_check_*' --fields=name,expiration
$ php -r 'foreach (["8.3.31", "8.3.36"] as $v) echo $v, " ", md5($v), PHP_EOL;'
```

 

El primer comando lista las comprobaciones guardadas y su caducidad; el segundo calcula el MD5 de las versiones que sospeches, para reconocerlas. La caducidad sale en segundos desde 1970 (`date -u -d @<número>` la traduce a UTC), y restándole siete días se sabe **cuándo** se hizo cada comprobación. Así supimos lo nuestro: una comprobación con 8.3.31 del 17 de octubre y otra con 8.3.36 del 21 a las 16:40 UTC. Nuestro proveedor actualizó PHP por su cuenta el mismo día del parche; los días de antes, la web corrió con una versión vulnerable.

 

[![Línea de tiempo del 15 al 28 de octubre: hasta el 21 de octubre la web tenía una versión vulnerable y después la corregida. Una comprobación del escritorio del 17 de octubre a las 19:22 UTC guardó PHP 8.3.31 durante una semana; otra del 21 a las 16:40 UTC guardó PHP 8.3.36, tres horas después de que PHP publicara los avisos (21 de octubre, 13:34 UTC).](https://jarroba.com/wp-content/uploads/2026/10/linea-de-tiempo-www.jarroba.com_.svg)](https://jarroba.com/wp-content/uploads/2026/10/linea-de-tiempo-www.jarroba.com_.svg)

 

Solo funciona si alguien abrió el escritorio o *Salud del sitio* esos días, y solo informa de esas fechas. Tampoco sirve si la web usa una caché de objetos persistente (Redis o Memcached): con ella, la comprobación no se guarda en la base de datos, y wp-cli avisa de que solo lista lo que hay ahí. Y hay que mirarlo pronto: cada comprobación caduca a la semana, y WordPress borra las caducadas una vez al día. Cuando volvimos a mirarlo el 26, la de 8.3.31 ya no estaba. Aun así, fue el único registro de la versión de esos días que encontramos.

 

## Qué hacer ya

 
- **Alojamiento compartido.** El proveedor es quien actualiza PHP. Comprueba la versión como arriba y, si no está corregida, pide el cambio por escrito y vuelve a mirarla después. Si estás en 8.1 o anterior, sube de rama: nosotros lo hicimos desde el panel, y WordPress, los plugins y nuestro código siguieron funcionando sin avisos (el único salió en wp-cli, la herramienta de consola).
- **Servidor propio.** Actualiza PHP y **reinicia** PHP-FPM o Apache: los procesos que ya estaban arrancados siguen con el código viejo hasta que se reinician.
- **Docker.** Reconstruye la imagen con `docker build --pull`, que vuelve a descargar la base ([«Always attempt to pull all referenced images»](https://docs.docker.com/reference/cli/docker/buildx/build/)), y vuelve a desplegar.
- **Si no puedes actualizar ya**, los avisos proponen medidas que reducen el riesgo sin eliminarlo:
- `session.upload_progress.enabled=0`, que corta uno de los dos casos confirmados de CVE-2026-109619. El aviso recomienda dejarlo desactivado **incluso después de parchear** si no usas esa función, porque es poco usada y poco probada, y es donde más gente va a buscar fallos nuevos. El núcleo de WordPress no la usa;
- en PHP 8.5, `fatal_error_backtraces=0`, que corta el otro caso confirmado;
- `memory_limit=-1` impuesto por el administrador, contra CVE-2026-109618, y nunca con `RLIMIT_AS` ni `RLIMIT_DATA`, que según el aviso provocan justo el fallo que se quiere evitar.

 

Las tres se ponen en la configuración que se carga al arrancar (`php.ini`, el *pool* o grupo de procesos de FPM, o la del servidor web), y después hay que reiniciar todos los procesos. No sirven `ini_set()` ni `.user.ini`: según el [aviso de php.net](https://www.php.net/archive/2026.php#2026-10-21-5), se aplican después de que PHP procese la petición, y el fallo salta antes.

 

## Lo que no te protege

 

Los plugins que limitan intentos de login, el doble factor, los captchas y actualizar WordPress son buenas medidas, pero aquí no sirven: el ataque no pasa por el login, y ocurre antes de que WordPress arranque. Lo decía el propio correo que recibimos, de un fabricante de plugins de login, y se agradece la sinceridad.

 

Tampoco te avisará un escáner que solo mire ficheros si lo que se llevaron fue memoria (se explica más abajo).

 

## ¿Me han entrado? Lo que revisamos

 

Lo hicimos por SSH con wp-cli; casi todo se puede hacer también con el gestor de ficheros del panel, con más paciencia. La idea es buscar lo que deja quien consigue ejecutar código: ficheros PHP nuevos, cuentas nuevas y tareas que se repiten.

 
1. **WordPress y los plugins, contra los originales:**

 

```console
$ wp core verify-checksums --include-root 2>&1 | grep -E 'should not exist: .*(\.ph|\.user\.ini)|Success|Error'
$ wp plugin verify-checksums --all
```

 

Lee los avisos, no solo la última línea: un fichero que sobra solo da un `Warning: File should not exist`, y la orden acaba en `Success` igualmente. Con `--include-root` también mira la carpeta raíz de la web, y avisa de todo lo que no es WordPress; el `grep` deja solo los ficheros PHP, los `.user.ini` y el resultado.

 

No cubren los ficheros de traducción `.l10n.php`, que también son PHP, ni un tema propio, ni los plugins que no vienen de wordpress.org (los de pago o los propios): esos se saltan con un aviso, y el resultado sigue siendo `Success`. Compara el tema y esos plugins con tu repositorio si lo tienes.

 
1. **PHP nuevo o cambiado desde antes de la ventana**, en todo tu espacio, no solo en la carpeta de la web:

 

```console
$ cd ~ && find . -newerct 2026-10-15 -type f \
    \( -name '*.php*' -o -name '*.phtml' -o -name '*.phar' -o -name '.user.ini' \) \
    -not -path '*/wp-admin/*' -not -path '*/wp-includes/*' -not -path '*/wp-content/cache/*'
```

 

`-newerct` mira cuándo cambió el fichero en el disco, una fecha que, según el [manual de `touch`](https://www.gnu.org/software/coreutils/manual/html_node/touch-invocation.html), no se puede poner a mano, al contrario que la de modificación. `wp-admin` y `wp-includes` ya los comprobó el paso 1. Y se empieza con `cd ~` porque en algunos alojamientos la carpeta personal es un enlace, y `find ~` no entra en ella: no encuentra nada y no da ningún error. Nos pasó al comprobar las órdenes de este artículo.

 

Saldrán muchas cosas legítimas (actualizaciones, copias restauradas, cambios de permisos): a nosotros, miles. Para verlas por carpetas, añade al final `| cut -d/ -f1-4 | sort | uniq -c | sort -rn`, y mira las que no sepas explicar.

 
1. **Escondites habituales:** PHP dentro de `wp-content/uploads`, la carpeta `wp-content/mu-plugins` (lo que hay ahí se carga siempre, no se puede desactivar desde el escritorio y sale aparte de la lista normal de plugins, según la [documentación de WordPress](https://developer.wordpress.org/advanced-administration/plugins/mu-plugins/)), `.htaccess` cambiados, `.user.ini` nuevos (con `auto_prepend_file` cargan un PHP en cada petición sin tocar WordPress, en servidores con [FPM o CGI](https://www.php.net/manual/en/configuration.file.per-user.php)) y, si tienes SSH, `~/.ssh/authorized_keys`: una clave que no reconoces es una puerta trasera.
2. **Cuentas y accesos:**

 

```console
$ wp user list --role=administrator --fields=ID,user_login,user_registered
$ wp user list --orderby=registered --order=DESC --number=20 --fields=ID,user_login,user_registered
$ wp user application-password list <usuario> --fields=name,created,last_used,last_ip
$ wp cron event list --fields=hook,next_run_relative
```

 

Administradores que no conoces, altas recientes (la segunda orden pone primero las últimas), contraseñas de aplicación que nadie creó (la tercera, para cada administrador; también están en su perfil) y tareas programadas con nombres raros. `created` y `last_used` salen en segundos desde 1970, como la caducidad de antes.

 
1. **Los registros de errores.** El aviso de CVE-2026-109619 pide buscar errores fatales, avisos repetidos antes de que se ejecute el script y `Maximum execution time exceeded`. Pero también avisa de que **no encontrar nada en los registros no demuestra que estés a salvo**. En muchos alojamientos compartidos los registros de errores no están a mano: hay que activar su descarga en el panel. **Hazlo antes de necesitarlos.**

 

En nuestro caso no apareció nada: los ficheros coincidían con los originales y con nuestro repositorio, y no había cuentas nuevas ni tareas raras.

 

## Por qué rotar los secretos aunque no encuentres nada

 

El aviso de CVE-2026-109619 incluye entre los efectos confirmados la **filtración de memoria**: lo que pasó por el servidor en otras peticiones. En un WordPress eso incluye la contraseña de la base de datos y las claves con las que se firman las cookies (se cargan en cada petición), las claves de servicios externos y las cookies de sesión de quien estuviera conectado. Leer memoria no deja ficheros ni cuentas: la revisión de arriba no lo detecta.

 

Si estuviste expuesto, cambia:

 
1. **la contraseña de la base de datos** (en el panel del alojamiento y en `wp-config.php`, justo después);
2. **las claves de servicios externos** que estén en `wp-config.php` (captcha, correo, APIs);
3. **las contraseñas de aplicación** que usen tus integraciones;
4. **las sales de WordPress**, lo último, porque cierran todas las sesiones y tus usuarios tendrán que volver a entrar:

 

```console
$ wp config shuffle-salts
```

 

[![Los cuatro pasos en orden: 1, la contraseña de la base de datos, en el panel del alojamiento y justo después en wp-config.php; 2, las claves externas de captcha, correo y APIs; 3, las contraseñas de aplicación de tus integraciones; 4, las sales con wp config shuffle-salts, que cierran todas las sesiones. Antes de cada paso, una copia de wp-config.php.](https://jarroba.com/wp-content/uploads/2026/10/rotar-orden-www.jarroba.com_.svg)](https://jarroba.com/wp-content/uploads/2026/10/rotar-orden-www.jarroba.com_.svg)

 

Antes de cada cambio, copia `wp-config.php` **fuera de la carpeta de la web** (por ejemplo, a tu carpeta personal) y borra la copia al terminar. Un error en ese fichero deja la web en blanco, y una copia con otro nombre dentro de la web (`wp-config.php.copia`) no la ejecuta PHP, así que el servidor puede entregarla como texto, con la contraseña de la base de datos que estás cambiando.

 

## Si te han entrado

 

La [guía de WordPress para webs atacadas](https://wordpress.org/documentation/article/faq-my-site-was-hacked/) tiene como primer paso algo que parece obvio y no lo es: **apuntarlo todo**. Qué ves, desde cuándo, qué cambiaste esos días. Después, según esa guía:

 
- pregunta a tu proveedor: en un alojamiento compartido el ataque puede afectar a más que tu web;
- revisa si los buscadores o los antivirus han marcado tu web, en Google Search Console y Bing Webmaster;
- cambia **todos** los accesos, de todas las personas: SFTP o FTP, el panel de WordPress, el del alojamiento y la base de datos;
- y analiza también tu propio ordenador: a veces el robo de contraseñas empieza ahí.

 

Por experiencia, añadiríamos una cosa más: **restaurar desde una copia anterior a la ventana de exposición**, no desde la de anoche, que puede llevar dentro lo que dejó el atacante. Para eso hacen falta copias de varios días, guardadas fuera del servidor, y haber probado alguna vez a restaurar una. Nosotros lo hicimos en un entorno aparte: la base de datos y los ficheros levantaron la web en menos de un minuto, con los mismos artículos, usuarios y comentarios que la real. Lo que de verdad lleva tiempo es rehacer `wp-config.php` si no está en la copia (hay quien lo deja fuera a propósito, porque guarda las contraseñas): conviene tener apuntado qué lleva y de dónde sale cada valor.

 

## Lo que aprendimos

 

PHP2Shell nos encontró con lo básico hecho, y eso hizo que revisarlo fuera cuestión de una mañana. También nos enseñó lo que faltaba: tener los registros de errores a mano antes de necesitarlos, y una vigilancia diaria que avise de los cambios sospechosos, en lugar de mirar solo cuando sale una noticia.

 

Y una lección sobre el alojamiento compartido. Que el proveedor actualice PHP solo es una ventaja enorme: el nuestro lo hizo el mismo día. Pero hay que **comprobarlo**, no suponerlo, y hasta ese momento la web estaba expuesta.

 

## Si quieres más

 

- Los dos avisos del proyecto PHP, con el detalle técnico y las medidas: [CVE-2026-109618](https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w) y [CVE-2026-109619](https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp).
- La [guía de WordPress para webs atacadas](https://wordpress.org/documentation/article/faq-my-site-was-hacked/) y la de [endurecimiento](https://developer.wordpress.org/advanced-administration/security/hardening/).
