The anti-malware scan finished. The screen showed zero active threats. The team exhaled. Three weeks later, a routine analysis of the uploads folder revealed five backup files — database dumps included — downloadable from the production server without any authentication.
Short answer. Cleaning malware and closing the breach are two distinct jobs. The first can finish while the second remains completely open. This article documents both, including the five methodological errors our team made that kept them separate for weeks.
What Does a Compromised Site Do While the Team Cleans It?
The Database Lies; the mtimes Do Not
Any forensic analysis of a compromised WordPress site starts with one question: when did this happen? The answer the database offers is, in this case, unusable as a timeline.
Eight published entries declared dates earlier than others with a higher database identifier: they were created later but dated as if they were older. The same pattern appears in another table on the site, with four comments of consecutive identifiers distributed across two different years. It was not a timezone error: the date field was modified manually.
View Mermaid source
%%{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
The valid timeline for this site is that of the file system modification times (mtime). They are consistent with each other and with the only documented code execution record. It was not proven that the attacker could alter them; claiming they are incorruptible would exceed the evidence. What can be stated: they are the only consistent source available.
The activity window reconstructed by mtime runs from June 25 to August 26, 2026.
The July 3 Execution Chain
The only moment when code execution can be demonstrated occurred on July 3, 2026. A directory created less than ten minutes before contained four elements: the complete copy of a legitimate plugin, a hidden file, a PHP loader, and a 16-byte configuration file. The choice of a plugins directory as the container was not accidental: any PHP file in that location is loaded automatically with every WordPress request.
The chain: the loader included the hidden file, which invoked an internal function that attempted to open an operating system process with popen(). The hosting provider had that function disabled. The attempt failed with the error recorded verbatim:
[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), error type, and stack structure are from the original record. The exact timestamp is pending authorization to publish. Source: Transgenia internal forensic file, 2026.This record is the only proof of code execution in the entire incident. The canonical reading of the block is the hosting provider's list of disabled functions; there is no other evidence that contradicts that interpretation.
View Mermaid source
%%{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
The Content in Numbers
At the time of analysis, the site had 604 inventoried entries: 115 published, 483 in the trash, 4 drafts, and 2 WordPress autodrafts. Of the 115 published, four corresponded to the client's legitimate content: 3.5 percent of the total. The rest: 102 sports betting entries in nine languages, five casino entries, and four WordPress default template entries.
View Mermaid source
%%{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]
View Mermaid source
%%{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
The authorship pattern confirms that the legitimate administrator account was used to publish spam: 10 of 18 entries with an assigned author were published under that identity. Of the 431 registered users, 428 were fake subscribers with no associated entries. The site's 38 categories included 29 sports betting categories. Of the 108 comments, none came from a real visitor: four were WordPress default template entries and eight were internal order notes from e-commerce platforms from previous years.
Cleaning Is Necessary. It Is Not Enough.
View Mermaid source
%%{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
As of the 2026-09-18 cutoff, 25 of the 33 documented actions had been closed: 76 percent. The must-use plugins directory was clean. The second administrator account had been deleted. The WordPress core had been updated. Fifteen malware artifacts no longer existed in the file system.
What remained open was not minor.
The Gaps That Remain Open
Backup files downloadable without authentication. Five backup files were downloadable directly from the production server without any credentials. They had been documented for twenty days since they were identified. The largest exceeds 200 MB and dates from 2017; its contents were not opened. By size and format, the case file infers file system plus database.
The smaller SQL dump, approximately 12 MB, contained the password hashes of at least two user accounts in phpass format: MD5 with iterations, crackable with modern GPU hardware in hours. It also contained personal data belonging to third parties: clients of the affected company registered on the e-commerce platforms that used the site.
The current database, approximately 118 MB in size, returns HTTP 404 on direct request: it is on disk but is not accessible from the outside. Not all sensitive files have the same level of exposure.
Active spam content page. At the time of analysis, the spam gateway page returned HTTP 200 and served approximately 800 KB of content. As of 2026-09-19 it was verified as HTTP 404: that specific gap has already been closed.
File manager active via admin panel. The administration panel still had the file manager enabled, which allows editing any PHP file on the site from the browser.
The .htaccess That Hides Without Protecting
The uploads folder had a .htaccess file with the IndexIgnore * directive. This directive hides the directory listing when someone browses to that URL. It does not deny access. An attacker who knows the file name can download it directly.
View Mermaid source
%%{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 * hides the directory listing but does not deny access to individual files. The site's actual protection covered .php files and did not extend to .sql or .zip. The right column shows an example configuration that would have denied access; it is not a universal recipe. Source: Transgenia internal forensic file, 2026.The actual protection the directory had covered .php files — a Deny directive for PHP. It did not cover .sql or .zip. The security configuration was partially correct, and that partiality made it ineffective exactly where it mattered.
The Latent Loader: How Not to Label a Finding
The Persistence Cycle
The reason a file in mu-plugins is more dangerous than in any other directory is not its content: it is its position. WordPress loads all PHP files from that directory before processing any request, without exception and without the administration panel showing them.
View Mermaid source
%%{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() stopped the execution in this case; without it, the cycle would have completed. Source: Transgenia internal forensic file, 2026.The 56-Byte File
In the must-use plugins directory, a 56-byte file was found. Its content was a single instruction: a phar:// wrapper inside trim().
trim() does not trigger that wrapper. The phar:// wrapper in PHP is activated when a function that touches the file system — include, require, fopen, among others — receives a phar:// path. trim() processes a string and returns it; it does not touch the file system. This was verified in an isolated container before including the finding in the report.
The file is removed from the site regardless, for two reasons: it loads on every WordPress request by being in the must-load directory, and it does not appear in the plugins list in the administration panel. The exact reason for that invisibility in the panel was not established in the case file.
There is a second artifact of 522 bytes with three execution functions encoded in hexadecimal escapes. Hexadecimal encoding is an evasion technique: a plain-text search on the file will not find the function keywords.
The Anti-Malware Analysis
Before sealing the evidence container, 210 files were subjected to anti-malware analysis. The result was zero detections. The container was sealed with AES-256 encryption; subsequent analysis attempts on the sealed container returned password protection errors for all 288 registered entries. The zero-detection result corresponds to the 210 files reviewed before sealing.
The explanation is not the absence of malware: the 16-, 56-, and 522-byte fragments are below the length threshold of any known anti-malware signature. A signature engine cannot identify what it has no mapping for. No one on the team issued the diagnosis "there is no malware because the antivirus says there is none"; the case file records this explicitly because it is the reasoning the result invites.
How to Label It Correctly
The tempting headline was: An active PHAR loader was found in the must-use plugins directory.
The correct headline is: A PHAR loader was found one word away from being armed, in a directory that loads it on every request and makes it invisible to the panel.
The difference is not semantic: one describes an active threat that did not exist; the other correctly describes the real threat: latent, not active, but in a privileged position.
An AI Connector Granted More Access Than the Equivalent REST Credential
During the analysis, the WordPress REST API was blocked in edit mode: it returned error 401 even with a valid administrator credential. The block is a standard hardening measure for sites that do not need to expose that interface.
An artificial intelligence connector plugin installed on the site did not respect that hardening. Through that channel, the team was able to read all 431 users with their roles without any problem, using the same credentials the REST API rejected.
The lesson has two sides. The immediate one: a read vector was open where the operator believed it was closed. The structural one: installing a second connector to gain access would have added third-party code with few reviews to an already compromised site. Adding attack surface to save five clicks is a poor security operation.
Five Errors Plus One Sister Correction
E1 — Partial count reported as final count. The sweep of the uploads directory produced a manifest of exactly 4,000 lines. The last line of the manifest is the tool's cutoff marker, not a real file name. The sweep covered approximately one third of the directory. The honest conclusion: a leak was found in the reviewed portion; what is in the rest is unknown.
E1-sister — Installed does not equal active. The panel API returns plugins installed on the server. The source that answers what is active is the options table in the database. These are two distinct questions with distinct sources.
E2 — Attacker capability overestimated. The initial description of the 56-byte file characterized it as an active PHAR deserialization. The experiment in an isolated container demonstrated that it was not, as written. The report preserves the original statement alongside its correction, with a date. That makes it auditable: whoever reads the case file can follow the reasoning that led to the error and the evidence that refuted it.
E3 — Attacker artifact confused with own noise. Two PHP error records, on the same site. One documents the attacker's execution chain from July 3. The other was generated by the audit's own HTTP probe in a previous session. The only thing that distinguishes them: having recorded what one did oneself and when. Without that record, the second error would have appeared to be part of the attack and would have biased the analysis.
E4 — The audit blocked Transgenia's IP. For nineteen days, the team could not access the site from the office. It took six days to realize the problem was their own access, not the site. "The site is down" meant "our access is blocked."
Four independently sufficient causes in a single work session: repeated failed authentications, recursive directory traversal, probing of paths with a vulnerability scanner signature, and range requests on large files. The hosting operator shared the same public IP address as the audit team. The confirmation that "I can't see it either" ratified the block, not disproved it. The solution was to measure from an external point with no connection to the client.
E5 — Frozen homepage attributed to the cache plugin. The homepage showed an outdated version of the site. A GET and a POST to the same URL, in the same second, returned different responses: the origin was healthy. The outdated copy came from the hosting's intermediate layer, fed by a one-year cache header that a plugin had left written. One particular detail: the hosting's anti-malware web shield intercepts URLs containing query strings and returns a JavaScript challenge of approximately 12 KB. This invalidates any measurement with a cache-buster on the command line and breaks automated requests. If the measurement tool is being intercepted, what is being measured is the tool, not the site.
View Mermaid source
%%{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
Why counting the errors adds authority. A report where nothing went wrong is not a report without errors: it is a report without quality control. The adversarial round that refuted E2, the isolated container that decided it, the scope note that bounded E1, the attribution withdrawn in writing in E3: that is the method working. The client has an auditable case file, not a polished narrative.
Exportable rule. Before touching a compromised site, record your own footprint: outgoing IP address, time, type, and volume of requests. Notify the hosting provider before you begin. If you do not, you will spend part of the time investigating your own marks.
What Does This Report Not Prove?
View Mermaid source
%%{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
- The entry vector is not proven. The most plausible working hypothesis is compromised credential or unpatched vulnerable plugin. It is a hypothesis, not a documented certainty.
- Successful code execution is not proven. The only available record documents a failed attempt from July 3. There is no evidence of a successful execution session before or after.
- Data exfiltration is not proven. Exposure does not equal extraction. The backup files were accessible; there is no evidence that they were downloaded by third parties. This distinction is relevant for determining obligations under LFPDPPP (Mexico's Federal Personal Data Protection Law).
- The sweep of the uploads directory covered approximately one third. This is an estimate based on the proportion of the documented cutoff, not an exact count of the total files in the directory.
- One of the three paths of the 56-byte loader was not verified. The container verification covered two of the three possible execution paths; the third was not tested.
- The timeline of the injected content is not reconstructible. Only the timeline of the file system files, based on mtime, can be stated with internal consistency.
The Case Is Open as of 2026-09-18
Status as of 2026-09-18: 25 of 33 actions closed (76%). Three critical pending items with assigned dates and owners.
View Mermaid source
%%{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]
| Pending Action | Owner | Deadline |
|---|---|---|
| Close downloadable backup files without authentication (documented 20 days ago) | Client + Saurat Xiuhcoatl | 2026-09-19 |
| Force password change on all site accounts (phpass hashes exposed) | Client | 2026-09-22 |
| Confirm LFPDPPP notification closure: notification date and responsible party name | Client + legal team | Before removing draft: true |
Publishing this article with PENDING status is the correct decision for a practical reason: the breach exists independently of the article's status. Documenting the pending items with owner and date puts them in an external record with a timestamp. The article does not announce the incident: it bounds it and establishes visible responsibilities.
Applicable Regulatory Framework (LFPDPPP)
Article 36 of LFPDPPP (Mexico's Federal Personal Data Protection Law) establishes that the data controller must notify the data subjects when a significant security breach occurs. The downloadable SQL dump contained personal data belonging to clients of the affected company: names, contact information, and transaction records from previous years. The determination of whether this exposure constitutes a breach under the terms of Article 36 requires legal judgment. What is documented: the data existed, was accessible without authentication, and belongs to identifiable third parties.
View Mermaid source
%%{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
Data controller responsible for personal data in this report: Efraín Carreón Ortiz, General Director, Centrum Transgenia S.A.S. de C.V. (RFC: CTR1708039T5). Contact: dev@transgenia.org.
Frequently Asked Questions
What is the difference between cleaning malware and closing the breach?
Cleaning malware involves removing malicious artifacts from the file system and database. Closing the breach involves correcting the vectors that enabled the intrusion and those still exposing data: unauthenticated access, compromised credentials, incorrect permissions. A site can be clean and still have the breach open; this report documents that in real time.
What are must-use plugins (mu-plugins) in WordPress?
WordPress automatically loads, before any normal plugin, all PHP files in the mu-plugins directory. They do not appear in the plugin list in the admin panel and cannot be deactivated from there. Any file in that directory executes on every request.
Why can an anti-malware analysis return zero detections on a compromised site?
Anti-malware engine signatures identify known patterns of sufficient length. When the attacker uses minimal fragments like 56-byte loaders, the pieces do not match any documented signature. Zero detections does not equal absence of malware: it equals the artifacts being below the threshold of any currently active signature.
What is phpass and why does it represent a risk when the database dump is exposed?
phpass is the password hash scheme used by WordPress by default until recent versions. It uses MD5 with iterations. With modern GPU hardware, a phpass hash can be cracked in hours or minutes. If the SQL dump of a WordPress site is downloadable without authentication, the hashes of all registered users with a password are exposed and crackable.
When does LFPDPPP apply to a web security incident?
Mexico's Federal Personal Data Protection Law (LFPDPPP) applies when personal data belonging to third parties has been exposed through a security breach. In this case, the SQL dump contained data belonging to the affected company's clients. The notification obligation and applicable timeframe depend on whether the exposure constitutes a breach under Article 36 of the law. The determination requires legal judgment, not only technical judgment.
About the Author and Transgenia
Efraín Carreón Ortiz is General Director of Transgenia. Transgenia helps Mexican companies implement governed artificial intelligence solutions and strengthen their digital security posture. For inquiries write to dev@transgenia.org.
Keep Reading
- Transgenia Services
- AI Solutions for Private Clinics
- How Transgenia Operates with Governed AI Agents
Sources
The primary material for this article is Transgenia's internal forensic case file, 2026. The findings are direct measurements on the file system, database, and PHP error log of the analyzed site, supplemented by verification experiments in an isolated container. No absolute paths, real file names, hosting identifiers, or client contact data are published.
For external technical context:
- PHP manual —
disable_functions: php.net/manual/en/ini.core.php#ini.disable-functions - PHP
phar://wrappers: php.net/manual/en/wrappers.phar.php - WordPress must-use plugins: developer.wordpress.org/advanced-administration/plugins/mu-plugins/
- phpass: openwall.com/phpass/
- LFPDPPP, full text: diputados.gob.mx/LeyesBiblio/pdf/LFPDPPP.pdf
This case is published with the client's identifying information suppressed and with authorization from the data controller. Publication responsible party: Efraín Carreón Ortiz, General Director, Transgenia — dev@transgenia.org.