When a system displays "Payment Successful," everyone assumes the hardest part is over. Money must have moved. The transaction must be valid. The policy must be real.

This vulnerability existed because that assumption was wrong.

A payment flow that looked completely normal

During an authorized security assessment, I was reviewing an online payment flow used to issue insurance policies.

From the user's perspective, the journey was standard:

  • A quotation was selected
  • The user proceeded to payment
  • A third-party payment gateway was used
  • A success page confirmed the transaction

Nothing about the UI looked suspicious.

But payment security doesn't live in the UI. It lives in how the backend decides that a payment actually happened.

In this case, the weakness was not in object references or authorization checks — it was in how payment state itself was trusted.

Ignoring the UI and watching the decision

I intentionally completed the payment using the simulator in failure mode. The gateway clearly returned a failed transaction. The user interface reflected that outcome.

But I wasn't interested in the UI — I was interested in how the backend reacted to failure.

The browser was redirected back with multiple parameters indicating:

  • Transaction identifiers
  • Payment status
  • Quotation and policy references

Those values were treated as authoritative.

None
Initiated Failed Payment Transaction on Payment Gateway

At this point, the backend had everything it needed to issue a policy — except proof of payment.

That's where the real risk appeared.

Intercepting the final step

Even after a failed transaction, the application still exposed the same finalization endpoint.

I intercepted the request responsible for marking payment status and issuing the policy. The backend appeared to accept status indicators from the request itself — without independently verifying whether the gateway had actually confirmed success.

This request contained everything needed to move the business state forward:

  • Payment identifiers
  • Quote or policy numbers
  • Flags indicating payment success

In effect, this single request decided whether a policy would be issued.

None
Intercepted the Processed Failed Transaction Request

Crucially, the backend appeared to trust these incoming values without independent verification.

Where trust turned into a vulnerability

By replaying and slightly modifying this request, a clear pattern emerged:

  • The application did not verify payment status server-to-server
  • It did not confirm that funds were actually received
  • It relied on client-side or redirect-based indicators to mark payment as complete

Once that trust boundary was identified, the outcome was predictable.

None
Altered the Parameters of the Failed Request and Forwarded it to the Application

At this point, I replayed the finalization request, modifying only the payment status indicator — even though the gateway had originally returned a failed transaction.

None
Observed the Secondary Failed Transaction Request in Queue
None
Altered the Parameters of the Queued Failed Request and Forwarded it to the Application

Unlike the earlier IDOR-based case, no identifiers were manipulated. No cross-user access was required.

The issue existed because the backend trusted externally supplied payment state instead of independently verifying the transaction outcome.

Policy issued without payment

After replaying the request with adjusted values, the system behaved exactly as if a real payment had occurred.

A policy was issued. A confirmation page was shown. Download options became available.

No payment was ever made.

None
None

This was not a visual bug. This was a payment bypass.

Why this happened

This issue was not caused by:

  • Weak encryption
  • Broken cryptography
  • A failure in the payment gateway

The failure was architectural.

The backend:

  • Trusted redirect parameters and client-controlled values
  • Did not verify payment outcomes with the gateway
  • Allowed critical state changes based on external input

In short, payment success was assumed, not proven.

Why this matters

Payment bypass vulnerabilities have immediate real-world impact:

  • Policies issued without payment
  • Direct financial loss
  • Fraud potential at scale
  • Reconciliation and audit failures
  • Regulatory exposure in financial systems

Any attacker who understands the flow could repeat this without attacking the gateway itself.

What a secure flow requires

A secure payment design ensures that:

  • Payment status is verified server-to-server
  • Redirects are treated as informational, not authoritative
  • Policy issuance is tied to internal transaction records
  • Business state changes occur only after backend validation

A success page should reflect reality — not create it.

Final takeaway

This vulnerability didn't require breaking encryption. It didn't require bypassing authentication. It didn't require exploiting complex logic.

It succeeded because payment success was assumed — not verified.

A success page should reflect reality — not create it.

When critical business decisions rely on unverified external signals, security stops being about attackers and starts being about architecture.