Warning: the application of the method described in this article is very likely illegal and unethically for sure. The purpose of this article is to show how weakly protected online lucky draws are easy target for exploits, the possible consequences for the company who runs these draws and strategies against this. So kids: don’t do that at home and developers: do better!
Last night I’ve got a bunch of codes for a lucky draw when buying a pack of chewing gum. I have to admit that these lucky draws somehow attract me: What are the prizes? What are the odds? Will I win the jackpot this time? And: any chance to – let’s say – increase my odds a little?
A common schema for these online lucky draws is:
- you buy something and you get a voucher with a code (Note: Often participation is somehow also possible without a purchase. I think that’s by law in certain countries otherwise – that’s just my interpretation – this could be considered as some hidden form of gambling)
- Online Phase 1: you go to the website, enter the code and are told whether the code is valid/invalid and/or whether your code qualifies for the the lucky draw (for whatever reason)
- Online Phase 2: you are asked to enter some personal information (name, email, day of birth) and your draw is logged in the system ultimately the main reason for these draws is to collect consumer information, so think about twice before you participate
After dinner – good consumer as I am – I opened the website, entered a code, my name and email address. Then I’ve entered a few more codes and recognized that the validation of the codes was nearly instant. Out of curiosity I opened the network inspector of my browser, entered a random code and got this:
You give me a API endpoint with the code as ID and a 404 response on an invalid code? Hm, that should make things easy… (I can’t tell why but these are the moments where something in my brain clicks and I transform from Dr. Jekyll to Mr. Hyde.) Note that there’s nothing wrong with the implementation in my opinion. It just makes it a very quick thing to code a “valid codes extractor”. (The real problem is something else. I’ll come to that in a minute.)
We have a 6 character code, containing uppercase letters and numbers. That gives us 26 + 10 = 36 possible characters and 36^6 possible combinations. 2’176’782’336 may look like a big number but in times of fast computers and internet connections a brute-force attack would have quite a chance of success here. We probably won’t be able to get all valid codes but at least a bunch of them if they (the developers) didn’t add protection.
Scanning 1000 possible codes gave me 8 potentially valid codes. Network view and the console didn’t show any sights of errors (besides the 404s) or blocking. Took one of the codes, entered on the website, worked! No sights of protection against my mini request flood. I was able to check ~30 codes/sec and got 0.45 valid codes/sec. Which would give me ~1600 valid codes/hour. Compared with the estimated total of ~32 mio valid codes that isn’t much but at least something which can help to increase my odds of winning.
One may argue that such an ‘attack’ is really easy to spot and yes it is but the issue I see is not so much about the one who tries to get and use the codes but the reputation risk for the company when other participants of the lucky draw get a “Code already used error”. I’m not saying that’s impossible to clean-up the mess (appropriate logging given) but this will cost the company time and money for sure. That’s the other risk.
So what to do to prevent such a case or to be able to recover from it?
- Log IP-Address on every request to validate the code AND to enter the draw (see Online Phase 1/2 above)
- Count requests (per time) from the same IP, block/ban above a certain threshold
- Matching the ‘harvesting’ attempts with entering the draw by IP is surely not enough. One could easily harvest using on IP and entering with an other. So both phases need to be monitored for ‘unusual’ patterns.
- Make the code longer. Yes it’s a trade-off between convenience and security but just 1 character more would give a total of ~78 billion possible codes and a hit rate dropping from 0.45 to 0.0125 valid codes / second (given that the total number of valid codes stays the same)
- User lower and upper case letters if your backend technology supports it (e.g. “aaa” and “AAA” are the same for mySQL) which will give you 57 billion codes for the same length of 6 (the rest of the math you can do on your own)
- And the safest of all: Just don’t bother people with online lucky draws. (To be discussed between Marketing and IT)
Nothing fancy. Didn’t bother to make it much flexible (e.g. use recursion). It’s not the point anyways. Note that while script is running only Firefox will show any output.
- Online Lucky Draws should be protected against attempts to find a significant number of valid codes and to enter codes in a frequency beyond human’s capabilities
- Even when an “attacker” can be easily identified & banned such an attack may lead to bad reputation as honest participants will find their codes invalid and cleaning up that mess will cost time and money
- As an absolute minimum every attempt to validate and to enter a code should be logged with the IP
- One proper protection measure is to limit the max. attempts to verify or enter a code per time unit
- Length of codes and possible characters must be balanced between security (IT) and convenience (Marketing)
May the luckiest win…