There is an area in most websites where protections like the ones found on most login pages aren’t present; the box where you type the promo/coupon code. An organization with a strong security posture for login pages will usually have several layers of defense-in-depth in place, such as the following:
- CAPTCHA challenges
- Rate limiting
- IP deny-listing
- Strong password policies that prevent the use of easily guessed values
- Account lockouts, where, after a certain number of failed logins, the user is prevented from making further login attempts for 5, 10, 15, 30, or more minutes.
However, many of these same organizations do not have similar types of protection in place for their promo/coupon code dialogs. By using a program called Burp Suite, it is possible to try hundreds of possible promo codes against shopping websites. I call this technique “CheakSk8ing”.
Shopping Like a Hacker
Generating the Potential Codes
To begin, our hypothetical cheapsk8er would need a text file full of possible promo/coupon codes.
The Github user, sbrws, has a list of 185,930 coupon/promo codes that you can download here.. I suppose just about any Artificial Intelligence driven chat bot could be used to generate a list of promo codes as well.
Most promo codes are a somewhat guessable combination of a buzzword (like “DEAL”, “HOLIDAY”, or “NEWCUST” ) and a number that denotes the percentage off the purchase that the promo code will net the potential shopper (10, 15, 20, 30, 33,). So promocodes usually look like this: DEAL10, HOLIDAY30, NEWCUST40, etc. Using a simple Python, Bash, or PowerShell script, our hypothetical cheapsk8er could quickly generate an extension wordlist based on those parameters.
Regardless of the approach, to action on these potential codes, the cheapsk8er would need to use a tool like Burp Suite to capture requests and be able to replay them instantly.
With Burp, the cheapsk8er could browse to the target in the browser that comes built-in with Burp Suite. Finding the promo code field, the cheapsk8er could enter anything in the promo code text and then send the request to the Intruder module in Burp.
The screen should look something like this, with the raw contents of the Http request displayed:
Within Intruder, the initial promo code can we marked with silcrows (‘§’). The cheapsk8er could then add the PromoCodes.txt file from earlier in the Payloads tab.
Additional features like Grep-Extract and Always Following Redirects would help the cheapsk8er hone in on the correct codes, but they aren’t a necessary part of the attack.
Once the cheapsk8er has all the settings configured, they can run the attack with Intruder.
Intruder will start doing its thing, sending the configured request over and over again, with a different promo code each time.
Analyzing the Results
When the attack finishes, the cheapsk8er can review the responses of the results.
Most of the responses will probably include some indication that the coupon/promo code was invalid.Using the example picture provided, here’s how to read the columns. Note that this skills is transferable to other Burp Suite-facilitated scenarios like password guessing:
A “400” is a “Bad Request”. The site is telling us that the request (the promo code) is bad. What we are looking for is a “200”, which means “ok”, and that the promo code is valid
This is the amount of data being returned in each response. Since the response is the same for every wrong promo code, it looks like most of the Payloads result in a response with a Length value of 290. If we see a response that is anything other than 290, it could be an indicator that the promo code is valid.
This column is named after the value set in the Grep – Extract menu. It shows a value chosen as a stand-out (typically for invalid requests to make sorting easier). So, if it says “Invalid”, the cheapsk8er know that it matches the response to the wrong promo code that we got when we hit the “Fetch Response” button.
It essentially comes down to playing “One of these things is not like the other”. If something other than “invalid” is present in this column, there is have a pretty strong indicator that a promo code is NOT invalid.
Depending on the size of the wordlist used in the payload tab, scrolling down could take a long time, trying to find a promo code that has either a status code of 200, a length that is something other than 290, or a “message” that is something other than “invalid”. The results can be sorted by column for easier analysis.
If we take a closer look at the hypothetical response:
HOLIDAY10 got a response that was unlike ALL of the others.
- The request returned a status code of 200 instead of 400
- The size of the response (length) was much different
- The response didn’t contain the word “invalid”
- Even the very structure of the JSON in the response body is very different from the others.
This is most likely a valid promo code.
At this point, with code in hand, the cheapsk8er could drop the new, potential code into the intended site:
The real lesson here is that there are protections against dictionary-based attacks on web forms, but while they are frequently in place on login pages, they are just as infrequently in place in checkout dialogs. A developer could stop us in our tracks by implementing any or all of the defenses that were mentioned at the beginning of this blog. However, most don’t.. Let’s review those protections from the beginning of this blog:
A developer should throttle down the time to respond to requests when it picks up on suspicious activity, like an unusually large number of requests coming from the same IP address with fractions of a second in between each request.
A developer should implement a time-based lockout after a specific number of failed promo code entries. For example, after 10 failed promo code entries, a 15 minute timer could pop up along with the message “You entered 10 consecutive invalid promo codes! Please wait 15 minutes before entering another promo code”. The “attacker” (or hungry dude) could get impatient and just end up paying full price rather than continue to try to work around the site’s defenses
A developer should implement a CAPTCHA test when a dictionary-based attack is detected. If the attacker wanted to continue blasting promo codes at the site, they would first have to identify all of the traffic lights, fire hydrants, crosswalks, or skateboarding dogs (gotcha on that last one didn’t I? There are no skateboarding dog CAPTCHA tests…yet)
IP deny-listing (A.K.A “Banhammering”)
Developers can even go so far as to black-hole traffic from a certain IP if a dictionary-based attack is detected. It is common for some sites to automatically redirect traffic that is detected as being part of the TOR network because of its association with anonymous (and sometimes illegal) activity while browsing.
Don’t use Easily Guessable Values
“P@ssword1”, “Summer2023!” and “Querty1@” are some of the easily guessable I often try on pentests. A strong password policy that discourages passwords like this by requiring (12, 15, or more characters at a minimum) is a great way to thwart attackers from guessing passwords. Cheapsk8ing could be discouraged by using values that are less guessable than DEAL10, HOLIDAY33, NEWCUST40, etc. However, the organization would need to decide if they are willing to accept this risk because, easily guessed values are also easy to remember. A value like “60b86797-765f-4b1b-a7e5-800ac6beb4dc” is nearly impossible to guess, but this value is far less memorable than DEAL10. Businesses often must balance security with usability, and this is a prime example of that.
This blog post was written by Josh Gatka and Mike Perorazio and based on a presentation given by both individuals at BSides Columbus 2023.