I found a way to alter a premium subscription service price and bought it for a penny. This is how I did it.
Whenever I’m bug hunting on a target that takes payments, I always try to buy something using a test credit card number as described in my write-up on Cracking Encrypted Credit Card Numbers. When the payment fails (or succeeds!) I look through all of the requests and responses for the entire process, searching for anything which looks interesting.
While hacking on a private SaaS bug bounty program, I went through the new user sign-up funnel and entered a test card at the payment page. The payment failed, of course, and so I went back to look at all of the requests and responses captured in Burp through this process. The subscription I had attempted to purchase via the website was the platform’s premium service at £19.99 per month.
I noticed that the request to buy with my credit card transaction token included the string “package_id”:”target:payments:packages:1205″. In the response I received the string “term”:”monthly”,”price”:{“amount”:1999,”currency”:”GBP”}. The amount was the monthly value of the subscription, £19.99 in pence. It also confirmed that the subscription was monthly. The thing that caught my attention was the numerical ID in the package_id parameter. I was curious to see if I could find any other valid package IDs and, if so, what prices and payment terms they were linked to.
Using Burp Suite Intruder I repeated my failed payment request 10,000 times, starting from “target:payments:packages:1” and incrementing the ID up to “target:payments:packages:10000”. While many of the requests errored out with a “no package found” message, I was able to find hundreds of past and future prices still listed in their pricing table,
The most expensive was £49.99 on a monthly term. The cheapest was £0.01 on both a monthly and annual term. Bingo. Now all I needed to do was confirm that the payment would actually work and I could create a real subscription with the identified package number.
Armed with a package_id number priced at £0.01 annually, I went back through the web-based new user sign-up process and enabled Burp Suite proxy intercept just as I was about to enter my payment details. This time, however, I entered my real credit card details and manually forwarded on the resulting requests until I saw the string “package_id”:”target:payments:packages:1205″ again. At this point, I changed the SKU 1205 for the annual £0.01 SKU and hit forward, then switched off Burp Suite proxy intercept.
To my surprise, it worked and I had just bought a £239.88 annual subscription for a penny. I went back into my account and reviewed my subscription and confirmed that I was a premium subscriber. The end date for my contract was 12 months later.
The package_id for this SaaS is the digital equivalent of a barcode SKU (stock-keeping unit) on the side of a box. By listing multiple SKUs and changing the sign-up form to switch between them, the target company can change prices at any time without losing the ability to renew subscriptions at the previous price.
In this case, it looks like the target company created a monthly and annual subscription SKU priced at £0.01 each to allow employees to test the live payment system after code deployments. While this is quite common, these penny SKUs are not usually publicly visible or easy to guess and are reserved for employee usage.
This bug has the potential of a big financial impact in terms of lost revenue. If this became public knowledge, all of their existing customers could cancel their subscriptions and renew for £0.01 costing the target £239.87 per customer per year. New customers could sign up for a penny losing all future revenue until the SKU is disabled. The 1p charge probably doesn’t even cover the credit card transaction fee.
The best way to fix this while keeping the ability for testers to test their payments system is to add a third value to each SKU for Enabled: True or False. That way, the team can enable the £0.01 SKUs for testing, make a purchase, then set them back to False in the application database so that they cannot be abused by people like me.
The payment processing code would need to be updated to read the value of the Enabled property from the database and cancel any transactions trying to use a disabled SKU, advising Stripe not to complete the payment for the presented token.