Engagements  /  PSY-WEB-0429

Business logic that let a loan approve itself

Every individual step in the loan workflow was secure. The order of the steps was not.

Representative engagement. Client names, figures and identifying details are changed or combined; no client findings are published in identifiable form.

Sector
Financial services and fintech
Engagement
Pentest & secure code review
Frameworks
OWASP ASVS 5.0 Level 3, OWASP WSTG, PCI DSS v4.0.1 (card disbursement)
Duration
15 test days

A consumer lending fintech.

The lender ran affordability checks, identity verification and a manual underwriting step before releasing funds to a card. Each service had been tested on its own.

Nobody had tested what happened between them.

What we did

Read the code, then race it.

Follow the state machine in source

With white-box access, we traced the loan application’s status field through every service and found it could be set directly by the client in one legacy route.

Send requests in parallel

The disbursement endpoint checked “already paid” before writing “paid”. Twenty parallel requests produced three disbursements.

Score in context

Both findings scored critical under CVSS v4.0 once the environmental metrics reflected that real money moved, not just data.

What we found

Two criticals between the services.

SeverityFindingMaps toStatus at retest
CriticalApplication status writable from the client on a legacy update route, skipping underwriting.WSTG-BUSL-06 · ASVS V2Closed
CriticalRace condition on disbursement allowed duplicate payouts.WSTG-BUSL-04 · CWE-362Closed
HighInterest calculated in floating point; rounding drift on early repayment.ASVS V2 · CWE-681Closed
HighIdentity-verification webhook accepted unsigned callbacks.API10:2023 · ASVS V4Closed
MediumVerbose error responses exposing internal service names.A10:2025 · ASVS V16Closed

What changed.

Status transitions now go through a single server-side state machine, and disbursement uses an idempotency key with an atomic update. Both criticals were verified closed with the same parallel test that found them.

The client added the race-condition test to its own CI pipeline, so the regression cannot quietly return.

Next engagement

Want this for your application?

Tell us what you ship and who is asking for proof. We will come back with a scope and a range.