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.
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.
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.
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 downloadPdf
function, it spit out the current ticket. Then I went on to look at the code of downloadPdf
function.
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'); | |
} |
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.
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 hdnBookingId
to bookingId
or there could be two files being saved for each ticket. One with hdnBookingId.pdf
and 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
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.
The endpoint returns passenger details in html table form ( instead of pdf as earlier ). Let’s verify the IDOR.
Now, documentation helps us even more. Remember the ProcessType
parameter, we’ve only looked at process type 1
, what about other values 😛
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.
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
- 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.
- 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 😉
- 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.
- No secret sauce : Bugs are simple, persistence is the key.
Other Titbits
- 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 withgrep
installed.
-
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')
-
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 :)