[SECURITY] Backdoors detectados en WordPress de producción (health-check.php + wp-highlits) #183

Open
opened 2026-07-29 06:33:13 +00:00 by rafa · 21 comments
Owner

Incidente de seguridad: backdoors WordPress detectados en producción

Severidad: crítica — pendiente de contención.

Hallazgos confirmados (solo lectura)

  1. MU-plugin no estándar y cargado automáticamente:

    • Ruta: /web/wp-content/mu-plugins/health-check.php
    • SHA-256: bbaeb9a2f7574d8ae6233c5bbb9cc18340dd0c6e86043460f4d9c0baf766e65f
    • Se presenta falsamente como “Health Check - Troubleshooting”, versión 23.42, autor “Team Yoast”.
    • Contiene una ruta POST admintool que itera administradores y ejecuta wp_set_current_user() + wp_set_auth_cookie(); también intenta bloquear su desactivación.
    • Provocó el fatal notificado por WordPress al llamar do_action('wp_login', $username) con un único argumento frente a LLAR 3.3.1, que requiere dos.
  2. Plugin activo sospechoso:

    • Ruta: /web/wp-content/plugins/wp-highlits/wp-highlits.php
    • SHA-256: d34b4e9b29ecef811dabf52bb47b1415f00fdc2a7784fcb6dffdc668408bd0c3
    • Activo como wp-highlits/wp-highlits.php.
    • Se hace pasar por “Schema & Structured Data for WP”, con versión anómala 7.499776, código ofuscado y referencias engañosas a Wordfence.
  3. Correlación temporal:

    • health-check.php: 2026-07-29 05:02:53 UTC
    • wp-highlits.php: 2026-07-29 05:03:37 UTC
    • Diferencia: 44 segundos; probable misma intrusión/deployment.
  4. Usuario andrey:

    • Existe en producción y en la copia local con el mismo ID y metadatos:
      • ID 1087, login andrey, email andrey@pabloarias.eu
      • registro 2018-02-05 19:03:42
      • capability administrator
    • Esto prueba que no fue creado por este incidente reciente, pero no acredita que siga siendo una cuenta autorizada hoy. Requiere validación editorial/propietario.

Acciones pendientes

  • Backup forense inmutable de ambos artefactos y hashes.
  • Contener: retirar de carga health-check.php y desactivar/aislar wp-highlits sin borrarlos hasta preservar evidencia.
  • Verificar home, login y escritorio tras la contención.
  • Revisar logs del hosting, especialmente el intervalo de modificación, para atribuir vector de entrada.
  • Inventariar/validar administradores, sesiones y application passwords; rotar credenciales según resultado.
  • Escanear resto del árbol WordPress para persistencia adicional.

No se han aplicado cambios en producción durante este diagnóstico.

## Incidente de seguridad: backdoors WordPress detectados en producción **Severidad:** crítica — pendiente de contención. ### Hallazgos confirmados (solo lectura) 1. MU-plugin no estándar y cargado automáticamente: - Ruta: `/web/wp-content/mu-plugins/health-check.php` - SHA-256: `bbaeb9a2f7574d8ae6233c5bbb9cc18340dd0c6e86043460f4d9c0baf766e65f` - Se presenta falsamente como “Health Check - Troubleshooting”, versión `23.42`, autor “Team Yoast”. - Contiene una ruta POST `admintool` que itera administradores y ejecuta `wp_set_current_user()` + `wp_set_auth_cookie()`; también intenta bloquear su desactivación. - Provocó el fatal notificado por WordPress al llamar `do_action('wp_login', $username)` con un único argumento frente a LLAR 3.3.1, que requiere dos. 2. Plugin activo sospechoso: - Ruta: `/web/wp-content/plugins/wp-highlits/wp-highlits.php` - SHA-256: `d34b4e9b29ecef811dabf52bb47b1415f00fdc2a7784fcb6dffdc668408bd0c3` - Activo como `wp-highlits/wp-highlits.php`. - Se hace pasar por “Schema & Structured Data for WP”, con versión anómala `7.499776`, código ofuscado y referencias engañosas a Wordfence. 3. Correlación temporal: - `health-check.php`: 2026-07-29 05:02:53 UTC - `wp-highlits.php`: 2026-07-29 05:03:37 UTC - Diferencia: 44 segundos; probable misma intrusión/deployment. 4. Usuario `andrey`: - Existe en producción **y en la copia local** con el mismo ID y metadatos: - ID `1087`, login `andrey`, email `andrey@pabloarias.eu` - registro `2018-02-05 19:03:42` - capability `administrator` - Esto prueba que no fue creado por este incidente reciente, pero no acredita que siga siendo una cuenta autorizada hoy. Requiere validación editorial/propietario. ### Acciones pendientes - [ ] Backup forense inmutable de ambos artefactos y hashes. - [ ] Contener: retirar de carga `health-check.php` y desactivar/aislar `wp-highlits` sin borrarlos hasta preservar evidencia. - [ ] Verificar home, login y escritorio tras la contención. - [ ] Revisar logs del hosting, especialmente el intervalo de modificación, para atribuir vector de entrada. - [ ] Inventariar/validar administradores, sesiones y application passwords; rotar credenciales según resultado. - [ ] Escanear resto del árbol WordPress para persistencia adicional. **No se han aplicado cambios en producción durante este diagnóstico.**
Author
Owner

Contención inicial — cuentas legacy deshabilitadas

Autorización explícita recibida para deshabilitar andrey, pabloarias y josek.

Aplicado y verificado server-side

Usuario ID Resultado
andrey 1087 login bloqueado; reset bloqueado; sesiones revocadas
pabloarias 1047 login bloqueado; reset bloqueado; sesiones revocadas
josek 1049 login bloqueado; reset bloqueado; sesiones revocadas

Mecanismo: MU-plugin limitado a estos tres IDs (fea-security-blocked-users.php), contraseñas aleatorias rotadas, tokens de sesión y application passwords revocados. Verificación: los tres devuelven login_blocked=true, reset_allowed=false, sin sesiones persistentes.

Evidencia

  • Snapshot previo de usuarios y metadatos: /web/wp-content/uploads/forensics/incident-183/disabled-users-before.json
  • Plugin de contención desplegado: /web/wp-content/mu-plugins/fea-security-blocked-users.php
  • Copia local del plugin: logs/incident-183/fea-security-blocked-users.php

Identidad de calvo

  • ID 396, Rafael Calvo Beca, info@feadulta.com, administrador desde 2012, 275 posts. No se ha tocado.

Logs

El acceso disponible desde la cuenta de hosting solo expone /logs/antimalware.log (49 MB), no access logs web. Su cola llega hasta 10-jun y registra un scan que detectó malware JS en /home/feadulta.com/web/media/com_rsform/js/jquerycalendar/jquery.datetimepicker.js; no permite atribuir los accesos recientes ni la subida de los dos backdoors. Se requiere al proveedor del hosting export de access/error logs del intervalo de modificación.

## Contención inicial — cuentas legacy deshabilitadas Autorización explícita recibida para deshabilitar **andrey**, **pabloarias** y **josek**. ### Aplicado y verificado server-side | Usuario | ID | Resultado | |---|---:|---| | andrey | 1087 | login bloqueado; reset bloqueado; sesiones revocadas | | pabloarias | 1047 | login bloqueado; reset bloqueado; sesiones revocadas | | josek | 1049 | login bloqueado; reset bloqueado; sesiones revocadas | Mecanismo: MU-plugin limitado a estos tres IDs (`fea-security-blocked-users.php`), contraseñas aleatorias rotadas, tokens de sesión y application passwords revocados. Verificación: los tres devuelven `login_blocked=true`, `reset_allowed=false`, sin sesiones persistentes. ### Evidencia - Snapshot previo de usuarios y metadatos: `/web/wp-content/uploads/forensics/incident-183/disabled-users-before.json` - Plugin de contención desplegado: `/web/wp-content/mu-plugins/fea-security-blocked-users.php` - Copia local del plugin: `logs/incident-183/fea-security-blocked-users.php` ### Identidad de `calvo` - ID 396, `Rafael Calvo Beca`, `info@feadulta.com`, administrador desde 2012, 275 posts. No se ha tocado. ### Logs El acceso disponible desde la cuenta de hosting solo expone `/logs/antimalware.log` (49 MB), no access logs web. Su cola llega hasta 10-jun y registra un scan que detectó malware JS en `/home/feadulta.com/web/media/com_rsform/js/jquerycalendar/jquery.datetimepicker.js`; no permite atribuir los accesos recientes ni la subida de los dos backdoors. Se requiere al proveedor del hosting export de access/error logs del intervalo de modificación.
Author
Owner

Contención de artefactos completada

Evidencia preservada

Copias byte a byte y manifest SHA-256 guardados localmente:

/home/rafa/joomla-migration/logs/incident-183/evidence/

  • health-check.php — SHA-256 bbaeb9a2f7574d8ae6233c5bbb9cc18340dd0c6e86043460f4d9c0baf766e65f
  • wp-highlits.php — SHA-256 d34b4e9b29ecef811dabf52bb47b1415f00fdc2a7784fcb6dffdc668408bd0c3

Cuarentena reversible aplicada

  • /web/wp-content/mu-plugins/health-check.phphealth-check.php.quarantined-incident-183
  • /web/wp-content/plugins/wp-highlits/wp-highlits.quarantined-incident-183/

No se borraron archivos.

Verificación posterior server-side

  • health-check.php ausente de la carga: PASS
  • wp-highlits ausente de la carga: PASS
  • Hook malicioso admin_login_check: ausente
  • Home vía WordPress HTTP: 200
  • /wp-login.php vía WordPress HTTP: 301 (respuesta normal de redirección)

Siguiente fase: atribución/vector de entrada, revisión de logs del proveedor, escaneo de persistencia adicional y rotación ordenada de secretos.

## Contención de artefactos completada ### Evidencia preservada Copias byte a byte y manifest SHA-256 guardados localmente: `/home/rafa/joomla-migration/logs/incident-183/evidence/` - `health-check.php` — SHA-256 `bbaeb9a2f7574d8ae6233c5bbb9cc18340dd0c6e86043460f4d9c0baf766e65f` - `wp-highlits.php` — SHA-256 `d34b4e9b29ecef811dabf52bb47b1415f00fdc2a7784fcb6dffdc668408bd0c3` ### Cuarentena reversible aplicada - `/web/wp-content/mu-plugins/health-check.php` → `health-check.php.quarantined-incident-183` - `/web/wp-content/plugins/wp-highlits/` → `wp-highlits.quarantined-incident-183/` No se borraron archivos. ### Verificación posterior server-side - `health-check.php` ausente de la carga: PASS - `wp-highlits` ausente de la carga: PASS - Hook malicioso `admin_login_check`: ausente - Home vía WordPress HTTP: 200 - `/wp-login.php` vía WordPress HTTP: 301 (respuesta normal de redirección) Siguiente fase: atribución/vector de entrada, revisión de logs del proveedor, escaneo de persistencia adicional y rotación ordenada de secretos.
Author
Owner

