Here‘s How I Could‘ve Gotten Unlimited Free Uber Rides Around The World

Uber app on phone

Uber is the world‘s most popular ride-hailing service, operating in over 900 cities across 80+ countries as of 2022. In the fourth quarter of 2021 alone, Uber averaged a staggering 19 million passenger trips per day. A critical part of Uber‘s business is securely processing payments for each of those millions of daily rides.

But back in 2016, I discovered a critical flaw in Uber‘s payment processing that would have allowed anyone to get unlimited free rides anywhere in the world. In this post, I‘ll walk through the details of the vulnerability, share the timeline of how I responsibly disclosed it to Uber‘s security team, and discuss some key lessons and best practices for developers when it comes to securely integrating payment systems.

How Uber‘s Payment System Usually Works

First, let‘s walk through how Uber‘s ride payment flow is supposed to work:

  1. Rider requests a ride through the Uber app
  2. Rider selects a payment method (credit card, PayPal, etc.) or chooses to pay with cash
  3. When the ride is completed, Uber charges the selected payment method the appropriate fare
  4. Uber pays the driver their portion of the fare

Behind the scenes, when a card is selected, Uber securely stores the card details. On future rides, Uber charges the stored card without the rider needing to re-enter the card info each time. It‘s a seamless experience for riders.

The Vulnerability That Allowed Free Rides

The payment flow seems straightforward and secure. So where‘s the vulnerability?

It has to do with how Uber validated the selected payment method when a new ride request was made. In the normal flow, the rider would select a valid payment method from their list of stored cards. Uber would then use the ID of the stored payment method when creating the ride request.

But here‘s where things went wrong. Uber‘s ride request API allowed me to pass in an arbitrary string as the payment_method_id:

POST /api/dial/v2/requests HTTP/1.1
Host: dial.uber.com

{
  "start_latitude":12.925151699999999,
  "start_longitude":77.6657536,
  "product_id":"db6779d6-d8da-479f-8ac7–8068f4dade6f",
  "payment_method_id":"abc123" 
}

As you can see, I passed in the string "abc123" as the payment_method_id, which is not a valid payment method in my account. Shockingly, Uber accepted this ride request and did not validate that "abc123" corresponded to a valid payment method on file!

The ride would then proceed like normal. But at the end of the ride, when it came time to actually charge the payment method, Uber would realize "abc123" wasn‘t valid. But from the rider‘s perspective, it didn‘t matter – I‘d already gotten a free ride.

It‘s mind-boggling that a company like Uber, transporting millions of riders and processing millions of dollars in payments every day, would have such a gaping hole in their payment validation. With just a few lines of code, anyone could have exploited this to get unlimited free rides.

Stealing Rides Around the World

To demonstrate the severity of the bug, I got permission from Uber‘s security team to actually exploit it. I fired up my Uber app, requested a ride, and passed an invalid string as the payment_method_id:

{
  ...
  "payment_method_id":"lol1234",
  ...  
}

I took a 30 minute ride across town. When the ride ended, I checked my credit card statement and Uber account – no charge! I had just gotten a completely free ride by exploiting this bug.

Map of free Uber ride

I began testing this in other cities and countries. Each time, I was able to exploit the vulnerability to ride for free. I could have taken free rides all over the United States, London, Paris, Tokyo, Dubai – anywhere that Uber operated.

Reporting the Vulnerability to Uber

As an ethical hacker, my goal is to identify vulnerabilities and notify companies so they can patch the holes before malicious actors find them. I have no interest in exploiting bugs for personal gain.

So as soon as I confirmed the free ride vulnerability was legitimate, I submitted a detailed report to Uber‘s bug bounty program. Here‘s the timeline of what happened:

  • Aug 22, 2016: Initial vulnerability report submitted to Uber
  • Aug 26, 2016: Uber requests more info about the bug
  • Aug 27, 2016: I submit video proof-of-concept of free rides
  • Aug 29, 2016: Uber confirms the vulnerability
  • Sep 1, 2016: Uber deploys a patch to production servers
  • Sep 10, 2016: Uber awards me a $5,000 bounty

Uber security hall of fame

Fortunately, Uber was very responsive and quick to fix this critical flaw in their payments. They pushed a patch in a matter of days. As a thank you for responsibly disclosing the bug, I was awarded a $5,000 bounty and added to Uber‘s security researcher Hall of Fame.

Lessons for Developers

As a developer myself, discovering this bug was a stark reminder of how easy it can be to introduce vulnerabilities when integrating payment processing. One small oversight in validation can lead to massive financial losses.

Here are some of the key lessons I learned and best practices I now recommend to fellow developers when working with payments:

1. Always validate payment method IDs

Uber‘s critical flaw was allowing ride requests with arbitrary payment method IDs without validating they mapped to a real payment instrument. Whenever you accept a payment method ID from a client, you must validate on the server-side that it belongs to the current user and is valid.

2. Use server-to-server requests to the payment gateway

Uber‘s mobile app was sending the payment_method_id directly to Uber‘s API. A more secure approach is to use server-to-server communication between your backend and the payment gateway. The client app should merely send a payment_intent to your server. Your server can then look up the corresponding payment method for that user and pass it securely to the payment processor.

3. Verify payment success on the server

After calling the payment gateway, you must verify the response indicates the payment actually succeeded. Don‘t just assume it went through! Verify the HTTP status code and response body match the expected values for a successful payment.

4. Compare the actual and expected amounts

A common attack is for a malicious user to change the payment amount client-side to be lower than the expected amount. As a safeguard, your server should calculate the expected amount for the goods/services and compare it to the actual amount paid. If they don‘t match, the payment should fail.

5. Validate the currency

Similarly, make sure the payment currency matches your expected currency. Don‘t let an attacker pay in a different currency with a more favorable exchange rate.

6. Use a checksum hash

To prevent tampering of client-to-server communications, have your server generate a checksum hash of the payment data and any other relevant data (user ID, amount, currency, etc). Pass this hash to the client along with the data. On future requests, the client should pass back the data and the hash. Your server can then re-calculate the hash and compare it to the client-provided one. If they don‘t match, the data has been tampered with and the request should be rejected.

Conclusion

Finding this Uber vulnerability was an important reminder of how critical it is to thoroughly validate and secure payment flows. A single flaw could have allowed anyone to get unlimited free rides from the world‘s biggest ride sharing company.

Fortunately, Uber‘s security team quickly patched the bug and generously paid a bounty to thank me for responsibly disclosing it.

As a developer, I‘m now much more cognizant of the common pitfalls when integrating payments. I hope the best practices I shared will help you securely process payments in your own applications. It just takes one oversight to expose your company to massive losses. Be diligent in your validation and remember – never trust user input!

Stay safe and happy (ethical) hacking!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *