
Эксплойт proof-of-concept и лабораторная работа для обхода оплаты через reward points плюс Free Checkout в OpenCart 4.1.0.4, позволяющего аутентифицированному покупателю сохранять баллы и недоплачивать за заказы.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · opencart-reward-free-checkout
OpenCart 4.1.0.4 - OpenCart Ltd
CVE-2025-15116 — это гонка купонов, исправленная в 4.1.0.3. В 4.1.0.4 coupon.save сбрасывает payment_method при изменении купона. reward.save — нет. Начислите достаточно баллов, чтобы getTotals стал <= 0.00, выберите Free Checkout, затем обнулите баллы. Confirm перезаписывает ожидающую строку на каталожную цену. free_checkout.confirm проверяет только код способа оплаты в сессии.
Авторизованный покупатель с бонусными баллами может оформить каталожный SKU как Free Checkout и сохранить баллы.
| ID | CVE пока нет |
| CWE | CWE-840, CWE-863 |
| CVSS | High: 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N |
| Продукт | OpenCart |
| Затронуто | по 4.1.0.4 reward + Free Checkout |
| Аутентификация | авторизованный покупатель (гостевая корзина — это не данная уязвимость) |
| Лицензия | GNU Affero GPL v3.0 |
| Лаборатория | только 127.0.0.1 |
Войти, положить в корзину SKU, покрытый баллами, применить достаточно вознаграждения, чтобы появился Free Checkout, сохранить этот способ, затем вызвать reward.save с 0. Второй confirm записывает каталожную сумму. free_checkout.confirm переводит заказ в оплаченный. Баллы при этом не списываются.
Shell они не получают. Гостевое оформление — это не данная уязвимость: reward требует покупателя. Free Checkout только отображается при total <= 0. Ошибка в том, что обнуление баллов не убирает его из списка, а confirm больше не смотрит на сумму.
Я прочитал reward.save, затем coupon.save, затем getMethods (total <= 0.00), затем editOrder при статусе 0, затем free_checkout.confirm. В 4.1.0.4 reward=0 удаляет session.reward. Он не трогает payment_method. Купон после усиления в 15116 — трогает.
Первый клиент, который это увидит, применит 100 баллов, выберет Free Checkout, подтвердит один раз и получит честный заказ с нулевой суммой. Это фича, а не баг.
Уже зафиксированные ложные пути: coupon.save вместо reward.save (этот путь сбрасывает способ оплаты); гостевое оформление; товар с points=0 (Free Checkout никогда не отображается); оставление reward в сессии до free_checkout.confirm (честный бесплатный заказ, баллы должны списываться); просмотр только order_status_id=0 (ожидание — это не оплата). Оракул — статус 1, сумма 100.00, оплата free_checkout, списание 0.
Лаборатория: вход покупателя. Добавить в корзину 36 (iPod Nano, points=100). reward.save 100. Список способов оплаты показывает Free Checkout. Сохранить его. Confirm записывает заказ 3 на 0. reward.save 0. Confirm перезаписывает заказ 4 на 100.00. free_checkout.confirm. Seed убирает налог и доставку, чтобы reward мог точно обнулить getTotals. Это не баг. Реалистичный случай — каталожный SKU, баллы которого покрывают строку. У покупателя всё ещё остаётся 1000.
cd lab
./run.sh
Цель — только http://127.0.0.1:18108. Сначала поместите OpenCart 4.1.0.4 upload/ в lab/www. Этого дерева нет в данном репозитории.
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
Сделать unset для payment_method в reward.save так же, как это уже делает coupon.save, и заставить free_checkout.confirm отклонять total > 0.
reward.php · coupon.php · free_checkout.php controller · free_checkout.php model · confirm.phpGNU Affero GPL v3.0. См. LICENSE. Только лаборатория на loopback. Без гарантий.