Escaneo de persistencia adicional — primera pasada (solo lectura)

Artefactos y estado de las comprobaciones:

  • Escaneo del árbol WordPress activo por PHP en uploads, MU-plugins, drop-ins y archivos con mtime correlacionado: no hay un tercer candidato con patrones de ofuscación/ejecución equivalente a los dos artefactos ya aislados.
  • No hay drop-ins activos (advanced-cache.php, db.php, object-cache.php, sunrise.php).
  • Cron de WordPress: solo hooks de core/ACF/opt-in conocido; no hay hooks con nombres de shell, eval o descarga sospechosa.
  • Los únicos dos .php bajo wp-content/uploads son runners internos de avatares import_prod_62.php y import_prod_90.php; inspeccionados: scripts declarados para los issues de importación de avatar, sin ofuscación ni funcionalidad de backdoor.
  • El directorio /web/antiguo (Joomla legacy) genera cache PHP continuamente; queda como superficie separada a inventariar y decidir si debe permanecer expuesta. Sus archivos cache no se han tratado como evidencia de compromiso por mtime.
  • wp-highlits/wp-highlits.php aún aparece en la opción active_plugins porque se aisló por filesystem; no se carga ya. Pendiente limpiar esa referencia de configuración como parte del cierre controlado.

Artefactos locales:

  • logs/incident-183/filesystem-scan-window.json
  • logs/incident-183/wp-state-scan.json

La atribución sigue bloqueada hasta recibir access/error logs del proveedor.

## Escaneo de persistencia adicional — primera pasada (solo lectura) Artefactos y estado de las comprobaciones: - Escaneo del árbol WordPress activo por PHP en `uploads`, MU-plugins, drop-ins y archivos con mtime correlacionado: no hay un tercer candidato con patrones de ofuscación/ejecución equivalente a los dos artefactos ya aislados. - No hay drop-ins activos (`advanced-cache.php`, `db.php`, `object-cache.php`, `sunrise.php`). - Cron de WordPress: solo hooks de core/ACF/opt-in conocido; no hay hooks con nombres de shell, eval o descarga sospechosa. - Los únicos dos `.php` bajo `wp-content/uploads` son runners internos de avatares `import_prod_62.php` y `import_prod_90.php`; inspeccionados: scripts declarados para los issues de importación de avatar, sin ofuscación ni funcionalidad de backdoor. - El directorio `/web/antiguo` (Joomla legacy) genera cache PHP continuamente; queda como superficie separada a inventariar y decidir si debe permanecer expuesta. Sus archivos cache no se han tratado como evidencia de compromiso por mtime. - `wp-highlits/wp-highlits.php` aún aparece en la opción `active_plugins` porque se aisló por filesystem; no se carga ya. Pendiente limpiar esa referencia de configuración como parte del cierre controlado. Artefactos locales: - `logs/incident-183/filesystem-scan-window.json` - `logs/incident-183/wp-state-scan.json` La atribución sigue bloqueada hasta recibir access/error logs del proveedor.
Collaborator

Aporte desde el lado de los bots (Inma)

Verificación externa post-contención (solo lectura, 29-jul tarde)

  • Los dos backdoors ya no responden en sus rutas. Calibrado contra un fichero inexistente: las rutas que no existen físicamente devuelven 301 → antiguo.feadulta.com (tanto en www como en wp-nuevo), y ambos artefactos (wp-content/plugins/wp-highlits/wp-highlits.php y wp-content/mu-plugins/health-check.php) dan exactamente esa respuesta. En cambio wp-content/mu-plugins/fea-security-blocked-users.php devuelve 500 (existe y casca al invocarlo a pelo, como debe). Conclusión: retirada de ficheros confirmada de forma independiente desde fuera.
  • Sin señales de manipulación de contenido. Los posts más recientes por fecha de modificación (API REST pública) son del 25-jul: la carta 736 y sus traducciones, todo legítimo. Nada tocado en la ventana del ataque.
  • Home, wp-login y REST de wp-nuevo responden 200 OK.

Línea temporal — descartado el PC de los bots como vector

El 29-jul las automatizaciones locales corrieron a las 03:00 (teologasbot) y a las 08:00 (escritoresbot, recordatoriobot), hora local. En el intervalo de subida de los backdoors (07:02–07:03 local / 05:02–05:03 UTC) no hubo ninguna actividad contra WordPress desde este equipo.

Usuario de las automatizaciones

Los bots publican con el usuario icalvotorre (application password, contra wp-nuevo). Al inventariar/validar administradores: es legítimo y debe conservarse. Si hay que rotar application passwords en bloque, avisad antes para actualizar el .env en el momento y que no se pare la cadena de la carta.

Cuentas legacy

Confirmado (Inma, por WhatsApp): andrey (pabloarias.eu) y josek fueron usuarios legítimos de la etapa Joomla. Correcto mantenerlos deshabilitados hoy.

Duda que queda viva

¿El escaneo del resto del árbol WordPress (persistencia adicional) está hecho o pendiente? Es el punto del checklist que más nos preocupa de cara a dar la web por limpia, junto con los access logs del hosting para atribuir el vector.

## Aporte desde el lado de los bots (Inma) ### Verificación externa post-contención (solo lectura, 29-jul tarde) - **Los dos backdoors ya no responden en sus rutas.** Calibrado contra un fichero inexistente: las rutas que no existen físicamente devuelven `301 → antiguo.feadulta.com` (tanto en www como en wp-nuevo), y ambos artefactos (`wp-content/plugins/wp-highlits/wp-highlits.php` y `wp-content/mu-plugins/health-check.php`) dan exactamente esa respuesta. En cambio `wp-content/mu-plugins/fea-security-blocked-users.php` devuelve 500 (existe y casca al invocarlo a pelo, como debe). Conclusión: retirada de ficheros **confirmada de forma independiente desde fuera**. - **Sin señales de manipulación de contenido.** Los posts más recientes por fecha de modificación (API REST pública) son del 25-jul: la carta 736 y sus traducciones, todo legítimo. Nada tocado en la ventana del ataque. - **Home, wp-login y REST de wp-nuevo responden 200 OK.** ### Línea temporal — descartado el PC de los bots como vector El 29-jul las automatizaciones locales corrieron a las 03:00 (teologasbot) y a las 08:00 (escritoresbot, recordatoriobot), hora local. En el intervalo de subida de los backdoors (07:02–07:03 local / 05:02–05:03 UTC) **no hubo ninguna actividad contra WordPress desde este equipo**. ### Usuario de las automatizaciones Los bots publican con el usuario `icalvotorre` (application password, contra wp-nuevo). Al inventariar/validar administradores: **es legítimo y debe conservarse**. Si hay que rotar application passwords en bloque, avisad antes para actualizar el `.env` en el momento y que no se pare la cadena de la carta. ### Cuentas legacy Confirmado (Inma, por WhatsApp): `andrey` (pabloarias.eu) y `josek` fueron usuarios legítimos de la etapa Joomla. Correcto mantenerlos deshabilitados hoy. ### Duda que queda viva ¿El **escaneo del resto del árbol WordPress** (persistencia adicional) está hecho o pendiente? Es el punto del checklist que más nos preocupa de cara a dar la web por limpia, junto con los access logs del hosting para atribuir el vector.
Author
Owner

Joomla sys.php: evidencia preservada y shell retirada de carga

Confirmación del artefacto

El fichero detectado por CDmon era una web shell funcional, no un falso positivo:

  • Ruta activa previa: /web/antiguo/modules/mod_menu/sys.php
  • SHA-256: 8152c22e26a7135b81d47d1de1c8454d1f4ed566193eaf2038af39152e33092e
  • Tamaño: 1.833 bytes
  • mtime: 2020-10-25T12:25:32Z (fecha del contenido; no atribuye el acceso actual)
  • Funciones: exploración del filesystem con cambio de directorio y subida arbitraria de archivos vía POST. No incluye un executor de comandos visible, pero sí permite plantar payloads en directorios escribibles.

Se preservó copia forense local y se ha puesto en cuarentena reversible:

/web/antiguo/modules/mod_menu/sys.php
→ /web/antiguo/modules/mod_menu/sys.php.quarantined-incident-183

Logs recuperados por SSH

SSH sí da acceso de lectura a la misma carpeta /errors que CDmon expone por FTP. Se preservaron localmente los ficheros de 27 y 28-jul:

  • errors.log.20260727.bz2 y errors.log.20260728.bz2
  • php_errors.log.20260727.bz2 y php_errors.log.20260728.bz2

Son principalmente logs de error/ModSecurity, no access logs completos. Contienen intentos RCE bloqueados por ModSecurity a través de IPs Cloudflare, pero ninguna petición a sys.php ni evidencia de la subida/creación de los backdoors. Siguen faltando access logs HTTP y logs FTP/SFTP/panel para atribución.

## Joomla `sys.php`: evidencia preservada y shell retirada de carga ### Confirmación del artefacto El fichero detectado por CDmon era una web shell funcional, no un falso positivo: - Ruta activa previa: `/web/antiguo/modules/mod_menu/sys.php` - SHA-256: `8152c22e26a7135b81d47d1de1c8454d1f4ed566193eaf2038af39152e33092e` - Tamaño: 1.833 bytes - `mtime`: `2020-10-25T12:25:32Z` (fecha del contenido; no atribuye el acceso actual) - Funciones: exploración del filesystem con cambio de directorio y subida arbitraria de archivos vía POST. No incluye un executor de comandos visible, pero sí permite plantar payloads en directorios escribibles. Se preservó copia forense local y se ha puesto en cuarentena reversible: ```text /web/antiguo/modules/mod_menu/sys.php → /web/antiguo/modules/mod_menu/sys.php.quarantined-incident-183 ``` ### Logs recuperados por SSH SSH sí da acceso de lectura a la misma carpeta `/errors` que CDmon expone por FTP. Se preservaron localmente los ficheros de 27 y 28-jul: - `errors.log.20260727.bz2` y `errors.log.20260728.bz2` - `php_errors.log.20260727.bz2` y `php_errors.log.20260728.bz2` Son principalmente logs de error/ModSecurity, no access logs completos. Contienen intentos RCE bloqueados por ModSecurity a través de IPs Cloudflare, pero **ninguna petición a `sys.php`** ni evidencia de la subida/creación de los backdoors. Siguen faltando access logs HTTP y logs FTP/SFTP/panel para atribución.
Author
Owner

