
Prueba de concepto de exploit y laboratorio para un bypass de pago de OpenCart 4.1.0.4 mediante puntos de recompensa más Free Checkout, que permite a un cliente autenticado conservar los puntos y pagar de menos en los pedidos.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · opencart-reward-free-checkout
OpenCart 4.1.0.4 - OpenCart Ltd
CVE-2025-15116 era una carrera de cupones hasta la 4.1.0.3. En la 4.1.0.4, coupon.save elimina payment_method cuando el cupón cambia. reward.save no lo hace. Aplica suficientes puntos para que getTotals sea <= 0.00, elige Free Checkout, luego borra los puntos. Confirm reescribe la fila pendiente al precio de catálogo. free_checkout.confirm solo verifica el código de pago de la sesión.
Un cliente con sesión iniciada y puntos de recompensa puede enviar un SKU de catálogo como Free Checkout y quedarse con los puntos.
| ID | sin CVE aún |
| CWE | CWE-840, CWE-863 |
| CVSS | Alto: 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N |
| Producto | OpenCart |
| Afectado | hasta 4.1.0.4 reward + Free Checkout |
| Auth | cliente autenticado (el carrito de invitado no es este bug) |
| Licencia | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1 |
Inicia sesión, pone un SKU cubierto por puntos en el carrito, aplica suficientes recompensas para que Free Checkout aparezca en la lista, guarda ese método, luego reward.save con 0. El segundo confirm escribe el total de catálogo. free_checkout.confirm promueve el pedido a pagado. Los puntos nunca se debitan.
No obtienen una shell. El checkout de invitado no es este bug: reward necesita un cliente. Free Checkout solo aparece en la lista con total <= 0. El bug es que borrar los puntos no lo quita de la lista, y confirm no vuelve a mirar el total.
Leí reward.save, luego coupon.save, luego getMethods (total <= 0.00), luego editOrder mientras el estado es 0, luego free_checkout.confirm. En la 4.1.0.4, reward=0 elimina session.reward. No toca payment_method. El cupón, tras el endurecimiento de la 15116, sí lo hace.
El primer cliente que mire esto aplicará 100 puntos, elegirá Free Checkout, confirmará una vez y obtendrá un pedido honesto con total cero. Eso es la funcionalidad, no el bug.
Caminos equivocados ya registrados: coupon.save en lugar de reward.save (esa ruta elimina el método); checkout de invitado; producto con points=0 (Free Checkout nunca aparece en la lista); dejar reward en la sesión a través de free_checkout.confirm (pedido gratis honesto, los puntos deberían debitarse); mirar solo order_status_id=0 (pendiente no es pagado). El oráculo es estado 1, total 100.00, pago free_checkout, débito 0.
Lab: inicio de sesión de cliente. Añadir al carrito 36 (iPod Nano, points=100). reward.save 100. Los métodos de pago listan Free Checkout. Guárdalo. Confirm escribe el pedido 3 a 0. reward.save 0. Confirm reescribe el pedido 4 a 100.00. free_checkout.confirm. El seed elimina impuestos y envío para que reward pueda poner getTotals exactamente a cero. Eso no es el bug. Un SKU de catálogo cuyos puntos cubren la línea es el caso realista. El cliente todavía tiene los 1000.
cd lab
./run.sh
Objetivo solo http://127.0.0.1:18108. Coloca primero el upload/ de OpenCart 4.1.0.4 en lab/www. Ese árbol no está en este repo.
SUCCESS OpenCart reward + Free Checkout underpay
IOC order-after-reward 3 total=0.0000 status=0 free_checkout.free_checkout
IOC order-after-clear 4 total=100.0000 status=0 free_checkout.free_checkout
IOC free_checkout.confirm redirect checkout/success
IOC order-final 4 total=100.0000 status=1 debit=0 balance=1000
Hacer unset de payment_method en reward.save tal como ya lo hace coupon.save, y hacer que free_checkout.confirm rechace total > 0.
reward.php · coupon.php · free_checkout.php controller · free_checkout.php model · confirm.phpGNU Affero GPL v3.0. Ver LICENSE. Solo lab en loopback. Sin garantía.