Saltar al contenido
CVE-2026-115450 ·
Alta · 22 de octubre de 2026

Manipulación de precios en el flujo de pago de la plantilla de comercio electrónico a través de un identificador de precio de Stripe proporcionado por el cliente

S0
Secur0 CNA
CVE-2026-115450

Descripción

Control externo de un parámetro web que se presume inmutable (CWE-472) en el flujo de carrito y pago de MarcosCamara01 Ecommerce Template, en src/app/api/user/cart/route.ts y src/app/api/stripe/payment/route.ts. Un usuario autenticado sin privilegios especiales puede ser cobrado por el precio de un producto mientras realiza el pedido de otro.

POST /api/user/cart acepta dos campos independientes del cuerpo de la petición: variantId, que determina lo que registra el pedido, y stripeId, que determina lo que cobra Stripe. Ambos se almacenan en la fila del carrito y nada exige que el stripeId sea el que corresponde al variantId. La carga se valida en su forma, no en su correspondencia.

POST /api/stripe/payment construye los line items de Stripe Checkout a partir del valor almacenado, como { price: item.stripeId, quantity }, por lo que el importe cobrado es el de ese precio de Stripe. El webhook checkout.session.completed crea después los productos del pedido a partir del variantId de esa misma fila del carrito. El cobro y el pedido son por tanto funciones de dos campos distintos y pueden hacerse discrepar.

Todos los stripeId del catálogo son legibles por cualquier visitante, por lo que no hay ningún valor que adivinar. Emparejar el variantId del producto más caro con el stripeId del más barato produce un pedido del primero al precio del segundo, repetible sin límite.

El cargo resultante en Stripe es válido y coincide con el identificador de precio que recibió, de modo que no genera ninguna disputa de pago ni señal de fraude. La discrepancia sólo se aprecia comparando la variante registrada en cada pedido con el importe realmente cobrado.

Tipo de vulnerabilidad (CWE)

  • CWE-472: Control externo de un parámetro web que se presume inmutable

Versiones afectadas

Afectado desde el commit 2aa8ec53 del 16 de noviembre de 2025, que es donde el pago empezó a cobrar a partir del stripeId almacenado mientras el carrito aceptaba ese campo del cliente, y corregido en el commit e6b364cd del 31 de julio de 2026. Estado por defecto: no afectado.

El proyecto no publica versiones y el repositorio no tiene etiquetas de git, y el campo version de package.json se ha mantenido en 0.1.0, por lo que una revisión afectada sólo puede identificarse por commit. El fallo se registra en consecuencia como un rango de commits y no como un rango de versiones.

Puntuación (CVSS 4.0)

Alta (7.1)

CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N

Los privilegios requeridos son Bajos porque el atacante necesita una cuenta corriente en la tienda y nada más. La interacción del usuario es Ninguna porque el atacante actúa por sí solo contra la API. La confidencialidad del sistema vulnerable es Ninguna: el atacante no conoce nada que no estuviera ya publicado, ya que los identificadores de precio del catálogo son legibles por cualquier visitante, y esa legibilidad pública es lo que hace trivial el ataque, no una divulgación en sí misma. La integridad del sistema vulnerable es Alta porque la tienda registra y sirve un pedido que no ha sido pagado, y la pérdida económica derivada de un movimiento de dinero no autorizado se puntúa en integridad. La disponibilidad es Ninguna porque la tienda sigue operando; la pérdida recae sobre el margen del operador, no sobre el funcionamiento del servicio. Las métricas del sistema subsiguiente son Ninguna: Stripe es un sistema distinto pero se comporta correctamente en todo momento, cobrando exactamente el identificador de precio que recibe.

Solución

Actualizar a una revisión que incluya el commit e6b364cd del 31 de julio de 2026.

No existe ninguna configuración que lo impida en una revisión afectada, porque el identificador de precio se toma del cuerpo de la petición en cada añadido al carrito.

Parche

Commit e6b364cd. Añade un auxiliar buildLineItems() que lee el precio únicamente del variant.stripeId ya unido en la consulta, de modo que el importe cobrado y el pedido registrado derivan ambos del variantId y no pueden discrepar, y la ruta de pago delega en él. addToCartSchema ya no acepta stripeId: el servidor lo resuelve desde la variante mediante un nuevo findVariantById(), y valida además la size solicitada contra las tallas que la variante ofrece realmente. Los clientes que llamaban a la API se actualizaron para dejar de enviar el campo, y el nuevo auxiliar cuenta con pruebas unitarias aisladas.

Créditos

  • Darío Rivas - descubridor
  • Gerard Martinez Guillamon - descubridor
  • Cristian Fernández Cornejo - analista
  • Xoán M. Otero Jorge - analista
  • Secur0 CNA - coordinador
  • MarcosCamara01 - desarrollador de la corrección

Origen del descubrimiento: Interno

Registro oficial: CVE-2026-115450

Registros relacionados

  • CVE-2026-90896 - consulta de sesión de pago sin autenticación en la misma aplicación