Skip to content
KitploitKITPLOIT
HerramientasExploitsBlog
Log in
Enviar
HerramientasExploitsBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2025-67923 — JetEngine <= 3.7.7 — Cross-Site Scripting Almacenado No Autenticado a través de la API REST de CCT | Kitploit
Herramientas/GitHubGitHub/randomrobbiebf/cve-2025-67923
Análisis de VulnerabilidadesExplotaciónExplotación de Aplicaciones WebSeguridad WebSeguridad de APIs
GitHubrandomrobbiebf/cve-2025-67923

CVE-2025-67923

JetEngine <= 3.7.7 — Cross-Site Scripting Almacenado No Autenticado a través de la API REST de CCT

Ver Repositorio
19hace 6 mesesAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Informe de Explotación de CVE-2025-67923

JetEngine <= 3.7.7 — Cross-Site Scripting Almacenado No Autenticado a través de la API REST de CCT

Fecha: 11 de marzo de 2026 Puntuación CVSS: 7.1 (Alta) Vector CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L Plugin afectado: JetEngine <= 3.7.7 Slug del plugin: jet-engine Corregido en: 3.7.8 CVE: CVE-2025-67923 CWE: CWE-79 Investigador: Bonds (vía Patchstack) Reportado: 19 de octubre de 2025 Divulgado: enero de 2026 Versión de WordPress probada: Última


Resumen Ejecutivo

CVE-2025-67923 es una vulnerabilidad de Cross-Site Scripting Almacenado No Autenticado en el plugin de WordPress JetEngine que afecta a todas las versiones hasta la 3.7.7 inclusive. Un atacante no autenticado puede escribir HTML/JavaScript arbitrario en un campo de texto de un Custom Content Type (CCT) a través de la API REST pública, que luego se inyecta sin saneamiento en el DOM mediante un sumidero innerHTML de JavaScript en el widget Maps Listing cuando una víctima visita cualquier página que contenga ese widget.

El ataque no requiere autenticación ni privilegios. Solo requiere que:

  1. Exista una CCT de JetEngine con acceso de escritura a la API REST configurado como public.
  2. Un widget Maps Listing en cualquier página del front-end esté configurado para mostrar el campo de texto de esa CCT como etiqueta del marcador del mapa.

✅ VULNERABILIDAD CONFIRMADA — Payload de XSS Almacenado Escrito y Ejecutado

Impacto confirmado:

  • HTML/JS arbitrario no autenticado almacenado en la base de datos de CCT sin saneamiento
  • El XSS se ejecuta para cada visitante que carga cualquier página con el widget Maps Listing
  • Robo de cookies, secuestro de sesión, recolección de credenciales almacenadas, toma de control del administrador

Detalles de la Vulnerabilidad

Resumen Técnico

La vulnerabilidad es una combinación de dos debilidades independientes:

  1. Falta de saneamiento de entrada (almacenamiento): El método sanitize_field_value() del manejador de elementos CCT no tiene una ruta de saneamiento para los campos de tipo text. El caso default: solo convierte marcas de tiempo — el HTML sin procesar pasa y se persiste en la base de datos.

  2. Sumidero de XSS basado en DOM (renderizado): El widget Maps Listing lee el valor de campo almacenado mediante get_marker_label(), lo envuelve en htmlspecialchars(json_encode(...)) para el atributo HTML data-markers, y el JavaScript del front-end lee el atributo con getAttribute() (que decodifica las entidades HTML) y luego inserta markerData.label directamente mediante innerHTML — ejecutando cualquier HTML/JavaScript incrustado.

Componentes Afectados

ArchivoProblema
includes/modules/custom-content-types/inc/rest-api/public-controller.php:424create_item_permissions_check devuelve true cuando CCT rest_put_access = 'public' — no se requiere autenticación
includes/modules/custom-content-types/inc/item-handler.php:489Rama default: de sanitize_field_value() — sin saneamiento de HTML para campos de tipo text
includes/modules/maps-listings/inc/render.php:233get_marker_label() devuelve el valor meta sin procesar sin esc_html()
includes/modules/maps-listings/inc/render.php:247htmlspecialchars(json_encode($result)) — solo protege el límite del atributo, no la inyección por innerHTML
includes/modules/maps-listings/assets/js/frontend-maps.js:112pinData.content = general.marker.html.replace('_marker_label_', markerData.label) — etiqueta sin procesar insertada en la cadena HTML
includes/modules/maps-listings/assets/js/public/mapbox-maps.js:175el.innerHTML = data.content — sumidero de ejecución de XSS
includes/modules/maps-listings/assets/js/public/leaflet-maps.js:34contentHtml.innerHTML = content — sumidero de ejecución de XSS

