Since the tale laid here is real, I’ll refrain from using specific names or locations. It might put me in some awkward situations. If you’ve been through something similar, it may sound familiar. That said, 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 people try to score free services through exploitations of lousy systems. This is meant to help secure those that are exposed.
My hope is that developers become more vigilant with systems, especially ones that involve 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. A negative financial incentive to not travel if you will.

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” it said.

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 a /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 for 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 hadn’t seen the pile of crap 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 and general approaches

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 - a) 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. b) 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 percentage of discount multiple times. Here’s a famous one found on Dropbox a few years back.

To sum things 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 it 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.