How a Leaky API Endpoint Led to Full PII Exposure on a Major Fintech Platform
In the world of bug bounty hunting, sometimes the most critical vulnerabilities don’t come from a single, glaring flaw. They come from a chain of seemingly minor issues that, when linked together, create a devastating attack path. This is one of those stories.
Recently, I was looking at a large fintech company — a major player that provides a global platform for businesses to accept and send payments across borders. They were rolling out a new version of their checkout system and had a sandbox environment available for testing. Sandboxes are a goldmine for hackers; they let us poke around a near-production environment, often with debug features and verbose errors that get sanitized before going live.
My target was their checkout page. My goal? To see if I could break the logic of a financial transaction. What I found was a classic Insecure Direct Object Reference (IDOR), but its impact was supercharged by a separate information leak, elevating it from a simple bug to a high-severity data breach.
The Hunt: When a “Feature” is a Bug
The hunt began, as it always does, with reconnaissance. I set up Burp Suite and started exploring my own sandbox account, mapping out the application’s functionality. I created a checkout page, tweaked settings, and watched the API calls fly by in my proxy history.
Most of it was standard stuff. But then, a strange endpoint caught my eye:
GET /v1/hosted/config/preview_tokens?type=checkout HTTP/2
Host: sandboxapi.target.net
I sent it to Repeater and examined the response. I expected to see a token for my own session. Instead, the API returned a list of two checkout IDs.
{
"data": [
{
"token": "checkout_0a98d5680369b7192e0e7ea91d0e380f"
},
{
"token": "checkout_f2b3f53a1df9ffe8698236efae4d50e5",
"is_demo": true
}
]
}
This was the thread.
Why was I, an authenticated user, seeing IDs that didn’t belong to my session? This was either a strange design choice or, more likely, an information leak. This endpoint was handing me the keys to other users’ carts.
From OPTIONS to GET: Probing the Endpoint
As I continued to browse my own checkout page, another request popped up in my history. It was a pre-flight OPTIONS
request, which browsers send to check if a server will allow a cross-origin GET
request.
OPTIONS /v1/checkout/client/checkout_0a98d5680369b7192e0e7ea91d0e380f HTTP/2
Host: sandboxapi.target.net
Access-Control-Request-Method: GET
Notice the ID: checkout_0a98d5680369b7192e0e7ea91d0e380f. It was the first ID from the leaky /preview_tokens
endpoint. The front end was clearly using these leaked IDs for something.
OPTIONS
requests themselves don't do much, but they signal intent. They tell you what the application wants to do. In this case, it wanted to send a GET
request.
So I did it for them.
I sent the request to Repeater, changed the method from OPTIONS
to GET
, and hit send.
The server responded with a 200 OK
and a JSON object for that checkout ID. It was an old, expired session with no interesting data. But it proved one thing: the endpoint was accessible. Now for the real test.
The Kill Chain: Leaking PII
I went back to Repeater. I still had the GET
request for the expired session loaded up. But now I had my target: the second ID from the list, checkout_f2b3f53a1df9ffe8698236efae4d50e5. I swapped it in.
GET /v1/checkout/client/checkout_f2b3f53a1df9ffe8698236efae4d50e5 HTTP/2
Host: sandboxapi.target.net
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0)
Origin: https://sandboxcheckout.target.net
I took a breath and hit send. The response came back instantly. Jackpot.
It wasn’t an expired session. It was a live one. And it contained a full package of another user’s PII.
{
"data": {
"id": "checkout_f2b3f53a1df9ffe8698236efae4d50e5",
"status": "NEW",
"customer_data": {
"id": "cus_c95421c63e484a86dd3d84a40e188b4e",
"name": "Israel",
"email": "email@target.net",
"phone_number": "593996436599",
"addresses": [
{
"name": "Carlos Address",
"line_1": "sample street",
"city": "Minas Gerais"
}
]
}
}
}
This was the full kill chain:
- Leak 1 (Information Disclosure): The
/preview_tokens
endpoint discloses checkout IDs of other users. - Leak 2 (IDOR): The
/checkout/client/
endpoint fails to check if the requester is authorized to view the requested ID.
Chained together, these two low-to-medium severity bugs created a high-impact PII breach.
Conclusion: Always Pull the Thread
This was a fantastic find that underscores a few key principles of hacking:
- Context is King: An IDOR on a gaming site might be a P4. An IDOR on a fintech platform that leaks a full PII package is a P2, easy. The impact is magnified by the environment.
- Don’t Ignore Leaky Endpoints: An endpoint that returns a list of IDs is a gift. It’s an invitation to test for IDORs across the entire API surface.
- Trust Your Gut: When you see something that looks out of place — like an API returning IDs that aren’t yours — pull on that thread. More often than not, it will lead to something bigger.