Overview

two wallos cves from the same trust mistake

June 5, 2026
4 min read

TL;DR

I picked up two CVEs in Wallos:

  • CVE-2026-50198
  • CVE-2026-50199

Neither of them was some huge flashy bug. No RCE, no auth bypass, no full account takeover.

But I still liked both of them because they came from the same kind of mistake: user-controlled state was accepted in one place, then trusted later without being tied back to the right user.

That kind of bug is easy to underestimate until you actually trace what the application does with the foreign reference.

The two bugs

CVE-2026-50198

This one was published as:

Cross-user subscription cost inference via replacement_subscription_id

The issue was in how Wallos handled replacement_subscription_id on inactive subscriptions.

An authenticated normal user could edit their own inactive subscription and point replacement_subscription_id at another user’s subscription ID. The write was accepted. Later, the stats logic dereferenced that foreign ID without scoping the lookup back to the current user.

So this was not direct object disclosure in the usual sense. The app did not suddenly return the victim subscription row in full.

What it did leak was still real: the attacker could infer another user’s monthly-normalized subscription cost by watching how their own stats changed after swapping in victim subscription IDs.

That detail mattered because the obvious read path had already been blocked.

According to the advisory:

  • direct cross-user subscription reads were denied
  • direct writes against the victim subscription row were not confirmed
  • the leak only showed up after the attacker stored a foreign replacement_subscription_id in their own record and waited for the stats logic to consume it

So the bug lived in the gap between “write accepted” and “foreign reference trusted later.”

The advisory was specific enough to show the important lookup boundary:

SELECT price, currency_id, cycle, frequency
FROM subscriptions
WHERE id = :replacementSubscriptionId

And just as important, it called out the missing constraint:

AND user_id = :userId

That missing user scope is what turned an otherwise normal reference lookup into a cross-user leak.

Why it mattered

The confirmed impact was limited, but not fake.

The attacker could infer financial metadata from another user’s subscriptions without direct read access to that subscription object. That is enough to cross a real tenant boundary, even if the leak is derived rather than raw.

The public advisory lists it as:

  • CVE: CVE-2026-50198
  • Severity: Moderate
  • CVSS: 4.3
  • Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N
  • Affected: 4.9.0
  • Patched: 4.9.1

The write path in the advisory also made the exploit shape easy to follow:

POST /endpoints/subscription/add.php
...
replacement_subscription_id=100

That was the whole setup. The attacker stored the foreign reference in their own inactive subscription, and the leak only appeared later when the stats logic consumed it.

CVE-2026-50199

The second one was published as:

Cross-user Fixer/API Layer credential consumption in exchange-rate refresh

This one was cleaner.

endpoints/currency/update_exchange.php loaded the first Fixer/API Layer credential globally, instead of loading the credential for the authenticated user making the request.

That meant a normal authenticated user with no provider key of their own could still trigger exchange-rate refreshes using another user’s stored provider credential.

The raw API key was not returned to the attacker, and that boundary matters.

But the attacker could still:

  • trigger outbound provider requests with another user’s stored credential
  • consume another user’s provider quota
  • update their own exchange-rate data through a credential they did not own

That is the kind of bug that looks “small” if you only care about direct key disclosure, but the authorization failure is still real. Ownership of a secret-backed integration matters even when the secret itself is not printed back in the response.

The advisory made the root cause very blunt:

SELECT api_key, provider
FROM fixer

There was no user scoping in that query. That is what made the endpoint consume another user’s provider row.

And the affected request path itself was tiny:

POST /endpoints/currency/update_exchange.php
force=true

The public advisory lists it as:

  • CVE: CVE-2026-50199
  • Severity: Moderate
  • CVSS: 4.3
  • Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
  • Affected: 4.9.0
  • Patched: 4.9.1

The shared pattern

Both bugs came from the same broad problem:

  • user A’s state was allowed to influence user B’s outcome
  • the app trusted a cross-user reference later than it should have
  • the normal UI boundaries did not match what the backend actually accepted

CVE-2026-50198 leaked derived financial data through a foreign subscription reference.

CVE-2026-50199 let one user spend and use another user’s integration credential indirectly.

Different impact, same smell.

References