CVE-2025-15116
Executive Summary
CVE-2025-15116 is a race condition vulnerability in OpenCart versions up to and including 4.1.0.3, specifically within the Single-Use Coupon Handler. An attacker can exploit this flaw to reuse a single-use coupon multiple times by sending concurrent requests, bypassing the intended one-time-use restriction. The attack complexity is high, requiring precise timing, but a public proof-of-concept (PoC) exists. The vendor was contacted but did not respond, leaving the vulnerability unpatched. This report provides a comprehensive technical analysis, detection rules, and exploitation guidance.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
OpenCart is a PHP-based e-commerce platform. The vulnerability resides in the core coupon handling logic, not in any third-party package. However, administrators should verify the integrity of their OpenCart installation and ensure no malicious modifications have been made to the coupon-related files.
CVSS Breakdown & Threat Level
- CVSS Score: 3.7 (Low)
- Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N
- Attack Vector: Network
- Attack Complexity: High (requires precise timing)
- Privileges Required: None
- User Interaction: None
- Scope: Unchanged
- Confidentiality Impact: None
- Integrity Impact: Low (coupon misuse)
- Availability Impact: None
Threat Level: Low. The high complexity and low impact reduce the immediate risk, but the public PoC increases the likelihood of targeted attacks.
Deep Dive & Mechanics
The vulnerability is a classic race condition (CWE-362) in the coupon validation logic. When a user applies a single-use coupon, the system checks if the coupon has been used before. If not, it marks the coupon as used and applies the discount. However, if multiple requests are sent simultaneously, the check and mark operations are not atomic, allowing multiple requests to pass the check before any of them marks the coupon as used.
Affected Component: system/library/cart/coupon.php (or similar path depending on OpenCart version)
Vulnerable Code Flow:
- User submits coupon code.
- System queries database:
SELECT * FROM oc_coupon WHERE code = 'COUPON123' AND uses_total > uses_current. - If valid, system updates:
UPDATE oc_coupon SET uses_current = uses_current + 1 WHERE coupon_id = X. - Race condition: Two concurrent requests both pass step 2 before either completes step 3.
Vulnerable vs. Patched Code
Vulnerable Code (OpenCart <= 4.1.0.3):
// In coupon validation function
$coupon_query = $this->db->query("SELECT * FROM " . DB_PREFIX . "coupon WHERE code = '" . $this->db->escape($code) . "' AND (uses_total = '0' OR uses_current < uses_total) AND status = '1'");
if ($coupon_query->num_rows) {
// Apply discount
$this->db->query("UPDATE " . DB_PREFIX . "coupon SET uses_current = (uses_current + 1) WHERE coupon_id = '" . (int)$coupon_query->row['coupon_id'] . "'");
}
Patched Code (Hypothetical fix using database locks):
$this->db->query("SELECT uses_current, uses_total FROM " . DB_PREFIX . "coupon WHERE code = '" . $this->db->escape($code) . "' FOR UPDATE");
$coupon_query = $this->db->query("SELECT * FROM " . DB_PREFIX . "coupon WHERE code = '" . $this->db->escape($code) . "' AND (uses_total = '0' OR uses_current < uses_total) AND status = '1'");
if ($coupon_query->num_rows) {
$this->db->query("UPDATE " . DB_PREFIX . "coupon SET uses_current = (uses_current + 1) WHERE coupon_id = '" . (int)$coupon_query->row['coupon_id'] . "'");
}
// Commit transaction
Exploitation & Lab Setup
Red Team
Prerequisites:
- OpenCart 4.1.0.3 or earlier installed.
- A valid single-use coupon code.
- Ability to send concurrent HTTP requests (e.g., using
curl,Burp Suite, or custom script).
Exploit Steps:
- Obtain a single-use coupon code (e.g.,
WELCOME10). - Send multiple concurrent requests to the coupon apply endpoint (e.g.,
/index.php?route=checkout/cart/addor/index.php?route=api/coupon). - Use a tool like
curlwith parallel requests:
for i in {1..10}; do
curl -s -X POST "http://target.com/index.php?route=api/coupon" \
-d "coupon=WELCOME10" &
done
wait
- Verify that the coupon was applied multiple times by checking the cart total.
Lab Setup:
- Use Docker to spin up a vulnerable OpenCart instance:
docker run -d -p 8080:80 --name opencart-vuln opencart:4.1.0.3
- Access
http://localhost:8080and install OpenCart. - Create a single-use coupon via Admin > Marketing > Coupons.
Blue Team
Detection:
- Monitor for multiple rapid requests to coupon endpoints from the same IP.
- Look for anomalies in coupon usage counts (e.g.,
uses_currentexceedinguses_total). - Enable database query logging to detect concurrent SELECT/UPDATE patterns.
Mitigation:
- Implement database-level locking (e.g.,
SELECT ... FOR UPDATE). - Use application-level mutexes or queues for coupon redemption.
- Rate-limit coupon application attempts per user/IP.
Indicators of Compromise (IOCs)
- Network: Multiple concurrent POST requests to
/index.php?route=api/couponor/index.php?route=checkout/cart/addwith the same coupon code. - Database: Rows in
oc_couponwhereuses_current > uses_total. - Logs: Repeated entries in access logs showing the same coupon code applied within milliseconds.
- File: Modified
system/library/cart/coupon.php(if attacker patches the vulnerability).
Detection Engineering
YARA Rule
rule CVE_2025_15116_OpenCart_RaceCondition {
meta:
description = "Detects vulnerable OpenCart coupon handling code"
author = "Valters IT"
date = "2025-04-10"
cve = "CVE-2025-15116"
strings:
$vuln_func = "uses_current < uses_total" nocase
$vuln_query = "SELECT * FROM " nocase
$vuln_update = "UPDATE " nocase
condition:
$vuln_func and $vuln_query and $vuln_update
}
Sigma Rule
title: CVE-2025-15116 OpenCart Coupon Race Condition
id: 8a9b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d
status: experimental
description: Detects multiple rapid coupon application attempts
author: Valters IT
date: 2025-04-10
logsource:
category: webserver
product: apache
detection:
selection:
cs-method: 'POST'
c-uri|contains:
- '/index.php?route=api/coupon'
- '/index.php?route=checkout/cart/add'
cs-uri-query|contains: 'coupon='
condition: selection
timeframe: 1s
aggregation:
field: c-ip
condition: gt 5
falsepositives:
- Legitimate users with slow network retries
level: medium
Suricata Rule
alert http any any -> any any (msg:"CVE-2025-15116 OpenCart Coupon Race Condition"; flow:to_server; content:"POST"; http_method; content:"/index.php?route=api/coupon"; http_uri; content:"coupon="; http_client_body; threshold: type both, track by_src, count 5, seconds 1; sid:1000001; rev:1;)
Remediation & Mitigation
Immediate Actions:
- Disable single-use coupons until a patch is available.
- Implement rate limiting on coupon endpoints (e.g., max 1 request per second per IP).
- Add database-level locking to the coupon update query (requires code modification).
Long-term Fix:
- Apply a vendor patch when released.
- Upgrade to OpenCart 4.1.0.4 or later (if patched).
- Consider using a Web Application Firewall (WAF) to block rapid requests.
Workaround Code (if you can modify the source):
// In system/library/cart/coupon.php
$this->db->query("START TRANSACTION");
$coupon_query = $this->db->query("SELECT * FROM " . DB_PREFIX . "coupon WHERE code = '" . $this->db->escape($code) . "' AND (uses_total = '0' OR uses_current < uses_total) AND status = '1' FOR UPDATE");
if ($coupon_query->num_rows) {
$this->db->query("UPDATE " . DB_PREFIX . "coupon SET uses_current = (uses_current + 1) WHERE coupon_id = '" . (int)$coupon_query->row['coupon_id'] . "'");
}
$this->db->query("COMMIT");
Valters IT
Securing Digital Frontiers