Avance de atribución: vector de entrada confirmado por el log web

Los registros aportados por Rafa cierran una parte crítica de la cronología. La hora del error log está en CEST (UTC+2), por lo que 07:02 local equivale a 05:02 UTC, exactamente la ventana de mtime de los artefactos WordPress.

Secuencia observada (29-jul)

  1. 07:02:16 CEST — petición a una shell temporal /rr.php, con path=/usr/home/feadulta.com/web; ModSecurity detecta intento de subir wp-cl.php.
  2. 07:02:53 CEST — misma /rr.php, ahora con path=/usr/home/feadulta.com/web/wp-content/mu-plugins; ModSecurity detecta la subida de health-check.php.
  3. health-check.php tiene mtime 05:02:53 UTC y wp-highlits.php 05:03:37 UTC, coherentes con esos requests.

El WAF registró las subidas pero su score fue 5 frente a umbral de bloqueo 25: alertó, no bloqueó. Las IP remotas (162.158.122.21) son de Cloudflare, no atribuyen al atacante.

Estado de /rr.php y wp-cl.php

Ya no existen en el árbol /web: quien operó la shell temporal la eliminó después de usarla. Un escaneo de la ventana UTC 04:55–05:10 no encuentra otros archivos no-cache además de los dos artefactos WordPress ya aislados.

Conclusión actual

El actor utilizó /rr.php como web shell de subida para plantar los backdoors WordPress. La shell Joomla sys.php era un mecanismo plausible para crear/operar esa shell, pero el log disponible todavía no muestra la petición inicial a sys.php ni el momento de creación de rr.php; esa parte sigue pendiente de access logs completos/Cloudflare.

sys.php sigue en cuarentena y rr.php no está presente.

## Avance de atribución: vector de entrada confirmado por el log web Los registros aportados por Rafa cierran una parte crítica de la cronología. La hora del error log está en CEST (UTC+2), por lo que `07:02` local equivale a `05:02 UTC`, exactamente la ventana de mtime de los artefactos WordPress. ### Secuencia observada (29-jul) 1. `07:02:16 CEST` — petición a una shell temporal `/rr.php`, con `path=/usr/home/feadulta.com/web`; ModSecurity detecta intento de subir `wp-cl.php`. 2. `07:02:53 CEST` — misma `/rr.php`, ahora con `path=/usr/home/feadulta.com/web/wp-content/mu-plugins`; ModSecurity detecta la subida de `health-check.php`. 3. `health-check.php` tiene `mtime 05:02:53 UTC` y `wp-highlits.php` `05:03:37 UTC`, coherentes con esos requests. El WAF registró las subidas pero su score fue 5 frente a umbral de bloqueo 25: **alertó, no bloqueó**. Las IP remotas (`162.158.122.21`) son de Cloudflare, no atribuyen al atacante. ### Estado de `/rr.php` y `wp-cl.php` Ya no existen en el árbol `/web`: quien operó la shell temporal la eliminó después de usarla. Un escaneo de la ventana UTC `04:55–05:10` no encuentra otros archivos no-cache además de los dos artefactos WordPress ya aislados. ### Conclusión actual El actor utilizó `/rr.php` como web shell de subida para plantar los backdoors WordPress. La shell Joomla `sys.php` era un mecanismo plausible para crear/operar esa shell, pero el log disponible todavía no muestra la petición inicial a `sys.php` ni el momento de creación de `rr.php`; esa parte sigue pendiente de access logs completos/Cloudflare. `sys.php` sigue en cuarentena y `rr.php` no está presente.
Author
Owner

Ticket abierto con CDmon para logs forenses

Campo Valor
Ticket #cdmoncc274423
Estado Abierto
Departamento Customer Care
Canal Red
Titular Rafael Calvo (info@feadulta.com)
Respuesta vence 29 Jul 2026, 3:41 PM (hora indicada por CDmon)
Vence 01 Aug 2026, 11:41 AM (hora indicada por CDmon)

Solicitud enviada: access logs HTTP completos de feadulta.com y antiguo.feadulta.com, IP real tras Cloudflare cuando exista, y logs FTP/SFTP/SSH/cPanel/File Manager para atribuir la subida de rr.php, wp-cl.php y health-check.php. También se solicita explicación y bloqueo efectivo de subidas PHP detectadas por ModSecurity.

## Ticket abierto con CDmon para logs forenses | Campo | Valor | |---|---| | **Ticket** | `#cdmoncc274423` | | Estado | Abierto | | Departamento | Customer Care | | Canal | Red | | Titular | Rafael Calvo (`info@feadulta.com`) | | Respuesta vence | 29 Jul 2026, 3:41 PM *(hora indicada por CDmon)* | | Vence | 01 Aug 2026, 11:41 AM *(hora indicada por CDmon)* | Solicitud enviada: access logs HTTP completos de `feadulta.com` y `antiguo.feadulta.com`, IP real tras Cloudflare cuando exista, y logs FTP/SFTP/SSH/cPanel/File Manager para atribuir la subida de `rr.php`, `wp-cl.php` y `health-check.php`. También se solicita explicación y bloqueo efectivo de subidas PHP detectadas por ModSecurity.
Author
Owner

Trabajo paralelo mientras esperamos CDmon

  • Escaneo de Joomla legacy (sin escritura): no se detectó otra web shell independiente con patrones de ejecución directa en el árbol no-cache. Esto no equivale a una certificación de limpieza; el sys.php ya confirmado permanece en cuarentena.
  • Superficie de subida identificada para revisión/aislamiento: com_foxcontact (incluye uploader), mod_foxcontact/upr.php, K2 y librerías elFinder. Son componentes de subida presentes en una Joomla 3.10 EOL; no se atribuye aún la entrada a ninguno sin logs de la petición inicial.
  • Se confirmó que los directorios principales de Joomla son escribibles por el proceso de la cuenta, de modo que cualquier RCE/shell puede plantar archivos fuera de su componente.
  • Limpieza WordPress: se retiró también la referencia residual wp-highlits/wp-highlits.php de active_plugins, con snapshot previo en el expediente. El directorio ya estaba en cuarentena y no se vuelve a cargar.

Siguiente paso de contención pendiente de decisión: restringir temporalmente el administrador Joomla y endpoints de subida legacy de forma reversible, o retirar el subdominio legacy mientras se construye el mirror estático.

## Trabajo paralelo mientras esperamos CDmon - Escaneo de Joomla legacy (sin escritura): no se detectó otra web shell independiente con patrones de ejecución directa en el árbol no-cache. Esto no equivale a una certificación de limpieza; el `sys.php` ya confirmado permanece en cuarentena. - Superficie de subida identificada para revisión/aislamiento: `com_foxcontact` (incluye uploader), `mod_foxcontact/upr.php`, K2 y librerías elFinder. Son componentes de subida presentes en una Joomla 3.10 EOL; no se atribuye aún la entrada a ninguno sin logs de la petición inicial. - Se confirmó que los directorios principales de Joomla son escribibles por el proceso de la cuenta, de modo que cualquier RCE/shell puede plantar archivos fuera de su componente. - Limpieza WordPress: se retiró también la referencia residual `wp-highlits/wp-highlits.php` de `active_plugins`, con snapshot previo en el expediente. El directorio ya estaba en cuarentena y no se vuelve a cargar. Siguiente paso de contención pendiente de decisión: restringir temporalmente el administrador Joomla y endpoints de subida legacy de forma reversible, o retirar el subdominio legacy mientras se construye el mirror estático.
Author
Owner

Hardening temporal Joomla aplicado y verificado

Aplicado bajo autorización de Rafa, con backup reversible del .htaccess raíz:

  1. Administrador Joomla bloqueado mediante /web/antiguo/administrator/.htaccess.
  2. Fox Contact bloqueado por HTTP:
    • rutas directas bajo components/com_foxcontact/;
    • modules/mod_foxcontact/upr.php;
    • rutas Joomla con option=com_foxcontact.
  3. El frontend histórico no se ha bloqueado.

Verificación desde el origen:

/administrator/                                     → 403
/components/com_foxcontact/controller/uploader.php  → 403
/modules/mod_foxcontact/upr.php                     → 403
/                                                   → 301 (sigue accesible)

Backup remoto: /web/antiguo/.htaccess.pre-incident-183-20260729T110202Z.

Este cierre es intencionadamente reversible. La función de contacto/upload de Fox Contact queda deshabilitada durante el incidente; el contenido histórico, enlaces e imágenes no se han tocado.

## Hardening temporal Joomla aplicado y verificado Aplicado bajo autorización de Rafa, con backup reversible del `.htaccess` raíz: 1. **Administrador Joomla bloqueado** mediante `/web/antiguo/administrator/.htaccess`. 2. **Fox Contact bloqueado por HTTP**: - rutas directas bajo `components/com_foxcontact/`; - `modules/mod_foxcontact/upr.php`; - rutas Joomla con `option=com_foxcontact`. 3. El frontend histórico no se ha bloqueado. Verificación desde el origen: ```text /administrator/ → 403 /components/com_foxcontact/controller/uploader.php → 403 /modules/mod_foxcontact/upr.php → 403 / → 301 (sigue accesible) ``` Backup remoto: `/web/antiguo/.htaccess.pre-incident-183-20260729T110202Z`. Este cierre es intencionadamente reversible. La función de contacto/upload de Fox Contact queda deshabilitada durante el incidente; el contenido histórico, enlaces e imágenes no se han tocado.
Author
Owner

Decisión de remediación estructural — 29-jul-2026

Rafa confirma que el riesgo estructural es mantener Joomla EOL ejecutando PHP dentro de la misma cuenta de hosting. La contención actual reduce la exposición, pero no sustituye la retirada definitiva de esa superficie.

Decisión: iniciar hoy la preparación de un mirror HTML estático y de solo lectura de antiguo.feadulta.com en Hetzner. Se preservará el archivo histórico público; no se eliminará contenido ni se apagará la web legacy hasta validar paridad pública y acordar el cutover.

Orden de trabajo:

  1. inventario/crawl reproducible de las rutas públicas y activos necesarios;
  2. exportación y despliegue inicial en Hetzner, aislado del hosting comprometido;
  3. verificación de paridad (URLs, contenido, imágenes, enlaces y redirecciones);
  4. cambio de tráfico y retirada final de Joomla/PHP de la exposición pública.

