Attention to Details: Finding Hidden IDORs

How a huge travel portal’s customer PII data could’ve leaked through some remanant functionality.This lead me to discover a few IDORs.

Goa has always been an adventure paradise. This tale is no exception.

A few of us friends were planning a Goa trip. Searching for cheap tickets on skyscanner, led me to this website, henceforth called as ‘whereIDORsLive.com’, which had amazing offers. This is a big travel portal in India and elsewhere, with offices in Singapore, Dubai and London too. Since its beginning, last decade, it has gained quite some traction in recent days, due to some big Bollywood celebrities advertising for them.

Note these IDORs are in the sequence of which I found them and not on the basis of severity.

1st IDOR : Download Anyone’s Ticket

I went on to book my tickets and on the transaction confirmation page, there were options to ‘SMS’, ‘Email’ and ‘Download’ the ticket as pdf.

Transaction Confirmed Page

Transaction Confirmed Page

I went on to download the ticket as PDF. It has pdf with encrypted name, at first I thought it was just base64 encoded but ‘decoding’ it ( after adjusting proper padding ) gave something gibberish. It’s usually the case that encrypted strings are then ‘encoded’ in base64 so that they can be converted to printable characters for smooth transfer and rendering amongst the applications.

PDF Download Page

PDF Download Page

One Simply Does Not Write Their Own Cipher

Whenever I see encryption on website, I get curious to explore that as in my experience, most of these have some wrong implementation or worse ‘custom implementation’.

My suspicions were raised cause the site was not using SSL certificate for their API and was doing ‘encryption’ shit on their pdf name, something really ‘hacky’ must be going on. These were all indications of slap dash work done on the website’s end. So, I went on to do ‘Inspect Element’ on the ‘Download PDF’ button.

Inspect Element on Download PDF Button

Inspect Element on Download PDF Button

One cannot fail to notice that there’s a downloadPdf function being called with the ‘Booking ID’. Now, the very first thing I did was call the same function with the incremental next booking ID i.e. ‘66786694'. That also opened the same pdf i.e. my ticket’s pdf, no matter what ID you put in the downloadPdffunction, it spit out the current ticket. Then I went on to look at the code of downloadPdf function.

downloadPdf code

The Code was simple, it was taking in the bookind id number ( here 66786693 ) as ‘tid’ but instead of using it reassigning that to ‘hdnBookingId’, the encrypted string. So, when you clicked on it, a new tab would open with your pdf :

http://api.whereIDORsLive.com/XYZService/EticketPdf/hdnBookingId.pdf

function downloadPdf(tid) {
if (document.getElementById("hdnBookingId") != null && document.getElementById("hdnBookingId").value != "")
tid = document.getElementById("hdnBookingId").value;
if (tid != null && tid != "" && tid != undefined)
window.open("http://api.whereIDORsLive.com/XYZService/EticketPdf/" + tid + ".pdf", '_blank');
}
view raw downloadPdf.js hosted with ❤ by GitHub

Now, this raises question as to why would someone do that, why not straightaway call the downloadPdf function, why to pass the booking number , when not using it.

There was only one thing that came to my mind, it would be legacy code and earlier it could be that the function was substituting the ‘bookingId’ straightaway into the URL.

So, earlier the links could’ve been like :

http://api.whereIDORsLive.com/XYZService/EticketPdf/bookingId.pdf

Now, just to check whether that backend still lives I went on to this link:

http://api.whereIDORsLive.com/XYZService/EticketPdf/66786693.pdf

And yup, it gave the PDF, iterating over the booking Ids , I could fetch other people’s tickets too. I promptly and responsibly disclosed this to their concerned team.

My Ticket Fetched Using Booking ID

My Ticket Fetched Using Booking ID

IDOR Confirmation — Fetching Other People’s ticket

IDOR Confirmation — Fetching Other People’s ticket

And Then We Said

Why Did This Happen ?

Probably because, at the backend the files were still being saved as bookingId.pdf , and there would be a middleware decrypting the hdnBookingIdto bookingId or there could be two files being saved for each ticket. One with hdnBookingId.pdfand the other with bookingId.pdf .

Mitigation

If the application only kept the ‘encrypted filename.pdf’ this whole thing could’ve been mitigated ( but then again, how come I would’ve written this blog 😛 ).

2nd IDOR : Another Day, another endpoint, Same Company

This day I started looking at the android app of the company. I found that the traffic was being routed to one endpoint :

http://cloud.whereIDORsLive.in/XYZService/dboperation.svc
A documentation of endpoints

