El barrido del antimalware terminó. La pantalla mostró cero amenazas activas. El equipo respiró. Tres semanas después, un análisis rutinario de la carpeta de subidas reveló cinco archivos de respaldo —volcados de base de datos incluidos— descargables desde el servidor de producción sin ninguna autenticación.
Respuesta breve. Limpiar el malware y cerrar la brecha son dos trabajos distintos. El primero puede terminar mientras el segundo permanece completamente abierto. Este artículo documenta ambos, incluyendo los cinco errores de método del equipo que los mantuvo separados durante semanas.
¿Qué hace un sitio comprometido mientras el equipo lo limpia?
La base de datos miente; los mtime no
Cualquier análisis forense de un sitio WordPress comprometido empieza con una pregunta: ¿cuándo ocurrió esto? La respuesta que ofrece la base de datos es, en este caso, inutilizable como cronología.
Ocho entradas publicadas declaraban fechas anteriores a otras con un identificador de base de datos mayor: fueron creadas más tarde pero fechadas como si fueran más antiguas. El mismo patrón aparece en otra tabla del sitio, con cuatro comentarios de identificadores consecutivos distribuidos en dos años distintos. No fue un error de zona horaria: el campo fecha fue modificado manualmente.
Ver fuente Mermaid
%%{init: {'theme': 'dark', 'themeVariables': {'primaryColor': '#1e293b', 'primaryTextColor': '#f8fafc', 'lineColor': '#94a3b8'}}}%%
flowchart LR
subgraph real["Orden real de creacion (ID de base de datos, secuencial)"]
direction LR
id_a["Entrada A\nID N"] --> id_b["Entrada B\nID N+1"] --> id_c["Entrada C\nID N+2"]
end
subgraph decl["Fecha declarada por quien publico el contenido"]
direction LR
f_a["2025-XX-XX"] --> f_b["2019-XX-XX\n(ID mayor,\nfecha mas antigua)"] --> f_c["2026-XX-XX"]
end
id_a -.-> f_a
id_b -.-> f_b
id_c -.-> f_c
style f_b fill:#7f1d1d,stroke:#ef4444,color:#fca5a5
style id_b fill:#1e3a5f,stroke:#3b82f6,color:#bfdbfe
La cronología válida para este sitio es la de los tiempos de modificación del sistema de archivos (mtime). Son coherentes entre sí y con el único registro de ejecución de código documentado. No se probó que el atacante pudiera alterarlos; afirmar que son incorruptibles excedería la evidencia. Lo que sí puede afirmarse: son la única fuente consistente disponible.
La ventana de actividad reconstruida por mtime va del 25 de junio al 26 de agosto de 2026.
La cadena de ejecución del 3 de julio
El único momento en que puede demostrarse ejecución de código ocurrió el 3 de julio de 2026. Un directorio creado menos de diez minutos antes contenía cuatro elementos: la copia íntegra de un plugin legítimo, un archivo oculto, un cargador PHP y un archivo de configuración de 16 bytes. La elección de un directorio de plugins como contenedor no fue casual: cualquier archivo PHP en esa ubicación se carga automáticamente con cada petición de WordPress.
La cadena: el cargador incluyó el archivo oculto, que invocó una función interna que intentó abrir un proceso del sistema operativo con popen(). El hosting tenía esa función deshabilitada. El intento falló con el error registrado literalmente:
[03-Jul-2026 HH:MM:SS UTC] PHP Fatal error: Call to undefined function popen() in /home/USUARIO/public_html/wp-content/plugins/NOMBRE-FALSO-DEL-ATACANTE/cargador.php on line 7Stack trace: #0 /home/USUARIO/public_html/wp-content/plugins/NOMBRE-FALSO-DEL-ATACANTE/oculto.php(38): funcion_interna() #1 /home/USUARIO/public_html/wp-settings.php(422): include_once('...') #2 /home/USUARIO/public_html/wp-load.php(50): require_once('...') #3 {main}
popen), el tipo de error y la estructura de la pila son los del registro original. La marca de tiempo exacta está pendiente de autorización para publicar. Fuente: expediente forense interno, Transgenia, 2026.Este registro es la única prueba de ejecución de código en todo el incidente. La lectura canónica del bloqueo es la lista de funciones deshabilitadas del hosting; no hay otra evidencia que contradiga esa interpretación.
Ver fuente Mermaid
%%{init: {'theme': 'dark', 'themeVariables': {'primaryColor': '#1e293b', 'primaryTextColor': '#f8fafc', 'primaryBorderColor': '#fb6c25', 'lineColor': '#94a3b8', 'edgeLabelBackground': '#1e293b'}}}%%
flowchart TD
A["Directorio de plugin falso\ncreado 9 minutos antes\n(timestamp Unix en el nombre)"]
B["cargador.php\nIncluye el segundo archivo"]
C["Archivo oculto\nLlama a la funcion interna"]
D["Funcion interna\nIntenta abrir proceso del sistema\npopen()"]
E["BLOQUEADO\nCall to undefined function popen\ndisable_functions del hosting"]
A --> B --> C --> D --> E
style A fill:#0f172a,stroke:#fb6c25,color:#f8fafc
style B fill:#0f172a,stroke:#fb6c25,color:#f8fafc
style C fill:#0f172a,stroke:#fb6c25,color:#f8fafc
style D fill:#0f172a,stroke:#fb6c25,color:#f8fafc
style E fill:#7f1d1d,stroke:#ef4444,color:#fca5a5,font-weight:bold
El contenido en números
Al momento del análisis, el sitio tenía 604 entradas inventariadas: 115 publicadas, 483 en la papelera, 4 borradores y 2 autodrafts de WordPress. De las 115 publicadas, cuatro correspondían al contenido legítimo del cliente: el 3.5 por ciento del total. El resto: 102 entradas de apuestas deportivas en nueve idiomas, cinco de casino y cuatro de plantilla por defecto de WordPress.
Ver fuente Mermaid
%%{init: {'theme': 'dark'}}%%
xychart-beta horizontal
title "115 entradas publicadas: 4 son del cliente (3.5%)"
x-axis ["Del cliente", "Casino", "Por defecto", "Apuestas en 9 idiomas"]
y-axis 0 --> 110
bar [4, 5, 4, 102]
Ver fuente Mermaid
%%{init: {'theme': 'dark', 'themeVariables': {'primaryColor': '#1e293b', 'primaryTextColor': '#f8fafc', 'lineColor': '#94a3b8'}}}%%
flowchart TB
subgraph EXT["Exterior (internet)"]
direction LR
visitante["Visitante\nlegitimo"]
atacante["Atacante\n(origen desconocido)"]
end
subgraph WP["WordPress (sitio comprometido)"]
direction TB
admin["Cuenta administradora\ncomprometida"]
mu["mu-plugins/\nCarga automatica\nInvisible al panel"]
plugins["plugins/\nPlugin falso del atacante"]
uploads["wp-content/uploads/\nArchivos de respaldo\nDescargables sin autenticacion"]
core["WordPress Core\nActualizado post-incidente"]
content["604 entradas:\n4 del cliente (3.5%)\n600 spam inyectado"]
end
visitante -->|"HTTP 200"| content
atacante -->|"Credencial comprometida\n(hipotesis de trabajo)"| admin
admin -->|"Publico contenido spam"| content
admin -->|"Instalo plugin falso"| plugins
plugins --> mu
mu -->|"Se ejecuta en cada peticion"| core
style mu fill:#7f1d1d,stroke:#ef4444,color:#fca5a5,font-weight:bold
style uploads fill:#7f1d1d,stroke:#ef4444,color:#fca5a5
style admin fill:#78350f,stroke:#f59e0b,color:#fde68a
style content fill:#1e3a5f,stroke:#3b82f6,color:#bfdbfe
style atacante fill:#450a0a,stroke:#ef4444,color:#fca5a5
El patrón de autoría confirma que la cuenta legítima de administración fue usada para publicar spam: 10 de 18 entradas con autor asignado salieron con esa identidad. De los 431 usuarios registrados, 428 eran suscriptores falsos sin ninguna entrada asociada. Las 38 categorías del sitio incluían 29 de apuestas deportivas. De los 108 comentarios, ninguno provenía de un visitante real: cuatro eran de plantilla por defecto de WordPress y ocho eran notas internas de pedidos de plataformas de comercio electrónico de años anteriores.
Limpiar es necesario. No es suficiente.
Ver fuente Mermaid
%%{init: {'theme': 'dark', 'themeVariables': {'primaryColor': '#1e293b', 'primaryTextColor': '#f8fafc', 'lineColor': '#94a3b8'}}}%%
flowchart LR
subgraph LIMPIO["Limpiado al 2026-09-18 (76%)"]
direction TB
l1["Plugin falso eliminado\nde mu-plugins"]
l2["Segunda cuenta\nadministradora eliminada"]
l3["WordPress Core\nactualizado"]
l4["15 artefactos de malware\neliminados del sistema de archivos"]
l5["Pagina spam\ncerrada (404)"]
end
subgraph BRECHA["Brecha abierta (24% pendiente)"]
direction TB
b1["Archivos de respaldo\nDescargables sin autenticacion"]
b2["Hashes phpass\nExpuestos en volcado SQL"]
b3["Gestor de archivos\ndel panel activo"]
b4["Notificacion LFPDPPP\npendiente de confirmacion"]
b5["Barrido del directorio\nde subidas: solo ~33% revisado"]
end
LIMPIO -. "No implica" .-> BRECHA
style LIMPIO fill:#052e16,stroke:#22c55e,color:#86efac
style BRECHA fill:#7f1d1d,stroke:#ef4444,color:#fca5a5
style b1 fill:#450a0a,stroke:#ef4444,color:#fca5a5,font-weight:bold
style b2 fill:#450a0a,stroke:#ef4444,color:#fca5a5,font-weight:bold
Al corte del 2026-09-18, 25 de las 33 acciones documentadas habían sido cerradas: el 76 por ciento. El directorio de plugins de carga obligatoria estaba limpio. La segunda cuenta administradora había sido eliminada. El núcleo de WordPress había sido actualizado. Quince artefactos de malware ya no existían en el sistema de archivos.
Lo que seguía abierto no era menor.
Los huecos que siguen abiertos
Archivos de respaldo descargables sin autenticación. Cinco archivos de respaldo eran descargables directamente desde el servidor de producción sin ninguna credencial. Llevaban documentados veinte días desde que se identificaron. El de mayor tamaño supera los 200 MB y data de 2017; su contenido no fue abierto. Por tamaño y formato, el expediente infiere sistema de archivos más base de datos.
El volcado SQL de menor tamaño, de aproximadamente 12 MB, contenía los hashes de contraseña de al menos dos cuentas de usuario en formato phpass: MD5 con iteraciones, crackeable con GPU moderno en horas. También contenía datos personales de terceros: clientes de la empresa afectada registrados en las plataformas de comercio electrónico que usaban el sitio.
La base de datos actual, con un tamaño aproximado de 118 MB, devuelve HTTP 404 por petición directa: está en disco pero no es accesible desde el exterior. No todos los archivos sensibles tienen el mismo nivel de exposición.
Página de contenido spam activa. Al momento del análisis, la página puerta del spam devolvía HTTP 200 y servía aproximadamente 800 KB de contenido. Al 2026-09-19 fue verificada como HTTP 404: ese hueco específico ya se cerró.
Gestor de archivos por panel activo. El panel de administración seguía teniendo habilitado el gestor de archivos, lo que permite editar cualquier archivo PHP del sitio desde el navegador.
El .htaccess que oculta sin proteger
La carpeta de subidas tenía un archivo .htaccess con la directiva IndexIgnore *. Esta directiva oculta el listado del directorio cuando alguien navega a esa URL. No niega el acceso. Un atacante que conozca el nombre del archivo puede descargarlo directamente.
Ver fuente Mermaid
%%{init: {'theme': 'dark', 'themeVariables': {'primaryColor': '#1e293b', 'primaryTextColor': '#f8fafc', 'lineColor': '#94a3b8'}}}%%
flowchart LR
subgraph L["Configuracion existente"]
direction TB
la["IndexIgnore *"]
lb["Efecto real:\nOCULTA el listado del directorio\nNO niega el acceso"]
lc["Resultado:\n.sql y .zip siguen\ndescargables sin autenticacion"]
la --> lb --> lc
end
subgraph R["Configuracion que habria negado el acceso"]
direction TB
ra["Options -Indexes"]
rb["RedirectMatch 403 \.sql$"]
rc["RedirectMatch 403 \.zip$"]
rd["(y .gz, .tar, .bak)"]
re["Resultado:\n403 Forbidden para archivos\nde base de datos y respaldo"]
ra --> rb --> rc --> rd --> re
end
L -. "vs." .-> R
style L fill:#7f1d1d,stroke:#ef4444,color:#fca5a5
style lc fill:#450a0a,stroke:#ef4444,color:#fca5a5,font-weight:bold
style R fill:#14532d,stroke:#22c55e,color:#86efac
style re fill:#052e16,stroke:#22c55e,color:#86efac,font-weight:bold
IndexIgnore * oculta el listado pero no niega acceso a archivos individuales. La protección real del sitio cubría archivos .php y no alcanzaba a .sql ni .zip. La columna derecha muestra un ejemplo de configuración que sí habría negado el acceso; no es una receta universal. Fuente: expediente forense interno, Transgenia, 2026.La protección real que tenía el directorio cubría archivos .php —una directiva Deny para PHP. No cubría .sql ni .zip. La configuración de seguridad estaba parcialmente correcta, y esa parcialidad la hacía ineficaz exactamente donde importaba.
El cargador latente: cómo no titular un hallazgo
El ciclo de persistencia
La razón por la que un archivo en mu-plugins es más peligroso que en cualquier otro directorio no es su contenido: es su posición. WordPress carga todos los archivos PHP de ese directorio antes de procesar cualquier petición, sin excepción y sin que el panel de administración los muestre.
Ver fuente Mermaid
%%{init: {'theme': 'dark', 'themeVariables': {'primaryColor': '#1e293b', 'primaryTextColor': '#f8fafc', 'lineColor': '#94a3b8'}}}%%
flowchart LR
req["Peticion HTTP\ncualquier visitante"]
wp["WordPress\ninicializa"]
mu["mu-plugins/\nCarga TODOS los .php\nSIN excepcion ni VoBo del admin"]
cargador["cargador.php\n56 bytes\nIncluye archivo oculto"]
oculto["Archivo oculto\n522 bytes\nFunciones en hex"]
panel["Panel de administracion\nNO muestra nada\nInvisible para el operador"]
bloqueo["BLOQUEADO\npopen() deshabilitada\npor el hosting"]
req --> wp --> mu --> cargador --> oculto --> bloqueo
mu -.->|"No aparece en"| panel
style mu fill:#7f1d1d,stroke:#ef4444,color:#fca5a5,font-weight:bold
style cargador fill:#450a0a,stroke:#ef4444,color:#fca5a5
style oculto fill:#450a0a,stroke:#ef4444,color:#fca5a5
style bloqueo fill:#052e16,stroke:#22c55e,color:#86efac,font-weight:bold
style panel fill:#1e293b,stroke:#64748b,color:#94a3b8,font-style:italic
popen() frenó la ejecución en este caso; sin él, el ciclo habría completado. Fuente: expediente forense interno, Transgenia, 2026.El archivo de 56 bytes
En el directorio de plugins de carga obligatoria se encontró un archivo de 56 bytes. Su contenido era una sola instrucción: un envoltorio phar:// dentro de trim().
trim() no dispara ese envoltorio. El wrapper phar:// en PHP se activa cuando una función que toca el sistema de archivos —include, require, fopen, entre otras— recibe una ruta phar://. trim() procesa una cadena y la devuelve; no toca el sistema de archivos. Esto fue verificado en un contenedor aislado antes de incluir el hallazgo en el informe.
El archivo se saca del sitio de todas formas, por dos razones: carga en cada petición de WordPress por estar en el directorio de carga obligatoria, y no aparece en el listado de plugins del panel de administración. La razón exacta de esa invisibilidad en el panel no quedó establecida en el expediente.
Existe un segundo artefacto de 522 bytes con tres funciones de ejecución codificadas en escapes hexadecimales. La codificación hexadecimal es una técnica de evasión: una búsqueda de texto plano sobre el archivo no encontrará las palabras clave de las funciones.
El análisis antimalware
Antes de sellar el contenedor de evidencias, 210 archivos fueron sometidos a análisis antimalware. El resultado fue cero detecciones. El contenedor fue sellado con cifrado AES-256; los intentos posteriores de análisis sobre el contenedor sellado retornaron errores de protección por contraseña para las 288 entradas registradas. El resultado de cero detecciones corresponde a los 210 archivos revisados antes del sellado.
La explicación no es la ausencia de malware: los fragmentos de 16, 56 y 522 bytes están por debajo del umbral de longitud de cualquier firma antimalware conocida. Un motor de firmas no puede identificar lo que no tiene mapeado. Nadie en el equipo emitió el diagnóstico de "no hay malware porque el antivirus dice que no hay"; el expediente registra esto explícitamente porque es el razonamiento que el resultado invita a hacer.
Cómo titular correctamente
El titular tentador era: Se encontró un cargador PHAR activo en el directorio de plugins obligatorios.
El titular correcto es: Se encontró un cargador PHAR a una palabra de armarse, en un directorio que lo carga en cada petición y que lo hace invisible al panel.
La diferencia no es semántica: uno describe una amenaza activa que no existía; el otro describe correctamente la amenaza real: latente, no activa, pero en posición privilegiada.
Un conector de IA concedió más alcance que la credencial equivalente por REST
Durante el análisis, la REST API de WordPress estaba bloqueada en modo edición: devolvía error 401 incluso con una credencial de administrador válida. El bloqueo es una medida de endurecimiento estándar para sitios que no necesitan exponer esa interfaz.
Un plugin de conector de inteligencia artificial instalado en el sitio no respetaba ese endurecimiento. Por ese canal, el equipo pudo leer los 431 usuarios con sus roles sin ningún problema, usando las mismas credenciales que la REST API rechazaba.
La lección tiene dos caras. La inmediata: un vector de lectura estaba abierto donde el operador creía que estaba cerrado. La estructural: instalar un segundo conector para ganar acceso habría sumado código de terceros con pocas valoraciones a un sitio ya comprometido. Sumar superficie de ataque para ahorrar cinco clics es una mala operación de seguridad.
Cinco errores más una corrección hermana
E1 — Conteo parcial reportado como conteo final. El barrido del directorio de subidas produjo un manifiesto de exactamente 4,000 líneas. La última línea del manifiesto es el marcador de corte de la herramienta, no un nombre de archivo real. El barrido cubrió aproximadamente un tercio del directorio. La conclusión honesta: se encontró una fuga en la parte revisada; se desconoce qué hay en el resto.
E1-hermana — Instalado no equivale a activo. La API del panel devuelve plugins instalados en el servidor. La fuente que responde qué está activo es la tabla de opciones en la base de datos. Son dos preguntas distintas con fuentes distintas.
E2 — Capacidad del atacante sobreestimada. La descripción inicial del archivo de 56 bytes lo calificaba como una deserialización PHAR activa. El experimento en contenedor aislado demostró que no lo era tal como estaba escrito. El informe conserva la afirmación original junto a su corrección, con fecha. Eso lo hace auditable: quien lea el expediente puede seguir el razonamiento que llevó al error y la evidencia que lo refutó.
E3 — Artefacto del atacante confundido con ruido propio. Dos registros de error PHP, en el mismo sitio. Uno documenta la cadena de ejecución del atacante del 3 de julio. El otro fue generado por la propia sonda HTTP de la auditoría en un turno anterior. Lo único que los distingue: haber registrado qué hizo uno mismo y cuándo lo hizo. Sin ese registro, el segundo error habría parecido parte del ataque y habría sesgado el análisis.
E4 — La auditoría bloqueó la IP de Transgenia. Durante diecinueve días, el equipo no pudo acceder al sitio desde la oficina. Tardó seis días en darse cuenta de que el problema era el acceso propio, no el sitio. "El sitio está caído" significaba "nuestro acceso está bloqueado."
Cuatro causas suficientes por separado en una sola sesión de trabajo: autenticaciones fallidas repetidas, recorrido recursivo de directorios, sondeo de rutas con firma de escáner de vulnerabilidades, y peticiones por rango sobre archivos grandes. El operador del hosting compartía la misma dirección IP pública que el equipo de auditoría. La confirmación de que "yo tampoco lo veo" ratificaba el bloqueo, no lo descartaba. La solución fue medir desde un punto externo sin relación con el cliente.
E5 — Portada congelada atribuida al plugin de caché. La portada mostraba una versión desactualizada del sitio. Un GET y un POST a la misma URL, en el mismo segundo, devolvieron respuestas distintas: el origen estaba sano. La copia desactualizada venía de la capa intermedia del hosting, alimentada por una cabecera de caché de un año que un plugin había dejado escrita. Una particularidad: el escudo web del antimalware del hosting intercepta URLs que contienen query strings y devuelve un reto de JavaScript de aproximadamente 12 KB. Esto invalida cualquier medición con cache-buster en la línea de comandos y rompe peticiones automatizadas. Si la herramienta de medición está siendo interceptada, lo que se mide es la herramienta, no el sitio.
Ver fuente Mermaid
%%{init: {'theme': 'dark', 'themeVariables': {'primaryColor': '#1e293b', 'primaryTextColor': '#f8fafc', 'lineColor': '#94a3b8'}}}%%
flowchart LR
subgraph E4["Error E4"]
direction TB
e4s["Sintoma:\nel sitio no responde\ndesde la oficina"]
e4r["Realidad:\nIP de la auditoria bloqueada\npor el hosting"]
e4c["Correccion:\nmedir desde un punto externo\nsin relacion con el cliente"]
e4s --> e4r --> e4c
end
subgraph E5["Error E5"]
direction TB
e5s["Sintoma:\nla portada muestra\ncontenido antiguo"]
e5r["Realidad:\ncopia en cache de la\ncapa intermedia del hosting"]
e5c["Correccion:\nGET vs POST al mismo segundo\ndesde la consola del navegador"]
e5s --> e5r --> e5c
end
subgraph E3["Error E3"]
direction TB
e3s["Sintoma:\nun fatal error coincide\ncon la firma del ataque"]
e3r["Realidad:\nlo genero la propia\nsonda HTTP de la auditoria"]
e3c["Correccion:\natribucion retirada\npor escrito con fecha"]
e3s --> e3r --> e3c
end
E4 -.-> E5
E5 -.-> E3
style e4r fill:#7f1d1d,stroke:#ef4444,color:#fca5a5
style e5r fill:#7f1d1d,stroke:#ef4444,color:#fca5a5
style e3r fill:#7f1d1d,stroke:#ef4444,color:#fca5a5
style e4c fill:#052e16,stroke:#22c55e,color:#86efac
style e5c fill:#052e16,stroke:#22c55e,color:#86efac
style e3c fill:#052e16,stroke:#22c55e,color:#86efac
Por qué contar los errores suma autoridad. Un informe donde nada salió mal no es un informe sin errores: es un informe sin control de calidad. La ronda adversarial que refutó E2, el contenedor aislado que lo decidió, la nota de alcance que acotó E1, la atribución retirada por escrito en E3: eso es el método funcionando. El cliente tiene un expediente auditable, no una narrativa pulida.
Regla exportable. Antes de tocar un sitio comprometido, registra tu propia huella: dirección IP de salida, hora, tipo y volumen de peticiones. Avisa al hosting antes de comenzar. Si no lo haces, pasarás parte del tiempo investigando tus propias marcas.
¿Qué no prueba este expediente?
Ver fuente Mermaid
%%{init: {'theme': 'dark', 'themeVariables': {'primaryColor': '#1e293b', 'primaryTextColor': '#f8fafc', 'lineColor': '#94a3b8'}}}%%
flowchart TB
inicio["Vector de entrada\nNO PROBADO\n(hipotesis de trabajo)"]
subgraph H1["Hipotesis 1: Credencial comprometida"]
direction TB
h1a["Contrasena de administrador\nobtenida por phishing,\nfuerza bruta o reutilizacion"]
h1b["Inicio de sesion\nen wp-admin"]
h1c["Instalacion del plugin falso\nPublicacion del contenido spam"]
h1a --> h1b --> h1c
end
subgraph H2["Hipotesis 2: Plugin con vulnerabilidad"]
direction TB
h2a["Plugin legitimo instalado\ncon vulnerabilidad sin parchear"]
h2b["Ejecucion remota de codigo\no carga de archivo malicioso"]
h2c["Credenciales obtenidas\nPlugin falso instalado"]
h2a --> h2b --> h2c
end
inicio --> H1
inicio --> H2
style inicio fill:#78350f,stroke:#f59e0b,color:#fde68a,font-weight:bold
style H1 fill:#1e293b,stroke:#64748b
style H2 fill:#1e293b,stroke:#64748b
- El vector de entrada no está probado. La hipótesis de trabajo más plausible es credencial comprometida o plugin vulnerable sin actualizar. Es una hipótesis, no una certeza documentada.
- La ejecución exitosa de código no está probada. El único registro disponible documenta un intento fallido del 3 de julio. No hay evidencia de una sesión de ejecución exitosa anterior o posterior.
- La exfiltración de datos no está probada. Exposición no equivale a extracción. Los archivos de respaldo eran accesibles; no hay evidencia de que hayan sido descargados por terceros. Esta distinción es relevante para la determinación de obligaciones bajo la LFPDPPP.
- El barrido del directorio de subidas cubrió aproximadamente un tercio. Es una estimación basada en la proporción del corte documentado, no un conteo exacto del total de archivos en el directorio.
- Una de las tres rutas del cargador de 56 bytes quedó sin verificar. La verificación en contenedor cubrió dos de los tres caminos de ejecución posibles; el tercero no fue probado.
- La cronología del contenido inyectado no es reconstruible. Solo la cronología de los archivos del sistema, basada en mtime, puede afirmarse con consistencia interna.
El caso está abierto al 2026-09-18
Estado al 2026-09-18: 25 de 33 acciones cerradas (76%). Tres pendientes críticos con fecha y owner asignado.
Ver fuente Mermaid
%%{init: {'theme': 'dark'}}%%
xychart-beta horizontal
title "33 acciones documentadas — corte 2026-09-18"
x-axis ["Pendientes (24%)", "Cerradas (76%)"]
y-axis 0 --> 28
bar [8, 25]
| Acción pendiente | Owner | Fecha límite |
|---|---|---|
| Cerrar archivos de respaldo descargables sin autenticación (documentado hace 20 días) | Cliente + Saurat Xiuhcoatl | 2026-09-19 |
| Forzar cambio de contraseña en todas las cuentas del sitio (hashes phpass expuestos) | Cliente | 2026-09-22 |
| Confirmar cierre de la notificación LFPDPPP: fecha de notificación y nombre del responsable | Cliente + área legal | Antes de quitar draft: true |
Publicar este artículo con estado PENDIENTE es la decisión correcta por una razón práctica: la brecha existe independientemente del estado del artículo. Documentar los pendientes con owner y fecha los pone en un registro externo con timestamp. El artículo no anuncia el incidente: lo acota y establece responsabilidades visibles.
Marco regulatorio aplicable (LFPDPPP)
El artículo 36 de la Ley Federal de Protección de Datos Personales en Posesión de los Particulares establece que el responsable del tratamiento debe notificar a los titulares cuando ocurra una vulneración de seguridad significativa. El volcado SQL descargable contenía datos personales de clientes de la empresa afectada: nombres, datos de contacto y registros de transacciones de años anteriores. La determinación de si esta exposición equivale a una vulneración en los términos del artículo 36 requiere criterio legal. Lo que está documentado: los datos existían, eran accesibles sin autenticación y pertenecen a terceros identificables.
Ver fuente Mermaid
%%{init: {'theme': 'dark', 'themeVariables': {'primaryColor': '#1e293b', 'primaryTextColor': '#f8fafc', 'lineColor': '#94a3b8'}}}%%
flowchart TD
inicio["Incidente de seguridad\ndetectado"]
dp["¿El sistema afectado\ncontenia datos personales\nde terceros?"]
si_dp["SI:\nVolcado SQL contenia\nhashes y datos de clientes"]
vuln["¿Equivale a vulneracion\nbajo LFPDPPP Art. 36?\n(Determinar con asesoria legal)"]
notif["Notificacion a la\nCuenta de Correo Electronico\nINAI (o directamente al titular\nsi el riesgo es alto)"]
medidas["Implementar medidas\nde seguridad correctivas:\nCerrar acceso no autorizado\nCambiar credenciales"]
cierre["Confirmar cierre:\nFecha de notificacion\nNombre del responsable\nEvidencia de remedios"]
no_dp["NO aplica\nobligacion de notificacion\nLFPDPPP"]
inicio --> dp
dp -->|"Si"| si_dp
dp -->|"No"| no_dp
si_dp --> vuln
vuln -->|"Si equivale"| notif
vuln -->|"Requiere evaluacion\n(pendiente)"| medidas
notif --> medidas --> cierre
style si_dp fill:#7f1d1d,stroke:#ef4444,color:#fca5a5
style notif fill:#78350f,stroke:#f59e0b,color:#fde68a
style cierre fill:#052e16,stroke:#22c55e,color:#86efac,font-weight:bold
style no_dp fill:#1e293b,stroke:#64748b,color:#94a3b8
Responsable del tratamiento de datos personales en este informe: Efraín Carreón Ortiz, Director General, Centrum Transgenia S.A.S. de C.V. (RFC: CTR1708039T5). Contacto: dev@transgenia.org.
Preguntas frecuentes
¿Cuál es la diferencia entre limpiar el malware y cerrar la brecha?
Limpiar el malware implica remover artefactos maliciosos del sistema de archivos y la base de datos. Cerrar la brecha implica corregir los vectores que permitieron la intrusión y los que siguen exponiendo datos: accesos sin autenticación, credenciales comprometidas, permisos incorrectos. Un sitio puede estar limpio y seguir teniendo la brecha abierta; este expediente lo documenta en tiempo real.
¿Qué son los plugins de carga obligatoria (mu-plugins) en WordPress?
WordPress carga automáticamente, antes de cualquier plugin normal, todos los archivos PHP en el directorio mu-plugins. No aparecen en el listado de plugins del panel de administración y no pueden desactivarse desde ahí. Cualquier archivo en ese directorio se ejecuta en cada petición.
¿Por qué puede dar cero detecciones un análisis antimalware sobre un sitio comprometido?
Las firmas de los motores antimalware identifican patrones conocidos de longitud suficiente. Cuando el atacante usa fragmentos mínimos como cargadores de 56 bytes, las piezas no coinciden con ninguna firma documentada. Cero detecciones no equivale a ausencia de malware: equivale a que los artefactos están por debajo del umbral de cualquier firma actualmente en uso.
¿Qué es phpass y por qué representa un riesgo cuando el volcado de base de datos está expuesto?
phpass es el esquema de hash de contraseñas usado por WordPress por defecto hasta versiones recientes. Utiliza MD5 con iteraciones. Con equipo de GPU moderno, un hash phpass puede crackearse en horas o minutos. Si el volcado SQL de un sitio WordPress es descargable sin autenticación, los hashes de todos los usuarios con contraseña registrada están expuestos y son crackeables.
¿Cuándo aplica la LFPDPPP en un incidente de seguridad web?
La Ley Federal de Protección de Datos Personales en Posesión de los Particulares aplica cuando datos personales de terceros han sido expuestos por una vulneración de seguridad. En este caso, el volcado SQL contenía datos de clientes de la empresa afectada. La obligación de notificación y el plazo aplicable dependen de si la exposición equivale a una vulneración en los términos del artículo 36 de la ley. La determinación requiere criterio legal, no solo técnico.
Sobre el autor y Transgenia
Efraín Carreón Ortiz es Director General de Transgenia. Transgenia ayuda a empresas mexicanas a implementar soluciones de inteligencia artificial gobernada y a fortalecer su postura de seguridad digital. Para consultas escribe a dev@transgenia.org.
Sigue leyendo
- Servicios de Transgenia
- Soluciones de IA para clínicas privadas
- Cómo Transgenia opera con agentes de IA gobernados
Fuentes
El material primario de este artículo es el expediente forense interno de Transgenia, 2026. Los hallazgos son mediciones directas sobre el sistema de archivos, la base de datos y el registro de errores de PHP del sitio analizado, complementados con experimentos de verificación en contenedor aislado. No se publican rutas absolutas, nombres de archivo reales, identificadores de hosting ni datos de contacto del cliente.
Para contexto técnico externo:
- PHP manual —
disable_functions: php.net/manual/en/ini.core.php#ini.disable-functions - Wrappers PHP
phar://: php.net/manual/en/wrappers.phar.php - WordPress must-use plugins: developer.wordpress.org/advanced-administration/plugins/mu-plugins/
- phpass: openwall.com/phpass/
- LFPDPPP, texto completo: diputados.gob.mx/LeyesBiblio/pdf/LFPDPPP.pdf
Este caso se publica con los datos identificadores del cliente suprimidos y con autorización del responsable del tratamiento. Responsable de la publicación: Efraín Carreón Ortiz, Director General, Transgenia — dev@transgenia.org.