Recently I had unusual success on a public program on HackerOne. Here is my story on how I approached this program, what I found and how I found it.

July the 1st troubleshooter shared a link in the Bug Bounty Forum Slack channel, showing that a public HackerOne program was going to pay for valid security issues (if I had been subscribed to the HackerOne feature Notify me of changes I would have known it even earlier). Since it is currently my summer holiday and I imagined that relatively not many people would have seen this news, I decided to give this program a go. After a short while I noticed that this program would be an interesting target since:

  • I found a few reflective XSS issues
  • The application is coded in PHP
  • They seemed to heavily rely on the Akamai WAF Kona.
  • A big scope with multiple apps and privilege levels (customer, manager, admin)

Encrypted ID

The first big problem I found relied on the way orders are displayed to the user. This website uses two different ways to refer to orders. Their numeric ID (29456739) or their encrypted ID (eQaqdv). The problem I noticed here quite quickly is that the encrypted ID has very little entropy and gives access to the order details no matter which user is logged using the following request:

POST /order_handling.php HTTP/1.1
Host: www.company.com
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 39
Cookie: {COOKIE}

case=orderdetail&res_id=1&tab_id=eQaqdv

Thus to test if brute forcing this encrypted ID was feasible, I used Burp Intruder with the brute force option. Here I chose to only brute force A-Z with a minimum and maximum of 2 characters. This way I would only send 676 requests. After this brute force was completed, I was greeted with the following screen, confirming 9 valid orders out of the 676 requests. Resulting in a 1,3% success rate. This request leaked full address, first and last name, phone number, the order, payment method and payment instructions among other details.

Results

Later I discovered an endpoint that would take any numeric value and output it as the corresponding encrypted ID. This made brute forcing unnecessary and allowed me to exploit another order bug where brute forcing was not feasible. Looking for these kind of "translations" can be super usefull, especially if you are bad at breaking encryption like me.

Mobile app

After these issues it was time to move on to the mobile app, since in my experience a mobile app has more server-side (read critical) issues, while a web application has more client-side vulnerabilities. After testing the login flow for the three different options: Facebook, Google and password based, I noticed that Google login looked slightly weird in the fact that a lot of data was send besides the Google OAuth token. Thus I started stripping parameters one by one after which I noticed that only the email of a user was required to login. This request would look like:

POST /v2/authenticate.json?isGoogle=true HTTP/1.1
X-PUBLIC-API-Key: 1bc29b36f623ba82aaf6724fd3b16718
Content-Type: application/x-www-form-urlencoded
Content-Length: 21
Host: api.company.com
Connection: close

[email protected]

Thus I could login as any user given I knew their email address.

Business App

This login vulnerability gave me access to a test account of the company which had a couple test venues setup in the Business app of the company. Using this account I logged in and noticed that this part of the scope was definitely under-tested compared to the part where the normal customer gets access to (basically this part is only for venue-owners).

One of the features that was interesting right of the bet was the possibility to add a secondary owner to your venue. After stripping the unnecessary parameters I got the following request:

POST /v2/addsecondaryowner.json HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 58
Host: api.company.com
X-PUBLIC-API-Key: 1bc29b36f623ba82aaf6724fd3b16718
X-Access-Token: {VALID_ACCESS_TOKEN}
X-Client-Id: company_ownerandroid_v2

res_id=313227&name=Admin&[email protected]

This request would invite [email protected] to venue 313227. The problem here is that any venue ID would work, giving the attacker the possibility to invite himself to any venue. Upon further investigation adding a single quote would make the server return a 500 status error, which.... SCREAMS SQLi. Exploitation was fairly straight forward (Akamai wasn't set to filter as aggressively as it currently does) with the payload res_id=)+AND+SLEEP(5)%23.

Hardcoded credentials

After this I found myself diving deep in their mobile application, since they allowed me to interact with the main website without the Akamai WAF in between and because I had found issues, like the one above, in the API before. Thus to discover more endpoints, I decompiled the app to smali and Java. After this I used the following grep command on the smali code to find more endpoints:

$ grep -hnr '\.json' /Users/Gerben/Downloads/App/smali/

However after looking for these grepped .json endpoints in the Java source code I accidently came across hardcoded credentials for a user cdev in the source code:

paramaa.e().b("Authorization", "Basic BASE64CredentialString");

I didn't know what to do with this right away. I left it for a while after which I started doing some searches for cdev. Here I found out that this was referring to a certain top domain. Most of these (sub)domains returned a 503 error at the time. So again I couldn't use the credentials. At this point I decided to enumerate the subdomains which I did with aquatone. This showed that some of its subdomains were online and would accept the credentials I found. The problem at this point was that I wasn't able to find anything interesting on these domains. This however changed at the end of the domain scan where aquatone found vikad.topdomain.com. Logging into this domain gave me access to an admin account on a clone of the main domain of the company, giving me access to all the domain tools they use on their main domain.

Leveraging JS files

BAMMmm... 5 minutes after logging in to this developer subdomain the entire top domain went offline 😕. However since I had Burp Suite running I had collected all the JS files served to the admin endpoints. After analyzing these files using LinkFinder I noticed a few new endpoints that normal users had access to.

Thus I started reconstructing the endpoints found in the JavaScript files and started to manually test them for IDOR or SQLi vulnerabilities. Here is one of them:

return HOST + "owner_proof_upload.php?type=upload-tan" + "&expected_files=" + t.t_files + "&file_index=" + (e + 1) + "&session_id=" + $(".session-id").val() + "&owner_id=" + $("#owner-id").data("owner-id") + "&tan_number=" + $("#owner-tan-number").val()

This endpoint would show {"message":" File type not allowed."}, however after injecting a single quote into the merchant_id the server returned a 500 HTTP error, which.... SCREAMS SQLi (again). After fuzzing around, while avoiding the banned keywords like SLEEP, BENCHMARK or the = characters, I noticed that the server would return a different response when inserting a true OR statement. After I had managed to do this I developed a POC showing possible data retrieval like:

https://www.company.com/owner_proof_upload.php?type=upload-tan&owner_id=444+OR+MID(CURRENT_USER,1,1)+LIKE+"p"/**/%23

Which returns {"status":"error","message":"TAN Number is already approved!"} because the first character of CURRENT_USER is a p. If false the server would return {"message":" File type not allowed."} like normal. Here I used the following strings instead of their more common counterpart to bypass the WAF:

  • MID instead of SUBSTRING
  • LIKE instead of =
  • /**/ instead of a space
  • CURRENT_USER instead of CURRENT_USER()
  • " instead of '

Things to learn from this

  1. Try to expand the scope using the vulnerabilities you found or by simply asking for a privileged account (2nd one is better but not always possible). A lot of the vulnerabilities I found in this program required some special privilege to find.
  2. Try your best to search extra information about potential vulnerabilities you find. Later I heard the hardcoded credentials had been reported before, however since no additional information about exploitation had been given the report was marked informative. Note that for these hardcoded credentials there were multiple invalid ones in the Android app as well.
  3. Be connected. Make sure you use multiple channels to stay up to date about security and bug bounty news. For me these channels are: Bug Bounty Forum, Twitter and HackerOne. By getting information about this program through Slack I was able to find bugs that opened up a "hidden" part of the application which I might not have found if I started later.

*Some information has been changed to keep the company private. Permission for this blog was granted.