visit
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:
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. 😈
to the rescue!
I fire up Firefox and 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.
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
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 , 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 :Another "discount code friendly" exploit (which I hadn't tried after getting a full price reduction), is a where a user can re-apply the same discount code, usually giving a discount percentage multiple times. .
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 . 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.Also published on .