01 The Client-Side Assumption
When auditing mobile applications, frontend developers often equate UI input masking with business logic validation. In this target Android banking service, the user interface featured a strict formatted phone and amount verification screen:
- Digits only (0–9) enforced via Android
DigitsKeyListener - Maximum transfer limit capped at 10,000 units by UI slider limits
- Regular expression verification on the client prior to activating the "Continue" CTA
To an unauthenticated end user testing the APK directly on a smartphone, tampering with the number seemed completely prohibited.
02 Interception & Network Setup
To analyze the raw wire traffic without triggering SafetyNet or device integrity attestation errors, we used physical Android hardware with a custom injected user CA certificate and transparent iptables redirection running inside Termux:
03 HTTP Request Inspection
The moment the network call was intercepted, the frontend masks completely dissolved. The API endpoint accepted arbitrary JSON structures with zero server-side boundary enforcement:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json; charset=utf-8
User-Agent: OkHttp/4.12.0
{
"recipient_account": "98214401",
"amount": -50000.00,
"currency": "USD",
"source_token": "tok_99182a"
}
Submitting a negative integer (-50000.00) bypassed the UI slider limit. Because the backend arithmetic subtracted the amount from the recipient and credited the origin balance without asserting amount > 0, it reversed the ledger transfer direction.
04 Frida Runtime Hooking
To inspect why the Android app's internal Retrofit interface passed negative floats without triggering local assertions, we hooked the compiled validation class using Frida:
05 Root Cause Analysis
The root cause was architectural asymmetry: the mobile client team wrote strict UI sanitization believing the backend would enforce business constraints; the backend microservice team assumed the client-facing gateway had already filtered invalid numbers.
06 Mitigation & Schema Validation
Never rely on client-side regex or input formatters. All numerical fields must be strictly validated at the API boundary using runtime schemas with strict bounds:
class TransferRequest(BaseModel):
recipient_account: Annotated[str, Field(pattern=r"^[0-9]{8,12}$")]
amount: Annotated[Decimal, Field(gt=0, le=10000, decimal_places=2)]
currency: Literal["USD", "EUR", "GBP"]
source_token: str