IDOR on CoinDepo: reading any user's KYC through a trusted email field
One profile endpoint decided whose data to return from values in the request, not from who was logged in. Any authenticated user could pull another user's full KYC record. Reported through HackenProof, rated Critical, rewarded $5,289.
CoinDepo is a crypto savings and lending platform, so the accounts on it carry real identity data. One of its API endpoints returned a user's full profile and KYC record (name, date of birth, phone, address, and links to their uploaded ID documents) based on identifiers sent in the request, without checking that the person asking actually owned that account. Any logged-in user could read any other user's record by changing a single field.
It was reported through HackenProof, disclosed, rated Critical, and rewarded $5,289.
What an IDOR is, in plain terms
When you open your profile, your app asks the server: give me my details. The server has to decide whose details to hand back. There is a safe way and an unsafe way to make that decision.
- Safe: decide from who you are, the account tied to your logged-in session.
- Unsafe: let the request say whose details it wants, and trust it.
When the server trusts an identifier in the request (an email, a user id) and returns that person's data without checking it belongs to you, that is an Insecure Direct Object Reference (IDOR), also called Broken Object Level Authorization (BOLA). You point directly at someone else's object, and nobody checks whether you are allowed to. It is the most common serious API bug there is, and it is exactly what happened here.
The endpoint and the flaw
The profile page loads through POST /api/details. The request carries
several identifier fields in its body (among them email, username and a client_token) and the endpoint returns the profile and KYC record for the account those fields name.
The endpoint picked the account from the email and identifiers in the request body, rather than from the authenticated session. Nothing on the server checked that the caller owned the account they were asking about. So changing the email to someone else's returned someone else's data.
Reproducing it
Testing was done with two accounts I created and controlled, so no real user's data was ever touched. The steps:
-
Log in with any valid account
Any authenticated account is enough. There is no special role required.
-
Catch the profile request and change the target
In the email-verification flow the browser sends a request to
/api/details. Send it to a proxy like Burp Suite, then change the identifier fields to point at another account.POST /api/details HTTP/2 Host: app.coindepo.com Content-Type: multipart/form-data; boundary=----boundary ... ------boundary Content-Disposition: form-data; name="username" VICTIM_EMAIL@example.com <-- the account you want to read ------boundary Content-Disposition: form-data; name="email" VICTIM_EMAIL@example.com <-- same here ------boundary Content-Disposition: form-data; name="client_token" <a valid token from any account> ------boundary Content-Disposition: form-data; name="cdkey" <request key> ------boundary-- -
Send it, and read the response
The endpoint answers
200 OKwith the target account's full record.
Know Your Customer: the identity data a financial platform collects to prove you are who you say: your ID card, a selfie, your address. It is some of the most sensitive data a company ever holds about a person.
What came back
The response was the account's complete profile. The fields that matter, sanitized:
{
"user_id": 19090,
"email": "VICTIM_EMAIL@example.com",
"first_name": "...",
"last_name": "...",
"date_of_birth": "YYYY-MM-DD",
"phone": "+...",
"address1": "...",
"city": "...",
"state": "...",
"zip": "...",
"document_type": "id-card",
"document_photo1": "./uploads/<...>_document_photo1.jpg",
"document_photo2": "./uploads/<...>_document_photo2.jpg"
}
The last two lines are the worst of it: paths to the images of the account holder's government ID. Full name, date of birth, address and a phone number is already enough to impersonate someone; add their ID documents and it becomes a complete identity kit.
Proof of concept
Why this is Critical
On a financial platform this is not just a privacy leak.
- Identity theft. Name, date of birth, address, phone and government-ID images are exactly what is needed to impersonate someone.
- Account recovery and phishing. The disclosed data makes credential-recovery attacks and targeted phishing far easier and more convincing.
- Fraud on money. On a platform that moves funds, confirmed identity data raises the risk of unauthorized transfers when chained with other weaknesses.
- Scale. Because the account was chosen from a request field, this was not one victim: it was anyone whose identifier you supplied.
The fix
IDOR is an authorization problem, so the fix is about who the server trusts to name the account:
- Never trust a client-supplied
email,user_idor similar field to decide authorization. - Identify the target account only from the authenticated session or token, never from the request body.
- Enforce an ownership check on the server before returning any profile or KYC data.
- Return only the fields the client actually needs, not the entire record.
- Serve document images through short-lived signed URLs with per-user access control, never plain paths.
- Add automated tests for IDOR / BOLA on every account and identity endpoint.
Key takeaways
- If an endpoint decides whose data to return from a value in the request, assume it can be pointed at someone else until proven otherwise.
- Authorization must be derived from the session, never from a field the caller controls.
- The severity of an IDOR is set by what it returns: here, KYC and ID documents, which makes it Critical.
- Test object-level access on identity endpoints specifically; they are where a leak hurts most.