Forging API Signatures to Find 15 IDORs in an E-Wallet App
Richard Tan · April 30, 2020

A lot of apps assume that signing a request is enough to stop parameter tampering. If the client computes a signature over the payload and the server checks it, changing a parameter should invalidate the signature. That assumption held right up until the signing function itself turned out to be reachable.
The setup
This particular e-wallet app signed sensitive requests with an MD5-based signature computed client-side over the request parameters. Endpoints like deleteBankAccount took a target ID plus a signature, and the server trusted any request where the signature matched. Change the ID without recomputing the signature, and the request gets rejected outright.
So the actual target wasn’t the endpoints at all, it was the function computing the signature.
A stock deleteBankAccount request. Change id without recomputing signature, and the server rejects it outright.
Cracking the signing function
Decompiling the APK with apktool and reading through it in jadx-gui surfaced the method responsible, a straightforward MD5Encode function that concatenated a handful of request parameters and hashed them. Nothing obfuscated, nothing dynamically loaded. Just a plain static method sitting right there in the code.
The whole signing routine, sitting in plain, unobfuscated Java.
That meant the signature could be recomputed offline. Extract the relevant Java, compile it locally, and calculate valid signatures for arbitrary parameter values without touching the app at all. For deleteBankAccount, that was enough on its own, swap in another user’s bank account ID, compute a matching signature locally, and the server accepted it as a legitimate request from that account’s owner.
The extracted signing method recompiled locally, generating valid signatures for a whole range of IDs at once. No device, no Frida, no app required.
A direct copy of the app’s own signing logic. Since nothing server-side ever validated it, reimplementing it offline was enough.
Scaling it up with Frida
Some endpoints combined more parameters, or used values that were awkward to obtain outside a live session, things like session tokens or rotating IDs that only exist while the app’s actually running. Rather than reimplementing the signing logic offline for every one of those cases, hooking the method at runtime made this trivial:
Java.perform(() => {
const Signer = Java.use('com.razer.pay.security.Signer');
Signer.MD5Encode.implementation = function (input) {
const sig = this.MD5Encode(input);
console.log(`[+] input=${input} signature=${sig}`);
return sig;
};
});
With the hook in place on a rooted test device, any payload, legitimate or tampered, could be run through the app’s own signing function and get back a valid signature for it. From there, forging requests for other users’ data was just a matter of building the payload and letting the app sign it for you.
Every call to MD5Encode on the live device, hooked and echoed back with its signature. Legitimate traffic or forged, the app signs whatever it’s handed.
What that unlocked
Chained across the affected endpoints, this pattern led to roughly 15 separate IDORs. Deleting other users’ linked bank accounts. Joining chat groups without an invitation. Reading private transaction history. Viewing, and in some flows actually participating in, other users’ fund transfers.
One of the chained IDORs: forging a signature for addGroupUser was enough to join a chat group with zero invitation.
Why this kept happening across so many endpoints
Once the signing function was extracted, none of these individual bugs took long to find. That’s really the point worth sitting with. This wasn’t fifteen separate authorization mistakes made by fifteen different developers. It was one shared signing mechanism, reused across the whole app, that never actually verified anything on the server side beyond “does this hash match.” Every endpoint that trusted that signature inherited the same weakness at once, whether the underlying IDOR itself was otherwise well protected or not.
That’s a fairly common shape for signature-based schemes gone wrong. The signing logic gets built once, gets reused everywhere for consistency, and because it’s treated as a solved problem early in development, nobody goes back to check whether it’s actually anchored to anything the client can’t reproduce.
The lesson
Signing a request is only as strong as how hard the signing function is to reach. If the signature is computed entirely client-side with no server-held secret involved, it doesn’t really function as authentication. It’s just an extra step an attacker has to automate before doing whatever they wanted to do anyway.
The interesting part of this engagement wasn’t spotting the IDOR endpoints themselves, those were fairly ordinary once reachable. It was recognizing early that the thing supposedly stopping tampering was sitting right there in the APK the whole time, fully readable, fully reproducible, and never actually backed by anything the server alone controlled.
Keep reading

IDOR
Finding an IDOR That the Web Testers Missed
The web app for this target had already been tested twice. The Android client hit a different endpoint entirely - and it wasn't locked down.
January 18, 2026

Race Condition
Bypassing a Passcode Screen With Nothing but Timing
Fuzzing the deep link parameters went nowhere. The bypass turned out to have nothing to do with parameters at all - it was a timing gap in how the passcode activity got loaded.
October 20, 2026
Flutter
Intercepting Flutter App Traffic When Burp Shows You Nothing
Flutter apps ignore the OS proxy and ship their own TLS stack, so Burp sits there empty. Here's how to actually get visibility with Frida.
March 2, 2026