La investigación de atribución y la rotación de secretos continúan en paralelo. El ticket CDmon #cdmoncc274423 sigue pendiente de logs forenses.

## Decisión de remediación estructural — 29-jul-2026 Rafa confirma que el riesgo estructural es mantener Joomla EOL ejecutando PHP dentro de la misma cuenta de hosting. La contención actual reduce la exposición, pero no sustituye la retirada definitiva de esa superficie. **Decisión:** iniciar hoy la preparación de un mirror HTML estático y de solo lectura de `antiguo.feadulta.com` en Hetzner. Se preservará el archivo histórico público; no se eliminará contenido ni se apagará la web legacy hasta validar paridad pública y acordar el cutover. Orden de trabajo: 1. inventario/crawl reproducible de las rutas públicas y activos necesarios; 2. exportación y despliegue inicial en Hetzner, aislado del hosting comprometido; 3. verificación de paridad (URLs, contenido, imágenes, enlaces y redirecciones); 4. cambio de tráfico y retirada final de Joomla/PHP de la exposición pública. La investigación de atribución y la rotación de secretos continúan en paralelo. El ticket CDmon `#cdmoncc274423` sigue pendiente de logs forenses.
Author
Owner

Plan operativo del mirror estático publicado en #180

En respuesta a la decisión de remediación de este issue (comment-455), el plan por fases para el
mirror HTML read-only de antiguo.feadulta.com está publicado en #180: #180 (comment)

Resumen para este hilo (lo que afecta al incidente):

  • El mirror se genera SOLO por HTTP (wget contra el origen), nunca copiando el filesystem.
    Por construcción es imposible arrastrar sys.php, rr.php ni ningún .php ejecutable a
    Hetzner: se captura lo que Joomla renderiza, no lo que hay en disco.
  • Se añade un escaneo de seguridad del propio mirror antes de publicar nada (PHP embebido,
    hosts externos en <script>, patrones de inyección, páginas de challenge congeladas), con regla
    de parada explícita. Capturar por HTTP evita el código del atacante, no su output — este es
    el punto que suele olvidarse.
  • El archivo forense frío (tar de /web/antiguo + dump) se preserva pero NO viaja a
    Hetzner
    : se queda en almacenamiento local etiquetado como cuarentena.
  • Nada de esto toca antiguo.feadulta.com, que sigue sirviendo Joomla durante toda la
    validación. Sin DNS, sin cutover, sin retirada. La contención de comment-452 se mantiene.
  • La retirada de Joomla/PHP es el último paso, nunca simultánea al cambio de tráfico, y solo
    tras 7-14 días estables.

Dependencia crítica que cruza los dos issues: el mirror se construye crawleando el origen, así
que si el hosting de CDMON se apaga el 07/08 perdemos la fuente. La continuidad del hosting
deja de ser solo una cuestión de rollback de la migración y pasa a ser prerrequisito de la
remediación de seguridad.

## Plan operativo del mirror estático publicado en #180 En respuesta a la decisión de remediación de este issue (comment-455), el plan por fases para el mirror HTML read-only de `antiguo.feadulta.com` está publicado en #180: https://gitea.feadulta.com/rafa/feadulta/issues/180#issuecomment-459 Resumen para este hilo (lo que afecta al incidente): - **El mirror se genera SOLO por HTTP** (`wget` contra el origen), nunca copiando el filesystem. Por construcción es imposible arrastrar `sys.php`, `rr.php` ni ningún `.php` ejecutable a Hetzner: se captura lo que Joomla renderiza, no lo que hay en disco. - **Se añade un escaneo de seguridad del propio mirror** antes de publicar nada (PHP embebido, hosts externos en `<script>`, patrones de inyección, páginas de challenge congeladas), con regla de parada explícita. Capturar por HTTP evita el *código* del atacante, no su *output* — este es el punto que suele olvidarse. - **El archivo forense frío** (tar de `/web/antiguo` + dump) se preserva pero **NO viaja a Hetzner**: se queda en almacenamiento local etiquetado como cuarentena. - **Nada de esto toca `antiguo.feadulta.com`**, que sigue sirviendo Joomla durante toda la validación. Sin DNS, sin cutover, sin retirada. La contención de comment-452 se mantiene. - **La retirada de Joomla/PHP es el último paso**, nunca simultánea al cambio de tráfico, y solo tras 7-14 días estables. **Dependencia crítica que cruza los dos issues:** el mirror se construye crawleando el origen, así que **si el hosting de CDMON se apaga el 07/08 perdemos la fuente**. La continuidad del hosting deja de ser solo una cuestión de rollback de la migración y pasa a ser prerrequisito de la remediación de seguridad.
Author
Owner

Hallazgo forense: sys.php YA estaba en la copia local — la shell precede a la intrusión del 29-jul

