Recently I had to dive into Android app development to fix an issue in an app. Then, while I was testing the app I noticed that the payment process was not working. The app uses Instamojo, a payment gateway provider in India. The process works fine through the Web client, but the Android flow was broken. As I was trying to fix it, I saw that Instamojo had updated the API and was urging developers to use the new version. Inspired by this Zack Whittaker tweet, I then discovered that this new process creates a security loophole that allows the end user or some other MITM attacker to alter the payment price to be paid during the transaction. This vulnerability can be mitigated by doing a further back-end verification after the transaction is completed, but at the very least it creates a messy situation for the vendor.
What’s the problem?
I pieced together what’s happening by stepping through the code and logging all the network calls. During the payment process, a client app first gets an Instamojo API access token (aka “authentication token” or “auth token”) from it’s own back-end server (Step 1 in the sequence diagram below). It then creates a new payment request with Instamojo by using an Instamojo code library that is embedded in the app (Step 3). During that creation step the client app provides the description of the payment and the price (Step 3a). Then the end user fills in the payment method details and the transaction is completed (Step 3b).
The flaw occurs in Step 3a, where the request can be intercepted and manipulated. I tested this out by setting up a HTTPS proxy on my laptop and creating a rule in the proxy to convert the price to 10 (the minimum allowed by Instamojo. The currency is implicitly Indian rupees). I then made a payment request for 200. As expected, the proxy intercepted the request changed the amount to 10, and that was the amount of the payment transaction.
In addition to allowing an app user to manipulate the price, this process also exposes the access token to a malicious user, who could use it to perform actions in the merchant’s name on the site. Think of an access token as a permission slip that says, “Whoever has this is authorized to do the following: x, y, z.” However, in the case of Instamojo’s access token, what actions the malefactor can take is unclear. There appears to be a disconnect between the API documentation and the implementation. According to the Instamojo documents page, this token can be used to create an account on Instamojo.
However, when I tried to create a client account, I received the message, “You do not have permission to perform this action.” During authentication itself the response contains a “scope” field that says,
"read write payouts:read payments:read payments:fulfil payments:refund", which would seem to indicate that this token doesn’t allow account creation. Nonetheless, it does not seem like a good idea to expose the access token this way.
Finally, there is also a slim possibility of someone installing a HTTPS proxy on the device or the network to capture all payment creation requests sent to Instamojo and redirecting them to their own spoof site. Since the Instamojo client doesn’t do any certificate verification or pinning (although even that can be overcome), it blindly trusts whatever response it gets from its API calls (or else my test would have failed). There may be reasons why Instamojo doesn’t want to pin, but if the payment requests were originated in the service back-end, a man-in-the-middle attacker would have to intercept two different API endpoints to spoof the payment request.
How could this have happened?
I don’t think that the people at Instamojo are stupid or careless, but I am taken aback that loophole was missed by their product managers, security analysts and developers. I am merely speculating here, but I think this occurred because they were trying to solve a different problem for vendor developers: that of integrating the payment gateway into their apps.
Integrating with a payment API, or any API, can be confusing and tedious. First there’s the OAuth mechanism to figure out, and thought it is supposed to be a standard, each API provider seems to have a slightly different way of doing it. Next, there’s the back-and-forth flow of setting up and executing the transaction.
A naive approach is to contain the entire process within the client app, starting with authentication, by putting the authentication credentials (they’re called client ID and client secret, analogous to userid and password) into the client code. This would be a very bad idea since that would mean that anyone with the wherewithal could reverse engineer the client code (it’s in their device, after all) and get the secret information. Less catastrophic but still bad would be to pass the credentials to the client dynamically, since someone could still grab them through the app or during transit. It seems that Instamojo is aware of these pitfalls because it’s put up the following warning on its Android integration documentation page:
I mention all this because it looks like Instamojo decided to try to eliminate the possibility of developers putting client code in the app by using a 3rd party integration tool, and that’s where the flaw may have been introduced. The tool, which is an executable that developers must download, breaks up the payment API code integration by explicitly generating server-side code that a developer has add to the server, and then automatically injecting the client-side code into the Android app source files (There are many issues with this idea but I’ll save those for another article).
Once the client app has an access token, it interacts directly with Instamojo’s servers. However, a properly secured payment flow requires more than a one call to the service backend solely to fetch an access token. It either requires a second request for a payment request id, or else both of those can be combined.
What’s the fix?
The best practice in a situation such as this is for the app’s back-end server to authenticate with the payment gateway server and get an access token that can be used to make transactions. Depending on how it’s to be used, the token may have a validity of limited duration or it may never expire. The service back-end can then use the access token to create a new payment request with the payment gateway. This way, the access token never travels outside the service backend and therefore the client never receives it. There is no opportunity for a malicious actor to disrupt the flow or cause other mischief. The next sequence diagram illustrates the process.
In this flow, the client app contacts its server to initiate a payment request (Step 1). The server gets an access token (aka “auth token”) from the Instamojo authentication server (Step 2) and creates a payment with the payments gateway (Step 3). It then sends a payment request ID to the client. Finally, the client app uses the payment request ID to get the payment details from the user and complete the payment (Step 4).
Instamojo already has a working and secure payment process when it comes to Web applications. There all the payment-setup interactions occur between the vendor’s server and Instamojo’s servers, and the Web client is redirected to an Instamojo web page where the user enters the payment details. Instamojo could fix this issue by applying the same process to the app client.
However, if for some reason the access token does need to be transmitted to the client app, there should be finer grain controls over what the token allows the app to do. Furthermore, allowing service providers to whitelist their server IP addresses with Instamojo’s api servers would prevent further token misuse. Finally, better documentation and clear examples would eliminate the need for an unproven integration tool that increases the possibility of security flaws.
I first posted a question about this flaw on Instamojo’s developer support site before I actually tested it out and was sure that the flaw existed. I got a response to that from an Instamojo support person asking for a screenshot (of what, I don’t know). I then submitted a bug report and got a response that said, “We will look into the report and update as soon as possible.” It has now been a week, which seems long enough to determine whether there’s an issue and what to do about it. Meanwhile our Android app is on hold and we cannot publish it because of this flaw.
Recently Troy Hunt posted about the positive effect of public shaming, and Brian Krebs concurred. Given that this flaw doesn’t cause any real harm to consumers, and anyone who exploits it will be known to the merchant, I feel comfortable that this disclosure will cause limited damage. I also think that pointing out that the access token can be compromised doesn’t fall outside the bounds of responsible disclosure, since that is how the process was designed. I hope this spurs Instamojo to change the process soon.