Sep 19, 2026·9 min read·34 visits
A race condition in Paymenter allows authenticated users to trigger multiple credit refunds for a single service downgrade by making synchronized, concurrent requests to the Livewire downgrade component. This occurs due to lack of row locking during state validation.
A concurrent execution vulnerability (CWE-362) exists in the Paymenter webshop solution within the service downgrade execution path (doUpgrade). Authenticated customers can exploit this concurrency issue by sending concurrent HTTP requests to trigger multiple parallel executions of the refund process. Because the application checks for pending upgrades without database transactional isolation or exclusive row locks, attackers can generate multiple duplicate refunds to their account balance for a single downgrade action. This leads to arbitrary credit inflation on the platform.
Paymenter is an open-source webshop and management solution designed specifically for hosting services. It features an automated service upgrade and downgrade system handled by Livewire components in the frontend and Laravel controllers in the backend. When a customer elects to downgrade their active hosting package to a lower-cost tier or a different configuration, the application calculates the pro-rata value of the remaining billing cycle. If this calculation results in a negative price differential, the platform is designed to refund the variance to the user's account wallet as credits, provided the configuration setting settings.credits_on_downgrade is enabled.
This functionality introduces a critical attack surface because it deals directly with the manipulation and generation of currency value within the platform database. The logic governing this process is located in the doUpgrade method of the app/Livewire/Services/Upgrade.php component. Prior to version 1.5.7, this logic was executed in an asynchronous, non-transactional environment without exclusive database row locks. As a result, the application was susceptible to concurrency exploits, allowing a customer to manipulate their account credits by sending overlapping HTTP requests.
The vulnerability is classified under CWE-362 (Concurrent Execution using Shared Resource with Improper Synchronization) and specifically represents a Time-of-Check to Time-of-Use (TOCTOU) flaw. When multiple threads process concurrent downgrade actions for the same service, they check the database state simultaneously. Because no lock is placed on the parent service record or the related pending upgrade records, multiple threads see the downgrade as valid and proceed to increment the customer balance multiple times. This results in direct unauthorized manipulation of the application's credit balances.
The primary root cause of this vulnerability lies in the non-atomic "Check-Then-Act" programming pattern implemented in the original doUpgrade function. Under a standard PHP-FPM and web-server model, HTTP requests are handled concurrently across multiple separate process threads. When a user triggers a downgrade, the application performs a series of database queries to validate the request before updating the state. This verification process must be atomic to ensure that once a check is passed, no other thread can execute the same logic on the same resource.
In the vulnerable implementation, the application first loaded the service model and verified whether there were any active, pending upgrades associated with that service ID. If the database returned no pending upgrade records, the application assumed the request was safe and proceeded. It then saved a new ServiceUpgrade record to the database, calculated the refund amount, and incremented the customer's wallet balance using Eloquent's increment method. However, because these steps occurred outside of a database transaction and lacked pessimistic locking, the state of the database was not secured during the read-to-write interval.
When an attacker sends multiple requests concurrently, the following race sequence occurs. Request A and Request B reach the server at almost the same millisecond. Request A queries the database for pending upgrades and finds none. Request B then performs the exact same query and also finds none, as Request A has not yet written its transaction to the database. Both requests validate successfully. Request A inserts a ServiceUpgrade record and increments the user's wallet. Immediately after, Request B inserts a duplicate ServiceUpgrade record and increments the user's wallet again. The application fails to detect that the downgrade operation has already been executed, resulting in a double-refund scenario.
Analyzing the patch implemented in commit a42e7f8bafce054ad70de3a2c2ac94d13579f41b reveals how the concurrency window was eliminated. The developer introduced database transactions alongside pessimistic locking on both the service record and the user's credit balance. This ensures that only one request can access and modify these records at any given moment.
// Vulnerable read pattern:
// $this->service was fetched via standard Eloquent without locking.
// If concurrent requests were executed, both would proceed to update the state.
// Patched implementation utilizing transaction and pessimistic lock:
DB::beginTransaction();
try {
// Lock the service record for update to prevent concurrent reads/writes
$service = Service::where('id', $this->service->id)->lockForUpdate()->first();
// Validate upgrade state again under the lock
if ($service->upgrade()->where('status', ServiceUpgrade::STATUS_PENDING)->count() != 0) {
DB::rollBack();
$this->notify('This service is not upgradable.', 'error', true);
return $this->redirect(route('services.show', $this->service), true);
}
// ... [Plan validation and ServiceUpgrade model creation] ...
if ($price->price <= 0) {
(new ServiceUpgradeService)->handle($upgrade);
if (!config('settings.credits_on_downgrade', true)) {
DB::commit();
$this->notify('The upgrade has been completed.', 'success', true);
return $this->redirect(route('services.show', $service), true);
}
// Lock the user's wallet balance before executing increment
$user = User::where('id', Auth::id())->lockForUpdate()->first();
$credit = $user->credits()->where('currency_code', $price->currency->code)->first();
if ($credit) {
$credit->increment('amount', abs($price->price));
} else {
$user->credits()->create([
'currency_code' => $price->currency->code,
'amount' => abs($price->price),
]);
}
DB::commit();
// ... [Redirect and notification logic] ...
}
} catch (\Exception $e) {
DB::rollBack();
// ... [Exception handling] ...
}The introduction of lockForUpdate() is critical. When the database processes Service::where(...)->lockForUpdate(), it issues a SELECT ... FOR UPDATE query to the underlying SQL engine (e.g., InnoDB). This places an exclusive write lock on the matching row. If Request B attempts to execute the same query while Request A's transaction is open, the database engine suspends Request B's execution. Request B remains blocked until Request A completes with either a DB::commit() or DB::rollBack(). Once Request A commits, the lock is released, Request B resumes, and immediately encounters the newly created ServiceUpgrade::STATUS_PENDING record, triggering the validation failure and rolling back safely.
Exploitation of this vulnerability requires standard customer credentials on a Paymenter instance and an active, downgradable hosting service. The objective is to force the server's web worker threads to process multiple downgrade actions simultaneously. This bypasses the logic checking for pending upgrades and results in multiple credit refunds.
An attacker begins by identifying a service plan that can be downgraded to a cheaper tier, which calculates a negative cost differential (a refund). The attacker initiates the downgrade process in the user interface and captures the outgoing Livewire HTTP POST request using an intercepting proxy. This payload contains the necessary validation tokens, component identifiers, and action parameters targeting the doUpgrade method.
To achieve the precision timing necessary for a race condition, the attacker utilizes a multi-threaded execution tool or an HTTP/2-capable client. Using HTTP/2 single-packet compression techniques (such as the single-packet attack), the attacker queues several identical POST requests inside a single TCP frame. This minimizes networking latency jitter. When the target server receives the frame, it distributes the concurrent HTTP requests to its PHP process pool at the exact same moment. Due to the lack of row locks in the vulnerable version, the parallel threads complete their read operations simultaneously. Each thread confirms there is no pending upgrade, creates a duplicate upgrade record, and increments the account's credit balance. This generates a combined refund that is several times larger than the single downgrade's value.
The impact of exploiting CVE-2026-71537 is highly severe to the business integrity of the hosting provider. Because account credits are directly tied to financial transactions and can be used to purchase hosting services or licenses, an attacker can exploit this flaw to generate infinite store credit. This credit can then be used to drain the provider's actual computing or licensing resources, causing significant financial loss.
The vulnerability is assessed with a CVSS v3.1 score of 6.5. This reflects medium severity because it requires authenticated customer privileges (PR:L) and does not compromise system confidentiality (C:N) or availability (A:N). However, the integrity impact is rated high (I:H) due to the direct, unauthorized creation of currency value within the database. The scope remains unchanged (S:U) because the exploit operates entirely within the context of the Paymenter database and does not allow lateral movement to the underlying operating system or hypervisors.
Currently, this vulnerability is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog, and there is no evidence of active exploitation in the wild. The exploit complexity is low, as the tooling required to synchronize HTTP/2 requests is widely available. Any user with a valid account and a cheap active service can execute the exploit, making immediate remediation critical for operators running affected versions.
The primary and recommended mitigation for this vulnerability is to upgrade the Paymenter installation to version 1.5.7 or higher. This release integrates the transactional and pessimistic locking mechanisms within app/Livewire/Services/Upgrade.php that eliminate the race window. Operators should execute standard application update routines, which include running database migrations to ensure any schema adjustments are applied correctly.
If patching immediately is not possible, hosting providers can apply several temporary workarounds to mitigate the risk:
settings.credits_on_downgrade configuration option to false. This prevents the application from issuing credit refunds during downgrades, eliminating the financial risk. This option can be configured in the Paymenter administrative dashboard./livewire/message/services.upgrade) using a reverse proxy or Web Application Firewall (WAF). Restricting clients to a single request per second on this path prevents the high-frequency concurrent requests required to trigger the race condition.SERIALIZABLE. Note that this can introduce performance overhead and potential lock contention issues under high load, so it should be evaluated carefully in a staging environment first.Administrators concerned about potential exploitation of this vulnerability should perform a retro-active audit of their database tables. Because the exploit causes duplicate records to be created, detection can be achieved by querying the database for anomalous, duplicate service upgrade events.
Run the following SQL query against the Paymenter database to identify any instances where multiple pending or completed upgrade records exist for a single service:
SELECT service_id, COUNT(*) as upgrade_count, GROUP_CONCAT(id) as upgrade_ids
FROM service_upgrades
GROUP BY service_id
HAVING upgrade_count > 1;If the query returns records, administrators should compare the creation timestamps (created_at) of the duplicate upgrades. If multiple upgrades were created for the same service within milliseconds of each other, it indicates a high likelihood of exploitation.
Additionally, examine the user credit transaction logs. Compare the credit increments with the downgrade records. If a customer's balance was increased multiple times for a single downgrade transaction, the account has likely exploited this race condition. In such cases, the operator should suspend the affected customer accounts, audit their credit balances, and reverse any unauthorized credits before upgrading the application to version 1.5.7.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Paymenter Paymenter | < 1.5.7 | 1.5.7 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-362 |
| Attack Vector | Network (AV:N) |
| CVSS v3.1 Score | 6.5 |
| Impact | High Integrity (Unauthorized Credit Manipulation) |
| Exploit Status | None / Theoretical (No public PoC) |
| CISA KEV Status | Not Listed |
The application performs a check-then-act sequence across database reads and writes without transactional isolation or exclusive row locks.
CVE-2026-62374 (GHSA-f9j6-8p6x-r9j6) is a verification bypass vulnerability in the OpenWallet Foundation SD JWT JS library (@sd-jwt/core and @sd-jwt/decode versions 0.19.0 and earlier). Due to permissive UTF-8 decoding and the omission of mandatory reference checks during presentation verification, tampered or unreferenced disclosures are silently dropped rather than rejected. This deviates from RFC 9901 §7.1 requirements and allows attackers to execute silent claim-set reduction attacks against relying parties.
An interpretation conflict in Bytebase DBHub (< 0.22.6) allows attackers to bypass read-only SQL query restrictions when connected to MySQL or MariaDB instances. Because DBHub's custom SQL tokenizer treats any '--' sequence as a comment, whereas MySQL and MariaDB require trailing whitespace or a control character, crafted queries containing '--' immediately followed by non-whitespace characters allow stacked destructive SQL statements to execute on connections configured as read-only.
CVE-2026-118777 is a prototype pollution vulnerability (CWE-1321) in the Forge JavaScript cryptography and networking library (distributed on npm as node-forge) prior to version 1.4.1. The flaw resides in the forge.http client subsystem, where incoming HTTP cookie names and paths parsed from Set-Cookie response headers are stored directly into standard JavaScript objects without key validation or property isolation. By returning crafted Set-Cookie headers containing keys such as __proto__ or constructor, a malicious HTTP server can inject arbitrary properties into Object.prototype, attach attributes to the global Object constructor, or delete built-in prototype methods to cause an application-wide denial of service.
CVE-2026-118775 (GHSA-4642-5mcj-2rqw) is an integer overflow and validation flaw in the ASN.1 serialization module of Forge (node-forge) prior to version 1.4.1. The asn1.oidToDer() function fails to validate the range of the first two arcs of an Object Identifier (OID) and truncates the packed initial subidentifier to a single 8-bit integer. Applications that perform policy checks on untrusted OID strings before encoding them into Distinguished Encoding Rules (DER) format can be tricked into emitting DER bytes that correspond to a completely different, restricted OID.
CVE-2026-119194 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting versions 0.3.3 through 0.6.4. The flaw resides in the schemaless decoding routine ConstructedPayloadDecoderBase._decodeComponentsSchemaless used across BER, CER, and DER codecs. By sending an ASN.1 container populated with child components whose tag identifiers are separated by multiples of CPython's integer hash modulus (sys.hash_info.modulus), an unauthenticated attacker can force worst-case hash collisions in an unbounded Python set, degrading decoding performance from O(N) to O(N^2) and causing a Denial of Service (DoS).
CVE-2026-119195 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting all versions prior to 0.6.5. When decoding constructed strings or indefinite-length ANY types across BER, CER, and certain DER code paths, the payload decoders repeatedly concatenate incoming fragments onto an immutable bytes object using the += operator. Because Python bytes objects cannot be resized in-place, each fragment iteration allocates a new buffer and copies all previously accumulated bytes, resulting in quadratic O(N^2) time and memory copy complexity. An unauthenticated remote attacker can supply a highly fragmented ASN.1 stream to exhaust CPU cycles and stall application worker threads.