Cadena de Explotación

Paso 1 — Escritura REST No Autenticada

El controlador REST público de CCT verifica los permisos mediante check_user_permissions():

// public-controller.php:370-376
public function check_user_permissions( $request, $context ) {
    $content_type = $this->get_content_type_from_request( $request );
    $cap = $content_type->get_arg( $context );

    if ( ! $cap || 'public' === $cap ) {
        return true;  // No authentication required
    } else {
        return current_user_can( $cap );
    }
}

public function create_item_permissions_check( $request ) {
    return $this->check_user_permissions( $request, 'rest_put_access' );
}

Cuando una CCT está configurada con rest_put_access = 'public' (una configuración admitida y documentada para formularios públicos), el endpoint es completamente no autenticado. Cualquier cliente HTTP puede hacer POST a /wp-json/jet-cct/{slug}.

Paso 2 — Almacenamiento sin Saneamiento

El manejador REST llama a $handler->update_item($params), que llega a sanitize_field_value():

// item-handler.php:489-562
public function sanitize_field_value( $value, $field ) {
    $type = isset( $field['type'] ) ? $field['type'] : false;

    switch ( $type ) {
        case 'repeater':    // sanitizes sub-fields
            // ...
        case 'checkbox':    // handles boolean arrays
        case 'checkbox-raw':
            // ...
        case 'media':
        case 'gallery':     // sanitizes media JSON
            // ...
        case 'wysiwyg':
            $value = jet_engine_sanitize_wysiwyg( $value );  // sanitized
            break;

        default:
            // TEXT TYPE FALLS HERE — only timestamp conversion, NO HTML sanitization
            $value = $this->factory->maybe_to_timestamp( $value, $field );
    }

    return $value;
}

Un campo de tipo text cae en la rama default:. maybe_to_timestamp() devuelve el valor sin cambios para cadenas que no son fechas. El payload XSS `` se almacena tal cual.

Paso 3 — Etiqueta Recuperada sin Escapado

Cuando el widget Maps Listing se renderiza, get_marker_label() lee el campo:

// render.php:479-535
public function get_marker_label( $post = null, $settings = array() ) {

    // ...
    switch ( $label_type ) {
        case 'meta_field':
            $field = $settings['marker_label_field'];
            if ( $field ) {
                $result = jet_engine()->listings->data->get_meta( $field, $post );
                // No esc_html() here — raw value returned
            }
            break;
    }

    return $result;  // Returns ""
}

El valor devuelto se coloca en el array de datos del marcador:

// render.php:231-237
$result[] = array(
    'id'        => $post_id,
    'latLang'   => $latlang,
    'label'     => $this->get_marker_label( $post, $settings ),  // raw XSS payload
    // ...
);

Paso 4 — La Codificación de Atributos No Previene el XSS por innerHTML

El array de marcadores se codifica para un atributo HTML:

// render.php:247
return htmlspecialchars( json_encode( $result ) );

json_encode serializa `` como la cadena "". Luego, htmlspecialchars codifica en HTML todo el JSON, produciendo:

[{...,&quot;label&quot;:&quot;&lt;img src=x onerror=alert(1)&gt;&quot;,...}]

Esto se escribe en el atributo HTML data-markers. La codificación solo protege el límite del atributo. Cuando JavaScript lee el atributo, el navegador decodifica las entidades HTML, restaurando los caracteres originales:

// Browser automatically decodes entities when reading via dataset / getAttribute
const markers = JSON.parse(el.dataset.markers);
// markers[0].label === ''

Paso 5 — Inyección por innerHTML (El XSS se Ejecuta)

En frontend-maps.js, la cadena de etiqueta sin procesar se inserta directamente en una cadena de contenido HTML:

Descargar herramienta