A documentation of endpoints

Now, this was a treasure trove (atleast for a hacker :p). It was a documentation of all the endpoints, when clicking on the hyperlink corresponding to the endpoint there was also JSON and XML sample payload and response you could expect from the endpoint. It was like swagger even better except the trying out feature was missing (then it would’ve been like serving on a platter).

Going through the endpoints, I found one that could probably lead to some information leakage.

/GetETicket/{TransactionscreenID}/{UserName}/{Password}/{ProcessType}

Now it required TransactionscreenID , UserName and Password , of which I didn’t have any clue at the moment.

Exploring the application a bit and fetching the ticket through the app, triggered this endpoint and then I could see the values required to get the ticket details.

This endpoint returns a table with passenger details

This endpoint returns a table with passenger details

The endpoint returns passenger details in html table form ( instead of pdf as earlier ). Let’s verify the IDOR.

Could fetch other person’s details

Could fetch other person’s details

Now, documentation helps us even more. Remember the ProcessType parameter, we’ve only looked at process type 1, what about other values 😛

ProcessType 2 stands for Invoice (perhaps)

ProcessType 2 stands for Invoice (perhaps)

3 isn’t a valid value for ProcessType and hence raises exception

3 isn’t a valid value for ProcessType and hence raises exception

Passing the ProcessType parameter as 3 raises an exception and we get a sneak-peek into the underlying code.

3rd IDOR : Same Day, another endpoint, Same Company

Looking through the documentation, there was another endpoint which looked like it would return sensitive information.

/GetPaxBookingDetails/{TransactionscreenID}/{UserName}/{Password}

Requesting data from this endpoint, returned PII of the customer. Any user who had ever booked a ticket from the company’s website, their data could be fetched.

Flight Details of any passenger

Flight Details of any passenger

PII of Passenger

PII of Passenger

Verifying IDOR — Fetching details for different booking ID

Verifying IDOR — Fetching details for different booking ID

Why did these Happen ?

This happened because there wasn’t any access control or ‘strong authentication’ on the endpoint. It was lying there waiting to be found 😛.

Mitigation

One possible way I’ve seen in other applications protecting their endpoints is by having proper access control. Usually it’s JWT or any other token that identifies the user and thus gives access to only resources related to them.

Key Takeaways

  1. Be Curious: Curiosity doesn’t always kill the cat 😛. Questioning and understanding why the input parameter was booking id but the request being made by hdnBookingId, led me to these bugs.
  2. Expand your search area: In this case, as I had checked the web app, going through the android app, led me to customer PII, which is a P0 data for any company. BTW I didn’t get the time to look at the iOS app, in case someone figures out which company it is 😉
  3. Learn your tools right: Linux can be your friend. A lot of linux commands used properly will save a lot of time and would be a great learning experience altogether. Also, a lot of small things like base64 encoding/decoding, URI encoding/decoding can be done in browser’s console itself and you don’t have to latch on to a 3rd party website or app.
  4. No secret sauce : Bugs are simple, persistence is the key.

Other Titbits

  1. Getting all the hardcoded endpoints from a decompiled apk or similar crawling, run this from the root of your recon/decompiled apk folder :
    grep -rE "https?://.*companyName.*" . 
    This works for *nix systems or any system with grep installed.
Finding all the hardcoded endpoints in an apk or folder

Finding all the hardcoded endpoints in an apk or folder

  1. Base64 encoding/decoding in chrome : 
    Decoding : ( helpful in JWT decoding too )

    atob('eW91IGxlYXJudCB0aGF0IHJpZ2h0') <- try this in your browser's console

    Encoding :

    btoa('this is how you encode in base64')
  2. URI decoding : Decoding

    decodeURIComponent('[https://example.com/?query=%22this%20is%20a%20url%20encoded%20string%22'](https://example.com/?query=%22this%20is%20a%20url%20encoded%20string%22%27))

Thank you everyone :)

Aseem Shrey (@aseemshrey)

Aseem Shrey

Hey! I'm Aseem Shrey.

const about_me = {
loves: "CyberSec, Creating Stuff", currently_reading: "Gandhi: The Years That Changed the World, 1914-1948", other_interests: [ "Reading 📚", "IoT Projects 💡", "Running 🏃( Aim to run a full marathon )", "Swimming 🏊‍♂️" ], online_presence: HackingSimplified AseemShrey };
Ping me up if you wanna talk about anything.

About meJoin newsletterGitHub