Un solo POST a un formulario y el servidor ejecuta tu código
Un solo POST a un formulario. Eso es todo lo que separa a un visitante anónimo de ejecutar código en tu servidor. La petición que aparece a continuación resume el fallo, y ya se está enviando contra sitios en producción. CVE-2026-85097 es una vulnerabilidad de subida arbitraria de archivos sin autenticación en Bricksforge, una extensión muy utilizada del constructor web Bricks. Afecta a todas las versiones hasta la 3.1.8.9, incluida, y está corregida a partir de la 3.1.8.10. [1][2] Wordfence, que asignó el identificador CVE, le otorga una puntuación de 9,8. Patchstack, que detectó su explotación, la califica con un 10,0. [1][2]
Bricksforge recibió el aviso el 3 de septiembre y los detalles se hicieron públicos el 7 de octubre. [2] Patchstack observó el primer intento de explotación ese mismo día, a las 21:47 UTC, procedente de una infraestructura automatizada que claramente estaba a la espera. [1] No he reproducido el ataque contra una instalación real. Este análisis se basa en el artículo de Patchstack y el código que publicó, el aviso de Wordfence, el historial de cambios del fabricante y la documentación de WordPress, consultados el 12 de octubre de 2026. [1][2][3][4] Lo llamativo no es que una subida de archivos acabase permitiendo ejecutar código. Es que el plugin comprobaba cuidadosamente el archivo y, aun así, podía ser comprometido.
POST /wp-json/bricksforge/v1/form_submit HTTP/1.1
Host: victim.example
Content-Type: application/json
{
"temporaryFileUploads": [
{
"field": "form-field-file",
"file": {
"file": "/var/www/.../uploads/bricksforge/tmp/img.gif", // real GIF, passed the MIME check
"url": "https://victim.example/wp-content/uploads/shell.php", // attacker names the destination
"type": "image/gif"
}
}
]
}El servidor comprobó los bytes. El atacante eligió el nombre
Así funcionaba el diseño en el que confiaba Bricksforge. Cuando un visitante adjunta un archivo, el plugin lo guarda en una carpeta temporal y comprueba allí su tipo MIME, correctamente y antes de seguir. Un GIF debe ser realmente un GIF, y esa comprobación funciona. Después, los desarrolladores hicieron algo que parece inofensivo: al enviar definitivamente el formulario, dejaron que el navegador indicara al servidor de qué archivo se trataba y dónde debía terminar.
Ahí está el error, y reducirlo a que «no se valida el campo url» se queda corto. El plugin validaba una propiedad del archivo, su contenido, a partir de un campo. Después obtenía el nombre y la extensión definitivos de otro campo, controlado por el atacante y que nunca se volvía a validar. Lo que se comprobaba y lo que determinaba la acción posterior no eran lo mismo. Los bytes de un GIF no indican si el archivo que vas a escribir se llamará shell.php. En un servidor Apache con mod_php, es la extensión .php, y no la cabecera de la imagen, la que determina si el servidor lo ejecutará.
Una imagen válida copiada a un archivo ejecutable
Veamos las dos fases. Primero, la subida. El atacante solicita un nonce a un endpoint accesible sin autenticación y después sube un archivo políglota: un GIF válido que también contiene una línea de PHP. Como es una imagen auténtica, mime_content_type devuelve image/gif y el archivo llega intacto al directorio temporal. [1] Hasta aquí, el plugin ha hecho las cosas bien. El archivo guardado en disco es realmente una imagen.
Después se envía el formulario, y entran en juego los metadatos. El JSON enviado contiene un objeto file con dos campos decisivos: file, la ruta en el servidor al archivo validado, y url, que indica su destino. El código siguiente resume la ruta vulnerable a partir del publicado por Patchstack. [1] El nombre del archivo de destino se extrae del campo url, se pasa por basename y se utiliza para copiar allí los bytes ya validados.
Fíjate en lo que hace copy(). Toma el GIF real del directorio temporal y escribe sus bytes en una ruta elegida por el atacante, terminada en .php y situada dentro de la raíz web. La cabecera de la imagen hace que siga siendo un GIF válido; el PHP incrustado es lo que ejecutará el servidor cuando alguien solicite esa URL por primera vez. [1] La comprobación MIME nunca pudo impedirlo, porque respondía a una pregunta distinta. Demostraba que el archivo era una imagen, pero no que se fuese a almacenar ni servir como tal.
// Bricksforge Pro Forms (condensed from the vulnerable path)
$file_path = $file_data['file']; // server path of the file that was MIME-checked
$file_url = $file_data['url']; // client-supplied, never re-validated
$attachment['guid'] = $file_url; // destination comes from the URL
$desired_filename = basename($attachment['guid']); // ...so from the attacker
$destination_path = $upload_path . '/' . $desired_filename;
copy($file_path, $destination_path); // GIF bytes written to an attacker-named .phpPor qué «nuestros formularios no permiten subir archivos» no te protege
Esta es la parte que debería preocuparte aunque creas que tu sitio no está expuesto. Podrías pensar que el problema solo afecta a los formularios que admiten adjuntos. El propio historial de cambios de Bricksforge indica lo contrario: afecta a cualquier sitio que tenga Pro Forms activo, incluso a formularios sin ningún campo de subida de archivos. [3] La explicación está en el código. La lista de archivos que se procesarán no se obtiene de una subida multipart real, sino del valor temporaryFileUploads incluido en el cuerpo JSON, que el atacante puede construir desde cero. [1] Basta con un simple formulario de contacto de texto.
Y el nonce no es la barrera que aparenta ser. Bricksforge expone un endpoint, bricksforge_regenerate_nonce, que entrega un nonce nuevo a cualquier visitante anónimo, lo que ya anula su utilidad como barrera. Incluso sin ese endpoint, WordPress genera el mismo nonce para todos los usuarios que no han iniciado sesión, porque comparten el ID de usuario 0. [4] Un nonce no demuestra que la persona que envía la petición sea de confianza. La documentación de WordPress es explícita: nunca debe utilizarse un nonce como mecanismo de autenticación, autorización o control de acceso. [4] Este controlador se apoyaba precisamente en él para eso.
// The file list is read from the JSON body, not from a real multipart upload:
$file_uploads = isset($form_data['temporaryFileUploads'])
? json_decode($form_data['temporaryFileUploads'], true) // attacker-supplied
: [];
$merged_files = array_merge($file_uploads, $request->get_file_params());
// The gate in front of it:
// GET bricksforge_regenerate_nonce -> hands any anonymous visitor a nonce
// WordPress gives every logged-out user the SAME nonce (user ID 0)
// A nonce is CSRF hygiene. It was never authentication.La corrección devuelve al servidor el control del nombre de archivo
La corrección recupera una regla básica: es el servidor, no el cliente, quien decide dónde se guarda un archivo subido y cómo se llama. Patchstack y el aviso de Wordfence identifican la versión 3.1.8.10 como la primera corregida. [1][2] El historial de cambios de Bricksforge incluye la corrección de la subida de archivos en la 4.0.1, publicada el 8 de octubre, e indica que los detalles completos se comunicarían después de la divulgación. [3] Actualiza a la versión corregida que corresponda a tu rama y licencia; para la rama 3.x, no bajes de la 3.1.8.10.
El principio de seguridad va más allá de este plugin. El nombre de un archivo almacenado es una decisión de seguridad y debe controlarlo el servidor: genera un nombre aleatorio, limita la extensión mediante una lista de permitidas y guarda el archivo fuera de la raíz web o en un directorio donde el servidor nunca ejecute PHP. El bloque siguiente ilustra el principio; no es el diff exacto del fabricante. Bricksforge presenta, además, un riesgo particular al actualizar: es un plugin comercial, no distribuido en WordPress.org, y sus actualizaciones dependen de la licencia. [3] Una licencia caducada puede dejarte silenciosamente en una versión vulnerable mientras el panel de administración parece estar en orden. «Lo compramos una vez» no significa «estamos protegidos con el parche».
// The fix, as a principle: the SERVER owns the name and extension, never the client.
$ext = 'bin'; // or an allowlisted image extension
$stored = wp_generate_uuid4() . '.' . $ext; // random name the attacker can't choose
$destination = $upload_path . '/' . $stored;
copy($validated_tmp_file, $destination);
// And decide execution by WRITE location, not by MIME: store outside the web root,
// or in a directory where the server is configured never to run PHP.Cómo buscaría el mismo fallo en código ajeno a Bricksforge
No se trata de un caso aislado, sino de una clase de errores anterior a WordPress: validar una propiedad y actuar según otra. Si un controlador de subida comprueba el contenido de un archivo pero permite que los datos de la petición determinen su nombre, extensión o directorio, aparece la misma vulnerabilidad, ya sea en PHP, Node o Java. Cuando reviso una subida de archivos, no empiezo por la comprobación MIME de la que todos presumen. Empiezo por la operación de escritura.
Localizo todos los puntos donde se escriben bytes en disco y sigo el flujo hacia atrás para averiguar de dónde sale el nombre de destino. El grep siguiente permite encontrar candidatos; el verdadero trabajo consiste en examinar cada uno. Para cada escritura me hago una pregunta, la misma que habría permitido detectar CVE-2026-85097 en una revisión: ¿la propiedad que has validado determina realmente lo que puede ejecutarse? Si compruebas los bytes, pero un campo independiente controlado por el cliente da nombre al archivo, la validación es poco más que decorativa.
# Smell to hunt in any upload handler:
# the stored filename, extension or directory derived from request input.
rg -n "basename\(|get_param|\$_POST|\$_REQUEST|json_decode" --glob '*.php'
# Then read each write sink by hand: move_uploaded_file, copy, file_put_contents, rename.
# The one question that confirms the bug:
# does the property you VALIDATED (the bytes, the MIME) decide the property
# that gets EXECUTED (the extension, the directory)?
# If a separate, client-controlled field names the sink, the check is decoration.¿Te afecta de verdad? Solo si utilizas Bricksforge, pero entonces sí
Conviene poner las cifras en contexto. Según Patchstack, Bricksforge cuenta con unas 6.000 instalaciones activas, así que no hablamos de un incidente que afecte a todo Internet. [1] Si no utilizas Bricksforge, puedes cerrar esta pestaña. Si lo utilizas, el riesgo no es hipotético. Patchstack registró intentos de explotación desde 63 direcciones IP de origen distintas a las pocas horas de la divulgación. En el 95 % de las peticiones maliciosas, la carga PHP se hacía pasar por un GIF o un PNG; algunos grupos probaron decenas de variantes de extensiones para eludir filtros simples. [1]
Merece la pena examinar esas técnicas de evasión porque indican qué buscar en los registros. Los atacantes probaron extensiones PHP alternativas como .php7 y .phtml, variaciones de mayúsculas y minúsculas como .PHp y representaciones codificadas para URL como %2ephp; todas apuntaban al mismo copy(). [1] Los archivos que intentaban dejar seguían patrones como login_admin_*.php dentro del directorio de subidas, incluida la carpeta temporal de Bricksforge. [1] A 12 de octubre, el CVE aún no figuraba en el catálogo de vulnerabilidades explotadas conocidas (KEV) de CISA y su puntuación EPSS era inferior al uno por ciento, pero eso no significa que no hubiera actividad. [2] Significa que era pronto. KEV es un indicador rezagado; los honeypots permiten detectar antes lo que ocurre, y ya estaban registrando ataques.
Para que esto constituya una emergencia en tu caso deben darse tres condiciones: usas Bricksforge, ejecutas una versión anterior a la 3.1.8.10 y el sitio es accesible desde Internet. Si se cumplen las tres, instalar el parche es el primer paso, no el último: un archivo PHP que se podía escribir quizá ya haya sido escrito y ejecutado. Como responsable del sitio, la pregunta no es solo «¿hemos actualizado?», sino también «¿alguien llegó al endpoint form_submit con una carga de archivo antes de que lo hiciéramos?». La respuesta está en los registros y en el directorio de subidas, no en la pantalla de plugins.
Qué hacer antes de cerrar esta pestaña
Este es el orden que seguiría. Primero, aplica el parche: la ventana de explotación se mide en horas, no en semanas. [1] Pero, si una versión vulnerable estuvo expuesta a Internet, da por hecho que alguien pudo intentarlo y compruébalo antes de declarar resuelto el incidente. Un parche impide la siguiente subida maliciosa; no elimina una shell que alguien instaló la semana pasada.
La lista siguiente es concreta. En los registros, busca la señal que el ataque difícilmente puede evitar: una petición al endpoint de envío de formularios con temporaryFileUploads en el cuerpo, donde la ruta del campo file apunta a una imagen, pero el campo url termina en una extensión PHP. Esa discrepancia es la huella del fallo y resulta difícil que aparezca por accidente.
Patch and verify (in this order):
[ ] Find every site running Bricksforge; confirm the version actually LOADED.
[ ] Update to 3.1.8.10+ (3.x branch) or 4.0.1+. Bricksforge is license-gated and
not on WordPress.org, so a lapsed license can silently block the update.
[ ] Cannot update now? Block at the WAF / server: POST to
/wp-json/bricksforge/v1/form_submit and the admin-ajax action
bricksforge_form_submit carrying a temporaryFileUploads body.
If a vulnerable version faced the internet, assume compromise and hunt:
[ ] Grep uploads for unexpected .php, especially
/wp-content/uploads/bricksforge/tmp/ and names like login_admin_*.php.
[ ] Audit for hidden admin accounts by querying the database directly,
and review the must-use (mu-plugins) directory.
[ ] In access logs, look for form_submit requests whose file path is an image
but whose url field ends in a PHP extension.
[ ] Rotate secrets reachable from PHP: database, API keys, SMTP.Lo que nunca demuestra «validamos los archivos subidos»
CVE-2026-85097 deja una lección clara: «validamos los archivos subidos» no responde a «¿es segura la subida de archivos?». El plugin comprobaba lo que era fácil de comprobar y confiaba en lo que realmente importaba. En esa brecha entre la propiedad que validas y la que determina la ejecución se esconden muchas vulnerabilidades de subida de archivos. No la detectarás mirando únicamente la versión del software ni la marca verde de un escáner. Hay que seguir el recorrido completo, desde la petición hasta la escritura en disco.
Rastrear ese recorrido, desde una petición anónima a través del framework hasta la línea exacta que da nombre a un archivo en disco, es una parte fundamental de una evaluación de seguridad de aplicaciones web. Y ese patrón de «validar una cosa y actuar según otra» lo busco también en código que no tiene nada que ver con WordPress. Si tu aplicación utiliza formularios, subidas de archivos o cualquier proceso que convierta datos externos en archivos del servidor, ese es el límite de confianza que examinaría. Cuéntame qué estás desarrollando y definimos el alcance.
Fuentes
¿Necesita que se compruebe esto en su propio producto?
Pentest de aplicaciones web y API¿Quiere aprender a encontrar vulnerabilidades como esta por su cuenta?
Curso individual de pentesting de aplicaciones web