How NOT to Secure Web Payment Systems

Written by omerxx | Published 2021/06/24
Tech Story Tags: security | appsec | bugbounty | webdev | devops-security | engineering-security | cybersecurity | ethical-hacking

TLDR This is not a new or sophisticated hack. This is developers' laziness at its best. This is meant to help secure those that are exposed. But testing at home for these kinds of bugs iscontributing to the greater good of the internet. Some of them may not have the skills or energy to dig into the system that holds personal medical records and results in other areas that can be easily exploited in areas that are easily exploited, such as medical records. The story is real, but I'll refrain from using specific names or locations.via the TL;DR App

Since the tale laid here is real, I'll refrain from using specific names or locations. It might put me in some awkward situation. If you've been through something similar, it may sound familiar.
Having said that, the identifying details have little to do with the story itself. It is not a new or sophisticated hack. This is developers' laziness at its best. Assuming users are all non-technical sheep. For the most part, they're right, but one greedy naughty user can change the picture.
Disclaimer:
  • I did not actually use what I got and reported the bug.
  • I do not suggest scoring free services through exploitations of lousy systems. This is meant to help secure those that are exposed.
  • I hope that developers become more vigilant with systems, especially those involving the personal and medical information of literally everyone that crosses their country's border.
  • I do think, however, that testing at home for these kinds of bugs is contributing to the greater good of the internet.

Once upon a time

I've been traveling a lot. More so during covid than "normal" times, to get the vaccine, medical checkups, and other personal obligations. Traveling became this huge payment burden annoyance where you have to pay for tests before boarding and right after landing. Oh, and all over again when traveling back.
While I don't have any criticism on the necessity of the test (nor have I got the professional background to comment on it), the pile of tests I had to undertake and pay for started to feel like a tax.
Throughout the last year, I've been traveling to my home country and had to take a free PCR test right at the airport. Scheduling the appointments in an online dedicated system using the same discount code every time:
Free1
.
A couple of days ago, while planning for another trip home, I was going over the same long routine. Filling a passenger locator form (both for out and inbound travel - 🤷) and pre-ordering my Airport Covid PCR test.
To my surprise, the test name has changed from "Covid PCR - mandatory" to something like "Covid PCR - $80 mandatory".
*Wait, what?*
Yep, it seems that from now on, an $80 fine (excuse me, charge) per person is a must if you want to leave the airport. The other option is paying with cash $100 upon arrival (some deal ay?).
I retried the same code that used to work over the last year.
Free1
and "Apply". The screen reported a red error message. "Invalid Voucher Code".
I did what any cybercriminal would do at that point - retried the same thing 3-5 times (genius). When that didn't work, I tried
Free2
,
Free80
, and a few others.
When I checked the outgoing request, I found that it was being sent to
/api/validate-voucher
endpoint. It returned a strange HTTP error;
422 (Unprocessable entity)
and a
{statusCode: 422, message: "Invalid Voucher or Appointment ID"}
, hmmm.. What "appointment"?
I was trying to apply a discount code. Upon bad response, the UI deletes the code entered, effectively "preventing" the use of the code. The fact that it actually got deleted could have been a good user experience but a lousy prevention mechanism at the same time. "Why not test it myself?" I thought. 😈
BurpSuit to the rescue!
I fire up Firefox and proxy everything through Burp.
Going through the outgoing requests, I see metrics and more metrics, 3rd party trackers, and whatnot. Sometimes I wish I haven't seen the pile of crap these websites make us send. Forwarding everything that's not interesting until WHOOPS, what's that?
A
POST
request holding the JSON data:
{
  appointmentId: "12345",
  voucher: ""
}
There we go - how about sliding the previously valid voucher through?
But wait, this sounds familiar.. earlier, when I just tried entering my old discount code, I got an "
Invalid appointmentId
...", could the same system actually be used for both purposes - validating the codes and creating appointments?
It feels off. A hunch tells me I'm going to see something funny.
I edit the object on the fly and forward it with burp:
{
  appointmentId: "12345",
  voucher: "Free1"
}
And what do you know, a huge shiny green label appeared on the screen: "PAID". Five seconds later, the screen reloaded, and a new QR with an appointment was generated.

Moral of the story

Users are not dumb. Some of them may not have the skills or energy to dig in. But if a system that holds personal medical records and results is this easily exploited, what happens in other areas?
Businesses are founded and run to make money. As such, they would always serve as a target for exploits. Let alone for a service that up until recently was free and is now forced upon all returning passengers, preventing them from going home.
One must check each and every incoming request as if it's coming from the wild (it actually is!). Employ zero trust

Ways to handle

First and foremost, assume you're going to get exploited. Much like checking if the front door is locked, applications should make sure users can only do what they're intended to.
Checking whether a discount code is valid in the UI? Excellent, what about the backend? The browser lives in userland, where the developers don't have control over how the application is manipulated or misused. One must check each and every incoming request as if it's coming from the wild (it actually is...). Employ zero trust.
On the same note - rate-limit incoming requests; when I couldn't apply my old
Free1
discount code, I tried
Free2
, then
Free3
and so on with different permutations. It seemed odd that I can just keep pushing codes through and learn whether they're valid or not, so I did what anyone would; I wrote a script. Tried a few dozens of them just for fun.
Although nothing came up, I had fun fuzzing again with ffuf, and I just learned that the system doesn't rate-limit requests. With enough time at their disposal, different users can run a similar script for hours and days. This is bad for two main reasons :
  • Servers are bombarded with requests. In case it was built correctly, it scales up to serve brute force attacks (funny on its own). In the worse case, it crashes.
  • At some point, a valid discount code will pop up.
Another "discount code friendly" exploit (which I hadn't tried after getting a full price reduction), is a race condition where a user can re-apply the same discount code, usually giving a discount percentage multiple times. Here's a famous one found on Dropbox a few years back.
To sum up, systems' security should be high on the priority list - if not for revenue, at least for customer's privacy. Web applications can be "hacked" in more than one way, and the ways might be simple. My go-to approach when testing my own apps is thinking like a malicious actor.
Not necessarily a top-notch security expert, which I'm far from, but a user with minimal skills and a couple of simple wishes. Like getting a free service, or a discount I do not deserve.
Thank you for reading 🖤
Feel free to reach out with questions or comments.

Written by omerxx | $(which nvim)
Published by HackerNoon on 2021/06/24