I'm fully aware my title sounds like a clickbait video from YouTube but I assure you it kind of does sound like that, huh?
A couple of weeks ago I created an app for Nintendo Switch owners to easily share their friend code with their friends. It's called Ninny Code!.
I received an email from Heroku, where I deployed the app, saying that I've exceeded the total amount of rows I'm allowed on the free tier.
The database DATABASE_URL on Heroku app ninnycode-b has exceeded its allocated storage capacity. Immediate action is required:
"The database contains 22,360 rows, exceeding the Hobby-dev plan limit of 10,000. INSERT privileges to the database will be automatically revoked in 7 days. This will cause service failures in most applications dependent on this database.
To avoid a disruption to your service, migrate the database to a Hobby Basic ($9/month) or higher database plan:
https://hello.heroku.com/upgrade-postgres-c#upgrading-with-pg-copy
If you are unable to upgrade the database, you should reduce the number of records stored in it."
In a panic, I quickly deleted all of the records. Yes, you read that right. I delete everything on complete accident. Ugh! That lesson was quickly learned the wrong way.
With my records at 0, a few hours later it was attacked again. And again, and again, and again. All day by somebody bored at home during the quarantine.
I was forced to shut the app down which is unfortunate because I use the app to show future employers. After a bit of research, I found an easy way to stop this mad man from messing with my app!
Enter Flask Limiter. This Python package ensures that an API endpoint can only be called up to a specified limit from the same IP address. So in my POST
endpoint I have set a limit of 5 per hour and I'm happy to say it has stopped!
Here is a very quick implementation to show you how easy it is to get set up in your Flask projects:
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
@app.route('/api', methods=['POST'])
@limiter.limit("3 per hour")
def post_user():
# create user
# custom 429 HTTP status
@app.errorhandler(429)
def ratelimit_handler(e):
return {"Error": "Too many requests. Please try again later."}, 429
It's that easy! You'll see at the bottom I created a custom 429 status code because I'm using React to connect to my backend. Normally, Flask Limiter will create a bare-bones page with an h1 and p tag telling you you've reached the limit. If you try to create more than 5 users per hour on Ninny Code!, you'll see my solution is a bit more elegant!
Top comments (6)
So if I understand, this is not a case of your app having some kind of vulnerability, but the attack was to bomb it with legitimate requests? I am curious to know what the attacker's rows looked like, were they garbage data?
Yep, they were just random alphanumeric characters that passed my POST validations.
Using user input as actual filenames is still a terrible idea. Consider to use an internal key or hashed string as filenames.
Thanks for sharing. I learned new thing today. Keep sharing
I second you
Nice.
Actually, I remembered 429 from some websites I have scraped.