Salió al evaluar si podíamos usar la copia local de Joomla como fuente del mirror (detalle completo
en #180: #180 (comment)).

Hecho verificado

La copia local de Joomla (contenedor joomla-web, construida desde un backup) contiene
modules/mod_menu/sys.php con el mismo SHA-256 exacto que la evidencia preservada de
producción en comment-445:

local:      8152c22e26a7135b81d47d1de1c8454d1f4ed566193eaf2038af39152e33092e
producción: 8152c22e26a7135b81d47d1de1c8454d1f4ed566193eaf2038af39152e33092e

Datación de esa copia local: configuration.php de 1-mar-2026, contenido hasta
enero/marzo 2026.

Conclusión

La web shell ya estaba en el sitio meses antes de la intrusión del 29-jul — coherente con su
mtime de 2020-10-25, que hasta ahora no se podía distinguir de una fecha falsificada. El actor de
esta semana no la plantó: la encontró ya puesta. Esto refuerza la hipótesis de comment-446 de
que sys.php fue el mecanismo plausible para crear /rr.php.

Implicación que hay que verificar ya

En N:\Backup\Joomla_db están los backups del incidente de malware de mayo-2026 (34
detecciones, "Fase E pendiente"), incluido feadulta-clean-20260525 — el posterior a aquella
limpieza.

Si sys.php aparece en feadulta-clean-20260525, la limpieza de mayo fue incompleta y esto no
son dos incidentes, sino uno solo mal cerrado.
Explicaría la re-infección sin necesidad de un
vector de entrada nuevo.

Esto no depende de CDmon

La atribución está bloqueada esperando los access logs del proveedor. Esta línea no. Se puede
datar la aparición del shell bisecando los backups que ya tenemos, offline y sin tocar producción:

Backup Fecha Formato
feadulta-20260111-pre-incidente 2026-01-11 Akeeba .jpa
feadulta-20260306-akeeba 2026-03-06 Akeeba .jpa
feadulta-20260525-INFECTED 2026-05-25 .tar.gz
feadulta-clean-20260525 2026-05-25 .tar.gz (post-limpieza)
  • Buscar mod_menu/sys.php en los cuatro. Prioridad: feadulta-clean-20260525.
  • Si aparece en el "clean": reabrir el alcance de la limpieza de mayo y revisar qué más
    sobrevivió a aquella pasada.

Pendiente de autorización de Rafa para lanzar la bisección (es solo lectura sobre backups locales).

## Hallazgo forense: `sys.php` YA estaba en la copia local — la shell precede a la intrusión del 29-jul Salió al evaluar si podíamos usar la copia local de Joomla como fuente del mirror (detalle completo en #180: https://gitea.feadulta.com/rafa/feadulta/issues/180#issuecomment-465). ### Hecho verificado La copia local de Joomla (contenedor `joomla-web`, construida desde un backup) contiene `modules/mod_menu/sys.php` con el **mismo SHA-256 exacto** que la evidencia preservada de producción en comment-445: ```text local: 8152c22e26a7135b81d47d1de1c8454d1f4ed566193eaf2038af39152e33092e producción: 8152c22e26a7135b81d47d1de1c8454d1f4ed566193eaf2038af39152e33092e ``` Datación de esa copia local: `configuration.php` de **1-mar-2026**, contenido hasta **enero/marzo 2026**. ### Conclusión **La web shell ya estaba en el sitio meses antes de la intrusión del 29-jul** — coherente con su `mtime` de 2020-10-25, que hasta ahora no se podía distinguir de una fecha falsificada. El actor de esta semana **no la plantó: la encontró ya puesta**. Esto refuerza la hipótesis de comment-446 de que `sys.php` fue el mecanismo plausible para crear `/rr.php`. ### Implicación que hay que verificar ya En `N:\Backup\Joomla_db` están los backups del **incidente de malware de mayo-2026** (34 detecciones, "Fase E pendiente"), incluido `feadulta-clean-20260525` — el **posterior** a aquella limpieza. > **Si `sys.php` aparece en `feadulta-clean-20260525`, la limpieza de mayo fue incompleta y esto no > son dos incidentes, sino uno solo mal cerrado.** Explicaría la re-infección sin necesidad de un > vector de entrada nuevo. ### Esto no depende de CDmon La atribución está bloqueada esperando los access logs del proveedor. **Esta línea no.** Se puede datar la aparición del shell bisecando los backups que ya tenemos, offline y sin tocar producción: | Backup | Fecha | Formato | |---|---|---| | `feadulta-20260111-pre-incidente` | 2026-01-11 | Akeeba `.jpa` | | `feadulta-20260306-akeeba` | 2026-03-06 | Akeeba `.jpa` | | `feadulta-20260525-INFECTED` | 2026-05-25 | `.tar.gz` | | `feadulta-clean-20260525` | 2026-05-25 | `.tar.gz` (post-limpieza) | - [ ] Buscar `mod_menu/sys.php` en los cuatro. Prioridad: **`feadulta-clean-20260525`**. - [ ] Si aparece en el "clean": reabrir el alcance de la limpieza de mayo y revisar qué más sobrevivió a aquella pasada. Pendiente de autorización de Rafa para lanzar la bisección (es solo lectura sobre backups locales).
Author
Owner

Posible exposición de datos: backups completos (sitio + BD) dentro del árbol web

Detectado al inventariar /web/antiguo para el mirror (detalle en #180: #180 (comment)).

Hecho

/web/antiguo/administrator/components/com_akeeba/backup/
    site-www.feadulta.com-20260111-*.jpa + .j01   2,9 GB
    site-www.feadulta.com-20260306-*.jpa + .j01   2,9 GB
    site-www.feadulta.com-20260526-*.jpa + .j01   2,9 GB

8,6 GB de backups completos del sitio — incluido el dump de fejoomla3 — dentro del directorio
servido por web.
Única protección: un .htaccess de 821 bytes en esa carpeta.

Por qué es relevante para este incidente

  • El actor tuvo escritura arbitraria de ficheros: sys.php incluye subida vía POST
    (comment-445) y rr.php se usó para plantar los backdoors (comment-446). Con capacidad de
    escritura, ese .htaccess se sobrescribe o se borra y los .jpa quedan descargables por HTTP.
  • Los archivos contienen la BD Joomla completa: 1.182 usuarios con hashes de contraseña, emails
    y datos de la etapa Joomla.
  • sys.php lleva en el sitio desde 2020 (comment-468) — la ventana de oportunidad no son días,
    son años.

No hay evidencia de que ocurriera. Pero tampoco hay forma de descartarlo sin los access logs,
que siguen pendientes en #cdmoncc274423.

Acciones propuestas

  • Ampliar la petición a CDmon: incluir explícitamente peticiones a *.jpa / *.j01 bajo
    /administrator/components/com_akeeba/backup/ en los access logs solicitados.
  • Retirar los .jpa/.j01 del árbol web. Los tres están ya duplicados en almacenamiento
    local (N:\Backup\Joomla_db), así que no se pierde nada. Reducción de superficie inmediata
    y sin coste. (Es escritura en producción — pendiente de autorización de Rafa.)
  • Si los logs confirman descargas, esto escala a notificación de brecha de datos por los
    hashes de contraseña de los 1.182 usuarios.

Nota relacionada

El directorio de Akeeba solo tiene 3 backups completos (11-ene, 6-mar, 26-may) separados ~2
meses. No hay backup automático diario ni semanal operativo en el legacy, en contra de lo que
se asumía.

## Posible exposición de datos: backups completos (sitio + BD) dentro del árbol web Detectado al inventariar `/web/antiguo` para el mirror (detalle en #180: https://gitea.feadulta.com/rafa/feadulta/issues/180#issuecomment-477). ### Hecho ```text /web/antiguo/administrator/components/com_akeeba/backup/ site-www.feadulta.com-20260111-*.jpa + .j01 2,9 GB site-www.feadulta.com-20260306-*.jpa + .j01 2,9 GB site-www.feadulta.com-20260526-*.jpa + .j01 2,9 GB ``` **8,6 GB de backups completos del sitio — incluido el dump de `fejoomla3` — dentro del directorio servido por web.** Única protección: un `.htaccess` de 821 bytes en esa carpeta. ### Por qué es relevante para este incidente - El actor tuvo **escritura arbitraria de ficheros**: `sys.php` incluye subida vía POST (comment-445) y `rr.php` se usó para plantar los backdoors (comment-446). Con capacidad de escritura, ese `.htaccess` se sobrescribe o se borra y los `.jpa` quedan descargables por HTTP. - Los archivos contienen la BD Joomla completa: **1.182 usuarios** con hashes de contraseña, emails y datos de la etapa Joomla. - `sys.php` lleva en el sitio desde 2020 (comment-468) — la ventana de oportunidad no son días, son años. **No hay evidencia de que ocurriera.** Pero tampoco hay forma de descartarlo sin los access logs, que siguen pendientes en `#cdmoncc274423`. ### Acciones propuestas - [ ] **Ampliar la petición a CDmon**: incluir explícitamente peticiones a `*.jpa` / `*.j01` bajo `/administrator/components/com_akeeba/backup/` en los access logs solicitados. - [ ] **Retirar los `.jpa`/`.j01` del árbol web.** Los tres están ya duplicados en almacenamiento local (`N:\Backup\Joomla_db`), así que no se pierde nada. Reducción de superficie inmediata y sin coste. *(Es escritura en producción — pendiente de autorización de Rafa.)* - [ ] Si los logs confirman descargas, esto escala a **notificación de brecha de datos** por los hashes de contraseña de los 1.182 usuarios. ### Nota relacionada El directorio de Akeeba solo tiene **3 backups completos** (11-ene, 6-mar, 26-may) separados ~2 meses. **No hay backup automático diario ni semanal operativo** en el legacy, en contra de lo que se asumía.
Author
Owner

Recalibrado: los 1.182 usuarios no son cuentas reales — baja la severidad de comment-479

Aporte de Rafa: los 1.182 usuarios no son personas que entren al sitio. Existen como registros
de autoría (firman artículos migrados de la etapa Joomla), pero nadie hace login salvo los
webmasters e Inma
.

Lo que se cae de comment-479

  • Descartada la vía de notificación de brecha por los hashes de contraseña de los 1.182. Si
    esas cuentas nunca se usaron para entrar, sus hashes son en la práctica inertes: no hay
    contraseñas reales que reutilizar en otros servicios, que es el daño típico de una fuga de
    hashes.
  • La severidad pasa de posible brecha de datos a higiene y reducción de superficie.

Lo que sigue en pie (y ahora es lo que de verdad importa)

El problema no era el volumen de cuentas, sino qué más hay en esos mismos .jpa:

  1. Las cuentas que sí son reales están en el mismo dump. Los webmasters, Inma, calvo (396) y
    las legacy andrey (1087) / pabloarias (1047) / josek (1049). Son exactamente las
    privilegiadas
    , y sus hashes viajan en esos archivos. Un puñado de admins pesa más que 1.182
    registros inertes.
  2. configuration.php va dentro del backup: credenciales de la BD y cualquier secreto de
    configuración del Joomla (SMTP, claves de componentes). Para un atacante suele valer más que
    una tabla de usuarios.
  3. Emails de colaboradores reales. Aunque no entren al sitio, los registros de autoría llevan
    direcciones de personas reales. Es dato personal, de menor gravedad que un hash pero no cero.

Efecto sobre las acciones propuestas

Acción de comment-479 Estado
Notificación de brecha por los 1.182 descartada
Sacar los .jpa del árbol web se mantiene — ahora por los admins, la config y la BD
Pedir a CDmon logs de *.jpa/*.j01 se mantiene, sin urgencia de brecha
Rotación de credenciales ⚠️ replantear: el foco es la credencial de BD de configuration.php y los admins reales, no las 1.182 cuentas

Sin prisa, pero la retirada de los archivos del directorio público sigue siendo la acción de mejor
relación coste/beneficio: están duplicados en N: y no se pierde nada.

## Recalibrado: los 1.182 usuarios no son cuentas reales — baja la severidad de comment-479 Aporte de Rafa: **los 1.182 usuarios no son personas que entren al sitio.** Existen como registros de autoría (firman artículos migrados de la etapa Joomla), pero **nadie hace login salvo los webmasters e Inma**. ### Lo que se cae de comment-479 - **Descartada la vía de notificación de brecha** por los hashes de contraseña de los 1.182. Si esas cuentas nunca se usaron para entrar, sus hashes son en la práctica inertes: no hay contraseñas reales que reutilizar en otros servicios, que es el daño típico de una fuga de hashes. - La severidad pasa de *posible brecha de datos* a **higiene y reducción de superficie**. ### Lo que sigue en pie (y ahora es lo que de verdad importa) El problema no era el volumen de cuentas, sino **qué más hay en esos mismos `.jpa`**: 1. **Las cuentas que sí son reales están en el mismo dump.** Los webmasters, Inma, `calvo` (396) y las legacy `andrey` (1087) / `pabloarias` (1047) / `josek` (1049). Son **exactamente las privilegiadas**, y sus hashes viajan en esos archivos. Un puñado de admins pesa más que 1.182 registros inertes. 2. **`configuration.php` va dentro del backup**: credenciales de la BD y cualquier secreto de configuración del Joomla (SMTP, claves de componentes). Para un atacante suele valer más que una tabla de usuarios. 3. **Emails de colaboradores reales.** Aunque no entren al sitio, los registros de autoría llevan direcciones de personas reales. Es dato personal, de menor gravedad que un hash pero no cero. ### Efecto sobre las acciones propuestas | Acción de comment-479 | Estado | |---|---| | Notificación de brecha por los 1.182 | ❌ **descartada** | | Sacar los `.jpa` del árbol web | ✅ **se mantiene** — ahora por los admins, la config y la BD | | Pedir a CDmon logs de `*.jpa`/`*.j01` | ✅ se mantiene, sin urgencia de brecha | | Rotación de credenciales | ⚠️ **replantear**: el foco es la **credencial de BD de `configuration.php`** y los admins reales, no las 1.182 cuentas | Sin prisa, pero la retirada de los archivos del directorio público sigue siendo la acción de mejor relación coste/beneficio: están duplicados en `N:` y no se pierde nada.
Author
Owner

CDmon: access log recibido — cronología ampliada y IP origen reportada

Evidencia original preservada localmente:

logs/incident-183/evidence/cdmon-access-log-extract-20260729.txt
SHA-256: 5c150abe3df5010e6c30ffc3afc80f17842998154af69eaadb3e18051c65d6f4

Secuencia confirmada (29-jul-2026, CEST / UTC+2)

Los registros de CDmon aportan como IP de origen reportada tras Cloudflare 130.195.250.105. La IP 162.158.122.21 es el nodo Cloudflare que conectó con el origen. La IP, user-agent y cabeceras son indicadores técnicos, no una identificación personal concluyente.

Hora CEST Acción Resultado
07:02:16 POST a rr.php con destino /web 200
07:02:19 GET a wp-cl.php 200, 67.634 bytes
07:02:25 POST a wp-cl.php 302
07:02:32–37 navegación de rr.php por /web, wp-content y mu-plugins 200
07:02:53 POST a rr.php para mu-plugins 200; coincide con health-check.php
07:04:29 GET final a rr.php 200

Esto confirma actividad interactiva desde la IP de origen reportada: el actor no solo subió archivos por rr.php, sino que accedió y ejecutó wp-cl.php antes de navegar y plantar el MU-plugin. El POST 302 de wp-cl.php no permite por sí solo determinar su payload o finalidad exacta, porque no disponemos del cuerpo de la petición.

La línea posterior de cic.es no pertenece a feadulta.com ni a esta ventana y se excluye de la cronología.

Gaps que siguen abiertos

  • La primera línea recibida ya encuentra rr.php operativo: sigue faltando la petición que creó/subió esa shell y cualquier acceso inicial a sys.php.
  • Siguen pendientes los logs de FTP/SFTP/SSH/panel solicitados a CDmon.
  • Conviene pedir un extracto de access logs anterior a las 07:02 CEST, buscando rr.php, sys.php y cualquier subida/escritura asociada, para acotar la implantación inicial.
## CDmon: access log recibido — cronología ampliada y IP origen reportada Evidencia original preservada localmente: ```text logs/incident-183/evidence/cdmon-access-log-extract-20260729.txt SHA-256: 5c150abe3df5010e6c30ffc3afc80f17842998154af69eaadb3e18051c65d6f4 ``` ### Secuencia confirmada (29-jul-2026, CEST / UTC+2) Los registros de CDmon aportan como IP de origen reportada tras Cloudflare **`130.195.250.105`**. La IP `162.158.122.21` es el nodo Cloudflare que conectó con el origen. La IP, user-agent y cabeceras son indicadores técnicos, no una identificación personal concluyente. | Hora CEST | Acción | Resultado | |---|---|---| | 07:02:16 | POST a `rr.php` con destino `/web` | 200 | | 07:02:19 | GET a `wp-cl.php` | 200, 67.634 bytes | | 07:02:25 | POST a `wp-cl.php` | 302 | | 07:02:32–37 | navegación de `rr.php` por `/web`, `wp-content` y `mu-plugins` | 200 | | 07:02:53 | POST a `rr.php` para `mu-plugins` | 200; coincide con `health-check.php` | | 07:04:29 | GET final a `rr.php` | 200 | Esto confirma actividad interactiva desde la IP de origen reportada: el actor no solo subió archivos por `rr.php`, sino que accedió y ejecutó `wp-cl.php` antes de navegar y plantar el MU-plugin. El `POST` 302 de `wp-cl.php` no permite por sí solo determinar su payload o finalidad exacta, porque no disponemos del cuerpo de la petición. La línea posterior de `cic.es` no pertenece a `feadulta.com` ni a esta ventana y se excluye de la cronología. ### Gaps que siguen abiertos - La primera línea recibida ya encuentra `rr.php` operativo: sigue faltando la petición que creó/subió esa shell y cualquier acceso inicial a `sys.php`. - Siguen pendientes los logs de FTP/SFTP/SSH/panel solicitados a CDmon. - Conviene pedir un extracto de access logs anterior a las 07:02 CEST, buscando `rr.php`, `sys.php` y cualquier subida/escritura asociada, para acotar la implantación inicial.
Author
Owner

Ventana ampliada de access log: 07:02:00–07:02:59 CEST

Se preservó el fichero íntegro aportado por Rafa, separado de las líneas de ruido de usuarios/bots:

logs/incident-183/evidence/cdmon-access-window-20260729-070200CEST.txt
SHA-256: 57a000c56a6a53e1b9482fb5e4b58af257dd9aabe79e4ff2170c510a7b8d22c9

Hallazgos nuevos

  1. rr.php ya estaba operativa antes de la primera línea de ataque recibida. A las 07:02:07 ya se navega por /web; además su Referer apunta a rr.php?path=.../wp-content, request previa que no aparece en esta ventana. Por tanto, este extracto no contiene la implantación inicial de rr.php.

  2. Cronología operativa consolidada desde 130.195.250.105:

    • 07:02:07–08: navegación de la shell por /web y acción 75706c6f6164=opet.
    • 07:02:16: POST a rr.php sobre /web (correlacionado con la subida de wp-cl.php).
    • 07:02:19: wp-cl.php responde 200 y 67.634 bytes.
    • 07:02:25: POST a wp-cl.php devuelve 302.
    • 07:02:26–29: se solicita /wp-admin/, que redirige a wp-login.php?reauth=1; se carga la página de login y sus assets.
    • 07:02:32–53: nueva navegación de rr.php hasta wp-content/mu-plugins y POST final, correlacionado con health-check.php.
    • 07:02:55: nueva carga de wp-login.php?reauth=1.
  3. No hay evidencia de login WordPress estándar exitoso en esta ventana. Tras el POST a wp-cl.php, la sesión llega al formulario de wp-login con reauth=1; el extracto no contiene un POST posterior a wp-login.php. Esto no descarta abuso mediante código/backdoor, pero descarta afirmar que el actor obtuvo acceso por un login WordPress convencional en estos 59 segundos.

Implicación

La hipótesis se afina: el actor llegó con rr.php ya desplegada, creó/operó wp-cl.php, comprobó o intentó el acceso administrativo y luego plantó el MU-plugin. Sigue faltando el evento anterior que creó rr.php y cualquier interacción inicial con la shell Joomla sys.php; hay que pedir a CDmon un intervalo anterior, no solo alrededor de 07:02.

## Ventana ampliada de access log: 07:02:00–07:02:59 CEST Se preservó el fichero íntegro aportado por Rafa, separado de las líneas de ruido de usuarios/bots: ```text logs/incident-183/evidence/cdmon-access-window-20260729-070200CEST.txt SHA-256: 57a000c56a6a53e1b9482fb5e4b58af257dd9aabe79e4ff2170c510a7b8d22c9 ``` ### Hallazgos nuevos 1. **`rr.php` ya estaba operativa antes de la primera línea de ataque recibida.** A las `07:02:07` ya se navega por `/web`; además su `Referer` apunta a `rr.php?path=.../wp-content`, request previa que no aparece en esta ventana. Por tanto, este extracto no contiene la implantación inicial de `rr.php`. 2. **Cronología operativa consolidada desde `130.195.250.105`:** - `07:02:07–08`: navegación de la shell por `/web` y acción `75706c6f6164=opet`. - `07:02:16`: POST a `rr.php` sobre `/web` (correlacionado con la subida de `wp-cl.php`). - `07:02:19`: `wp-cl.php` responde `200` y 67.634 bytes. - `07:02:25`: POST a `wp-cl.php` devuelve `302`. - `07:02:26–29`: se solicita `/wp-admin/`, que redirige a `wp-login.php?reauth=1`; se carga la página de login y sus assets. - `07:02:32–53`: nueva navegación de `rr.php` hasta `wp-content/mu-plugins` y POST final, correlacionado con `health-check.php`. - `07:02:55`: nueva carga de `wp-login.php?reauth=1`. 3. **No hay evidencia de login WordPress estándar exitoso en esta ventana.** Tras el POST a `wp-cl.php`, la sesión llega al formulario de `wp-login` con `reauth=1`; el extracto no contiene un POST posterior a `wp-login.php`. Esto no descarta abuso mediante código/backdoor, pero descarta afirmar que el actor obtuvo acceso por un login WordPress convencional en estos 59 segundos. ### Implicación La hipótesis se afina: el actor llegó con `rr.php` ya desplegada, creó/operó `wp-cl.php`, comprobó o intentó el acceso administrativo y luego plantó el MU-plugin. Sigue faltando el evento anterior que creó `rr.php` y cualquier interacción inicial con la shell Joomla `sys.php`; hay que pedir a CDmon un intervalo anterior, no solo alrededor de `07:02`.
Author
Owner

CDmon: coincidencias wp-cl.php del 27-jul — interpretación cauta

Evidencia del correo preservada:

logs/incident-183/evidence/cdmon-email-extract-20260727-wp-cl.txt
SHA-256: 049e58650c612411868c920eb4120840281d73df83cfeb40eedd556e55c9dd1b

CDmon informa de varios GET desde la IP de origen reportada 149.102.236.228 entre 12:07:55 y 12:09:16 CEST del 27-jul:

  • GET /wp-cl.php301;
  • seguimiento a GET /es/wp-cl.php404 y carga de la página de error Joomla;
  • no hay POST, 200 del fichero, subida ni ejecución de PHP en esas líneas.

Conclusión

Esto prueba que esa IP sondeó la ruta wp-cl.php dos días antes. No prueba que wp-cl.php existiera el 27-jul: la respuesta 301 y el posterior 404 en la ruta Joomla son compatibles con el comportamiento de redirección/routing para una ruta inexistente. Es distinto de la evidencia del 29-jul, donde wp-cl.php respondió 200 con 67.634 bytes y fue objeto de POST desde otra IP (130.195.250.105).

Por ahora se tratan como dos actividades potencialmente relacionadas por el nombre de ruta, pero no se atribuyen al mismo actor ni se adelanta la fecha de creación del fichero sin una respuesta 200, hash, mtime o request de escritura.

CDmon indica que adjunta el log completo; ese adjunto no llegó al expediente de chat. Al recibirlo hay que preservarlo, hashearlo y buscar de forma reproducible rr.php, sys.php, wp-cl.php, health-check.php, wp-highlits.php y las IPs 130.195.250.105 / 149.102.236.228 en el intervalo disponible.

## CDmon: coincidencias `wp-cl.php` del 27-jul — interpretación cauta Evidencia del correo preservada: ```text logs/incident-183/evidence/cdmon-email-extract-20260727-wp-cl.txt SHA-256: 049e58650c612411868c920eb4120840281d73df83cfeb40eedd556e55c9dd1b ``` CDmon informa de varios GET desde la IP de origen reportada `149.102.236.228` entre `12:07:55` y `12:09:16 CEST` del 27-jul: - `GET /wp-cl.php` → **301**; - seguimiento a `GET /es/wp-cl.php` → **404** y carga de la página de error Joomla; - no hay POST, 200 del fichero, subida ni ejecución de PHP en esas líneas. ### Conclusión Esto prueba que esa IP **sondeó** la ruta `wp-cl.php` dos días antes. **No prueba que `wp-cl.php` existiera el 27-jul**: la respuesta 301 y el posterior 404 en la ruta Joomla son compatibles con el comportamiento de redirección/routing para una ruta inexistente. Es distinto de la evidencia del 29-jul, donde `wp-cl.php` respondió 200 con 67.634 bytes y fue objeto de POST desde otra IP (`130.195.250.105`). Por ahora se tratan como dos actividades potencialmente relacionadas por el nombre de ruta, pero no se atribuyen al mismo actor ni se adelanta la fecha de creación del fichero sin una respuesta 200, hash, mtime o request de escritura. CDmon indica que adjunta el log completo; ese adjunto no llegó al expediente de chat. Al recibirlo hay que preservarlo, hashearlo y buscar de forma reproducible `rr.php`, `sys.php`, `wp-cl.php`, `health-check.php`, `wp-highlits.php` y las IPs `130.195.250.105` / `149.102.236.228` en el intervalo disponible.
Author
Owner

LOG COMPLETO 29-jul recibido — compromiso WordPress administrativo confirmado

Preservación

logs/incident-183/evidence/cdmon-accesslog-20260729-full-received.txt
Tamaño: 63.061 bytes
SHA-256: 24dae1ed3f1a3c99670fc63c3fdb0615c15f5c890088f37225fd22bcc453fc9a

Este log corrige y amplía los comentarios previos, que solo cubrían hasta 07:02:59.

Cronología confirmada (CEST / UTC+2)

Hora Acción desde IP de origen reportada 130.195.250.105 Evidencia
06:58:36–47 Reconocimiento de la web y license.txt GET 200/301/404
06:59:10 rr.php ya está operativa GET 200, 4.676 bytes
07:00:17 Navegación de la shell por /web/wp-content GET 200
07:02:16 Operación POST sobre rr.php en /web 200; correlacionada con wp-cl.php
07:02:19–25 wp-cl.php se sirve (67.634 bytes) y recibe POST 200 / 302
07:02:53 POST a rr.php bajo wp-content/mu-plugins 200; correlacionado con health-check.php
07:03:00 POST a wp-login.php 500
07:03:03–24 Sesión ya autenticada: home carga assets de admin bar; /wp-admin/ y users/me?context=edit responden 200 acceso WordPress autenticado confirmado
07:03:28–41 Abre instalador, sube plugin y activa wp-highlits/wp-highlits.php upload-plugin POST, activación con nonce
07:03:47–04:12 Accede a Wordfence y hace dos POST a wp-admin/options.php ambos redirigen a settings-updated=true
07:04:35 Última acción observada de rr.php POST 200

Conclusiones corregidas

  1. La ventana corta no incluía el POST de login. El log completo muestra que a las 07:03:00 hubo POST a wp-login.php con 500 y, segundos después, una sesión WordPress autenticada. La evidencia posterior (/wp-admin/ 200, REST users/me?context=edit 200, instalación/activación de plugin) confirma acceso administrativo efectivo.
  2. El actor instaló y activó explícitamente wp-highlits; no fue solo un fichero abandonado en disco.
  3. El actor modificó la configuración de Wordfence dos veces. Los cuerpos POST no están en access log: no sabemos aún qué settings cambió, así que la configuración de Wordfence debe considerarse no fiable hasta auditoría/reinstalación controlada.
  4. rr.php ya existía a las 06:59:10. Este archivo no contiene su request de creación. Tampoco contiene una request del atacante a la ruta de sys.php; la hipótesis de Joomla como vector sigue siendo plausible por el artefacto hallado, pero no está probada por este access log.

Acciones que pasan a ser obligatorias antes de declarar el WordPress limpio

  • rotar credenciales de todos los administradores WP, revocar sesiones y Application Passwords, y rotar salts;
  • revisar/reinstalar Wordfence desde fuente limpia y comparar/reconstruir su configuración; buscar whitelists o referencias a 130.195.250.105/149.102.236.228;
  • revisar las cuentas de administrador existentes y los logs/configuración de Wordfence si quedan disponibles;
  • mantener la migración de Joomla a mirror estático como mitigación estructural, pero no atribuirle sin reservas la implantación inicial de rr.php.
## LOG COMPLETO 29-jul recibido — compromiso WordPress administrativo confirmado ### Preservación ```text logs/incident-183/evidence/cdmon-accesslog-20260729-full-received.txt Tamaño: 63.061 bytes SHA-256: 24dae1ed3f1a3c99670fc63c3fdb0615c15f5c890088f37225fd22bcc453fc9a ``` Este log corrige y amplía los comentarios previos, que solo cubrían hasta `07:02:59`. ### Cronología confirmada (CEST / UTC+2) | Hora | Acción desde IP de origen reportada `130.195.250.105` | Evidencia | |---|---|---| | 06:58:36–47 | Reconocimiento de la web y `license.txt` | GET 200/301/404 | | 06:59:10 | `rr.php` ya está operativa | GET 200, 4.676 bytes | | 07:00:17 | Navegación de la shell por `/web/wp-content` | GET 200 | | 07:02:16 | Operación POST sobre `rr.php` en `/web` | 200; correlacionada con `wp-cl.php` | | 07:02:19–25 | `wp-cl.php` se sirve (67.634 bytes) y recibe POST | 200 / 302 | | 07:02:53 | POST a `rr.php` bajo `wp-content/mu-plugins` | 200; correlacionado con `health-check.php` | | 07:03:00 | POST a `wp-login.php` | **500** | | 07:03:03–24 | Sesión ya autenticada: home carga assets de admin bar; `/wp-admin/` y `users/me?context=edit` responden 200 | acceso WordPress autenticado confirmado | | 07:03:28–41 | Abre instalador, sube plugin y activa `wp-highlits/wp-highlits.php` | `upload-plugin` POST, activación con nonce | | 07:03:47–04:12 | Accede a Wordfence y hace **dos POST a `wp-admin/options.php`** | ambos redirigen a `settings-updated=true` | | 07:04:35 | Última acción observada de `rr.php` | POST 200 | ### Conclusiones corregidas 1. La ventana corta no incluía el POST de login. El log completo muestra que a las `07:03:00` hubo POST a `wp-login.php` con 500 y, segundos después, una sesión WordPress autenticada. La evidencia posterior (`/wp-admin/` 200, REST `users/me?context=edit` 200, instalación/activación de plugin) confirma acceso administrativo efectivo. 2. El actor instaló y activó explícitamente `wp-highlits`; no fue solo un fichero abandonado en disco. 3. El actor modificó la configuración de Wordfence dos veces. Los cuerpos POST no están en access log: no sabemos aún qué settings cambió, así que la configuración de Wordfence debe considerarse no fiable hasta auditoría/reinstalación controlada. 4. `rr.php` ya existía a las 06:59:10. Este archivo no contiene su request de creación. Tampoco contiene una request del atacante a la ruta de `sys.php`; **la hipótesis de Joomla como vector sigue siendo plausible por el artefacto hallado, pero no está probada por este access log**. ### Acciones que pasan a ser obligatorias antes de declarar el WordPress limpio - rotar credenciales de todos los administradores WP, revocar sesiones y Application Passwords, y rotar salts; - revisar/reinstalar Wordfence desde fuente limpia y comparar/reconstruir su configuración; buscar whitelists o referencias a `130.195.250.105`/`149.102.236.228`; - revisar las cuentas de administrador existentes y los logs/configuración de Wordfence si quedan disponibles; - mantener la migración de Joomla a mirror estático como mitigación estructural, pero no atribuirle sin reservas la implantación inicial de `rr.php`.
Author
Owner

Pausa operativa temporal — 29-jul-2026

La investigación forense queda pausada, no cerrada, para priorizar la publicación editorial urgente de la carta.

Estado preservado:

  • evidencia CDmon íntegra del 29-jul: logs/incident-183/evidence/cdmon-accesslog-20260729-full-received.txt (SHA-256 24dae1ed3f1a3c99670fc63c3fdb0615c15f5c890088f37225fd22bcc453fc9a);
  • cronología administrativa confirmada y acciones pendientes documentadas en el comentario #492;
  • no se debe interpretar la pausa como resolución, ni eliminar/quitar cuarentenas o medidas de contención vigentes.

Al reanudar, el orden es: recuperar/auditar acceso administrativo, rotar secretos WP, reconstruir/verificar Wordfence y solicitar/correlacionar logs FTP/SFTP/SSH/panel para localizar la creación inicial de rr.php.

## Pausa operativa temporal — 29-jul-2026 La investigación forense queda **pausada**, no cerrada, para priorizar la publicación editorial urgente de la carta. Estado preservado: - evidencia CDmon íntegra del 29-jul: `logs/incident-183/evidence/cdmon-accesslog-20260729-full-received.txt` (`SHA-256 24dae1ed3f1a3c99670fc63c3fdb0615c15f5c890088f37225fd22bcc453fc9a`); - cronología administrativa confirmada y acciones pendientes documentadas en el comentario #492; - no se debe interpretar la pausa como resolución, ni eliminar/quitar cuarentenas o medidas de contención vigentes. Al reanudar, el orden es: recuperar/auditar acceso administrativo, rotar secretos WP, reconstruir/verificar Wordfence y solicitar/correlacionar logs FTP/SFTP/SSH/panel para localizar la creación inicial de `rr.php`.
Author
Owner

D1 ejecutada: el Joomla ya no es superficie de ataque — se puede apagar tras la ventana

La remediación acordada en D1 (#180 comment-442) está desplegada. Desde el 30-jul ~20:30 UTC,
antiguo.feadulta.com ya no sirve el Joomla: sirve un mirror HTML estático desde el servidor
de Hetzner. Detalle técnico en #180 comment-510 y comment-511.

Lo que esto significa para este incidente: el archivo histórico deja de ejecutar PHP. El mirror
se construyó únicamente por HTTP, nunca copiando el filesystem, precisamente para no arrastrar
ningún .php comprometido. Se verificó antes de publicarlo: 0 ficheros con PHP, 0 páginas de
challenge congeladas, y la paridad de contenido salió limpia (25.383 páginas, 0 diferencias de
título ni de texto — #180 comment-509).

Cuándo se puede apagar el Joomla

A partir del 1-2 de agosto, al cerrarse la ventana de 48-72 h de tráfico real que arrancó con
el cambio de DNS.

El motivo de esperar no es técnico del mirror, es de reversibilidad: mientras dura la ventana, el
plan de vuelta atrás es revertir el registro A, y eso exige que el Joomla siga respondiendo al otro
lado. Cerrada la ventana, esa red de seguridad ya no hace falta.

Cómo apagarlo — y qué NO hacer

.htaccess deny  +  parar el sitio

Sin borrar nada. Tres razones, y las tres importan en este issue:

  1. La investigación forense está pausada, no cerrada (comment-494). El árbol del Joomla contiene
    la web shell sys.php y el resto de artefactos en cuarentena reversible. Borrarlos destruye
    evidencia de un incidente abierto.
  2. El apagado sigue siendo reversible mientras los ficheros existan: quitar el deny lo
    devuelve a la vida. Borrar convierte una puerta de ida y vuelta en un camino sin retorno.
  3. Para reconstruir el mirror ya no dependemos de CDMON —el snapshot está capturado y verificado
    (#180 comment-502)—, pero para reabrir la investigación sí.

⚠️ Esto pone fecha límite a la evidencia

El hosting de CDMON caduca el 07/08/2026. Si no se renueva o degrada a un plan barato,
desaparecen a la vez el rollback y todo el material forense de este incidente: el árbol
comprometido, los artefactos en cuarentena y los logs del panel que aún no se han solicitado.

En #180 la renovación de CDMON estaba justificada como seguro de la migración. Aquí es además un
requisito de preservación de evidencia
, y con una fecha mucho más dura: quedan 8 días.

Al reanudar la investigación (orden fijado en comment-494: recuperar/auditar acceso administrativo,
rotar secretos WP, reconstruir/verificar Wordfence, y correlacionar logs FTP/SFTP/SSH/panel para
localizar la creación inicial de rr.php), conviene sacar copia de lo que haga falta antes del
07/08
, no después.

Lo que este cambio NO resuelve

Nada de esto toca el WordPress, que es el objeto de este issue. Los backdoors
(health-check.php, wp-highlits) y el vector de entrada siguen exactamente donde estaban. Lo
único que cambia es que la mitad Joomla del sitio deja de poder ejecutar código, que era el
objetivo de D1.

## D1 ejecutada: el Joomla ya no es superficie de ataque — se puede apagar tras la ventana La remediación acordada en D1 (#180 comment-442) está desplegada. Desde el **30-jul ~20:30 UTC**, `antiguo.feadulta.com` **ya no sirve el Joomla**: sirve un mirror HTML estático desde el servidor de Hetzner. Detalle técnico en #180 comment-510 y comment-511. Lo que esto significa para este incidente: **el archivo histórico deja de ejecutar PHP**. El mirror se construyó únicamente por HTTP, nunca copiando el filesystem, precisamente para no arrastrar ningún `.php` comprometido. Se verificó antes de publicarlo: 0 ficheros con PHP, 0 páginas de challenge congeladas, y la paridad de contenido salió limpia (25.383 páginas, 0 diferencias de título ni de texto — #180 comment-509). ### Cuándo se puede apagar el Joomla **A partir del 1-2 de agosto**, al cerrarse la ventana de 48-72 h de tráfico real que arrancó con el cambio de DNS. El motivo de esperar no es técnico del mirror, es de reversibilidad: mientras dura la ventana, el plan de vuelta atrás es revertir el registro A, y eso exige que el Joomla siga respondiendo al otro lado. Cerrada la ventana, esa red de seguridad ya no hace falta. ### Cómo apagarlo — y qué NO hacer ``` .htaccess deny + parar el sitio ``` **Sin borrar nada.** Tres razones, y las tres importan en este issue: 1. **La investigación forense está pausada, no cerrada** (comment-494). El árbol del Joomla contiene la web shell `sys.php` y el resto de artefactos en cuarentena reversible. Borrarlos destruye evidencia de un incidente abierto. 2. **El apagado sigue siendo reversible** mientras los ficheros existan: quitar el `deny` lo devuelve a la vida. Borrar convierte una puerta de ida y vuelta en un camino sin retorno. 3. Para *reconstruir* el mirror ya no dependemos de CDMON —el snapshot está capturado y verificado (#180 comment-502)—, pero para *reabrir la investigación* sí. ### ⚠️ Esto pone fecha límite a la evidencia El hosting de CDMON **caduca el 07/08/2026**. Si no se renueva o degrada a un plan barato, desaparecen a la vez el rollback y **todo el material forense de este incidente**: el árbol comprometido, los artefactos en cuarentena y los logs del panel que aún no se han solicitado. En #180 la renovación de CDMON estaba justificada como seguro de la migración. **Aquí es además un requisito de preservación de evidencia**, y con una fecha mucho más dura: quedan 8 días. Al reanudar la investigación (orden fijado en comment-494: recuperar/auditar acceso administrativo, rotar secretos WP, reconstruir/verificar Wordfence, y correlacionar logs FTP/SFTP/SSH/panel para localizar la creación inicial de `rr.php`), **conviene sacar copia de lo que haga falta antes del 07/08**, no después. ### Lo que este cambio NO resuelve Nada de esto toca el WordPress, que es el objeto de este issue. Los backdoors (`health-check.php`, `wp-highlits`) y el vector de entrada siguen exactamente donde estaban. Lo único que cambia es que **la mitad Joomla del sitio deja de poder ejecutar código**, que era el objetivo de D1.
Author
Owner

Evidencia preservada en tres copias — y corrección al comentario anterior

Corrección

En comment-513 escribí que al caducar CDMON el 07/08 desaparecería "todo el material forense de
este incidente"
. No es cierto, y conviene aclararlo antes de que empuje a decidir por miedo.

Los artefactos y los logs ya recibidos estaban en local desde el principio. Lo que sí se perdería
al apagarse CDMON es distinto y más acotado:

  • el árbol de producción comprometido tal cual está ahora (no una copia, el original vivo);
  • los logs de panel, FTP/SFTP y SSH que aún no se han solicitado — que son justamente los que
    harían falta para localizar la creación inicial de rr.php.

La renovación de CDMON sigue siendo necesaria por el rollback de la migración (#180 §1). Como
argumento de preservación de evidencia, es mucho más débil de lo que dije.

Lo que estaba en local y ya está respaldado

logs/incident-183/ no estaba en gitea: logs/ figura en el .gitignore (línea 43), así que
ninguno de esos ficheros se ha subido nunca. Vivía en una única copia, en el disco de la WSL.

Ya no. Empaquetado y replicado:

incident-183-evidencia-20260730T233010Z.tar.gz
26 ficheros · 60 KB
SHA-256  5a4eed13222b35579a0fcb8ffa212a34e98bfb41df5cf873025629a088b32127
Copia Ruta
Origen ~/joomla-migration/logs/incident-183/ (WSL)
Windows N:\Backup\incident-183\ — fuera de la WSL, junto al resto de backups
Hetzner /data/backups/incident-183/

Las tres verificadas contra el mismo SHA-256.

Va en .tar.gz a propósito. Dentro hay malware real (health-check.php, wp-highlits.php,
joomla-sys.php): comprimido no puede ejecutarse por accidente ni acabar servido si el árbol se
despliega alguna vez en un webroot. Por el mismo motivo no se ha subido al repo suelto, aunque
logs/ esté ignorado y bastara con un git add -f.

Contenido preservado: los tres artefactos, el log de acceso completo del 29-jul
(cdmon-accesslog-20260729-full-received.txt), los errors.log/php_errors.log del 27, 28 y 29 de
julio, el extracto del correo de CDmon sobre wp-cl.php del 27-jul, y el manifest.json con los
hashes.

Estado del Joomla

Retirado como superficie de ataque: desde el 30-jul antiguo.feadulta.com sirve un mirror estático
desde Hetzner y no ejecuta PHP (#180 comment-510). El Joomla de CDMON sigue vivo e intacto hasta
que se cierre la ventana de 48-72 h; al apagarlo, .htaccess deny + parar, sin borrar.

Copia íntegra del Joomla en local, por si hiciera falta volver a él:
mirror-antiguo/source/ — 2,7 GB de ficheros + 66 MB de base de datos, con MANIFEST-source.sha256.

Lo que este incidente aporta a la migración del 03-ago

El WordPress que se migra el lunes es el que tiene los backdoors. El criterio de migración limpia
—instalación nueva, plugins desde su origen, y de producción solo base de datos y uploads previo
escaneo— queda documentado en #180, junto con la parte que un cambio de servidor no corta:
las cuentas de administrador, sus hashes y las application passwords viajan dentro del dump. Ahí
está también el recordatorio de resolver la cuenta andrey (ID 1087), que sigue pendiente desde
este issue.

## Evidencia preservada en tres copias — y corrección al comentario anterior ### Corrección En comment-513 escribí que al caducar CDMON el 07/08 desaparecería *"todo el material forense de este incidente"*. **No es cierto, y conviene aclararlo antes de que empuje a decidir por miedo.** Los artefactos y los logs ya recibidos **estaban en local desde el principio**. Lo que sí se perdería al apagarse CDMON es distinto y más acotado: - el árbol de producción comprometido *tal cual está ahora* (no una copia, el original vivo); - los logs de panel, FTP/SFTP y SSH **que aún no se han solicitado** — que son justamente los que harían falta para localizar la creación inicial de `rr.php`. La renovación de CDMON sigue siendo necesaria por el rollback de la migración (#180 §1). Como argumento de preservación de evidencia, es mucho más débil de lo que dije. ### Lo que estaba en local y ya está respaldado `logs/incident-183/` **no estaba en gitea**: `logs/` figura en el `.gitignore` (línea 43), así que ninguno de esos ficheros se ha subido nunca. Vivía en una única copia, en el disco de la WSL. Ya no. Empaquetado y replicado: ``` incident-183-evidencia-20260730T233010Z.tar.gz 26 ficheros · 60 KB SHA-256 5a4eed13222b35579a0fcb8ffa212a34e98bfb41df5cf873025629a088b32127 ``` | Copia | Ruta | |---|---| | Origen | `~/joomla-migration/logs/incident-183/` (WSL) | | Windows | `N:\Backup\incident-183\` — fuera de la WSL, junto al resto de backups | | Hetzner | `/data/backups/incident-183/` | Las tres verificadas contra el mismo SHA-256. **Va en `.tar.gz` a propósito.** Dentro hay malware real (`health-check.php`, `wp-highlits.php`, `joomla-sys.php`): comprimido no puede ejecutarse por accidente ni acabar servido si el árbol se despliega alguna vez en un webroot. Por el mismo motivo **no se ha subido al repo suelto**, aunque `logs/` esté ignorado y bastara con un `git add -f`. Contenido preservado: los tres artefactos, el log de acceso completo del 29-jul (`cdmon-accesslog-20260729-full-received.txt`), los `errors.log`/`php_errors.log` del 27, 28 y 29 de julio, el extracto del correo de CDmon sobre `wp-cl.php` del 27-jul, y el `manifest.json` con los hashes. ### Estado del Joomla Retirado como superficie de ataque: desde el 30-jul `antiguo.feadulta.com` sirve un mirror estático desde Hetzner y **no ejecuta PHP** (#180 comment-510). El Joomla de CDMON sigue vivo e intacto hasta que se cierre la ventana de 48-72 h; al apagarlo, `.htaccess` deny + parar, **sin borrar**. Copia íntegra del Joomla en local, por si hiciera falta volver a él: `mirror-antiguo/source/` — 2,7 GB de ficheros + 66 MB de base de datos, con `MANIFEST-source.sha256`. ### Lo que este incidente aporta a la migración del 03-ago El WordPress que se migra el lunes es el que tiene los backdoors. El criterio de **migración limpia** —instalación nueva, plugins desde su origen, y de producción solo base de datos y `uploads` previo escaneo— queda documentado en **#180**, junto con la parte que un cambio de servidor **no** corta: las cuentas de administrador, sus hashes y las *application passwords* viajan dentro del dump. Ahí está también el recordatorio de resolver la cuenta `andrey` (ID 1087), que sigue pendiente desde este issue.
Sign in to join this conversation.
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rafa/feadulta#183