Leaking a Google OAuth 2.0 Access Token Through Misconfigured Error Handling
Passionate about all things cybersecurity.
I was hacking on a mobile app that lets musicians purchase sheet music for various compositions. After having proxied traffic from my phone to Burp Suite, I didn't find anything that piqued my interest or seemed vulnerable, all the API endpoints appeared secure. So I decided to download the APK from the Play Store onto my computer to analyze the binary and potentially find more endpoints worth investigating.
To do this I used Android Studio, which lets you simulate a real phone on your computer. I downloaded the app onto the simulated device and then pulled it onto my computer via ADB. ADB (Android Debug Bridge) is a command-line tool that lets your computer communicate with an Android device or emulator.
With the binary in hand, it was time to analyze it. I used a tool called JADX, a decompiler for Android apps. Fortunately, the app wasn't obfuscated at all, so I could read the source code and all its strings without any issues.
I quickly found the section of the code containing all the API endpoints and discovered many that hadn't shown up while proxying traffic. One endpoint caught my attention: processPlayStoreCreditsPurchaseReceipt, which appeared to handle receipts related to in-app purchases. My first thought was an access control bug, maybe I could fetch other users' receipts? It turned out to be far worse than that.
After identifying what parameters the endpoint accepted, I sent garbage values to see how it would respond.
POST /parse/functions/processPlayStoreCreditsPurchaseReceipt HTTP/2
...
{ "receiptId": 123, "userId": 123, "productID": 123, "purchaseToken": "asdsad" }
The response contained a live Google OAuth 2.0 access token, the one the backend used to communicate with the Google Play Android Publisher API. (Had to redact this one for obvious reasons)
The backend was taking the values from the POST request, productID and purchaseToken , and passing them directly into a server-side request to the Google Play API to retrieve information about a purchase. Since we sent garbage values, that request failed, and because the error handling in /parse/functions/processPlayStoreCreditsPurchaseReceipt was completely misconfigured, instead of returning a clean error response, it leaked the OAuth access token the backend had used to authenticate with the Google API.
So what could be done with this token? Quite a lot, it turns out. Using it against Google's APIs, you could call /androidpublisher/v3/applications/{pkg}/edits to obtain an edit ID, then use that ID to push changes to the app on the Play Store — for example, uploading malicious code. In practice, this means silently pushing a trojanized update to every single user of the app. Not great.
The team behind the app responded swiftly and fixed the issue.

