Protect your Flask applications using CrowdSec - The open-source & collaborative IPS
January 4th, 2022
3 mn read

Protect your Flask applications using CrowdSec

At CrowdSec we want our users to protect themselves regardless of the tech stack they use. The simplest way to do that is to implement threat remediation at the network level, with a firewall bouncer. CrowdSec bouncers can also be set up at the upper levels of an applicative stack:  web server, CDN, and in the case we are looking at here, the business logic of the main application itself.

In this post, we’re going to learn how web applications developed using Python can be protected by CrowdSec at the application level.

Remedying directly in your application can be helpful for various reasons:

  • It allows you to provide a business-logic answer to potential security threats
  • It gives you a lot of flexibility about what and how to do when a security issue arises

We are going to deploy a Python bouncer which will integrate with a flask application. This application will then be able to apply captcha and ban remediations to the IPs suggested by CrowdSec. A reference flask app protected by CrowdSec is available here.

Before we begin, here are the prerequisites:

Prerequisites

  • Flask application running.
  • Google reCaptcha Site key and Secret key. See instructions here.

In the following steps, we would be creating a CrowdSec client and a Flask middleware. This middleware would be registered with your flask app. For every incoming request, the middleware will take any action(ban, captcha) if CrowdSec has a decision against the IP.

Creating CrowdSec Client in flask app:

We will first create a client which polls CrowdSec to keep track of the latest (IP, remediation) pairs. This client is provided by the ‘pycrowdsec’ library.

pip install pycrowdsec # install pycrowdsec

Then in your application code before you create the flask app object, instantiate the client via 

sudo cscli bouncers add flaskBouncer

Creating the ban view:

We will create a view where all the IPs which are suggested to be banned by CrowdSec will be redirected to. They won’t be able to access your web app.

from flask import abort
@app.route("/ban")
def ban_page():
    return abort(403)

Creating the Captcha view:

IPs that are suggested to get captcha by CrowdSec will need to be:

  • Redirected to captcha view if they haven’t solved captcha very recently
  • Solve captcha correctly
  • Redirected back to the original view they were trying to access. 

We will be using Google’s reCaptcha to provide and verify the captcha. So this would be a lot simpler.

First, create an HTML template to render the captcha. Let’s name it  “captcha_page.html”.

<html>
  <head>
    <title>reCAPTCHA</title>
    <script src="https://www.google.com/recaptcha/api.js" async defer></script>
  </head>
  <body>
    <form action="" method="POST">
      <div class="g-recaptcha" data-sitekey="{{public_key}}"></div>
      <br/>
      <input type="submit" value="Submit">
    </form>
  </body>
</html>
from flask import request, render_template, session, redirect, url_for, abort

def validate_captcha_resp(g_recaptcha_response):
“””
Helper function which returns True if solved captcha is correct
“””
    resp = requests.post(
        url="https://www.google.com/recaptcha/api/siteverify",
        data={
            "secret": "GOOGLE_RECAPTCHA_PRIVATE_KEY"
            "response": g_recaptcha_response,
        },
    ).json()
    return resp["success"]

Valid_captcha_keys = {}
@app.route("/captcha", methods=["GET", "POST"])
def captcha_page():
    if request.method == "GET":
        return render_template(
            "captcha_page.html", public_key="GOOGLE_RECAPTCHA_SITE_KEY"
        )
    elif request.method == "POST":
        captcha_resp = request.form.get("g-recaptcha-response")
        if not captcha_resp:
            return redirect(url_for("captcha_page"))

        is_valid = validate_captcha_resp(captcha_resp)
        if not is_valid:
            return redirect(url_for("captcha_page"))

        session["captcha_resp"] = captcha_resp
        valid_captcha_keys[captcha_resp] = None
        return redirect(url_for("index"))

# Replace “GOOGLE_RECAPTCHA_PRIVATE_KEY” and “GOOGLE_RECAPTCHA_SITE_KEY” with your own keys. 

Registering CrowdSec middleware:

Finally, we create a middleware to combine the work of the previous steps. This middleware, again, is provided by the `pycrowdsec` library.

from pycrowdsec.flask import get_crowdsec_middleware

actions = {
    "ban": lambda: redirect(url_for("ban_page")),
    "captcha": lambda: redirect(url_for("captcha_page"))
    if session.get("captcha_resp") not in valid_captcha_keys
    else None,
}
# app here is the flask app object.
app.before_request(
    get_crowdsec_middleware(actions, crowdsec_client.cache, exclude_views=["captcha_page", "ban_page"])
)

Now test it!

Testing ban:

Let’s ban some IPs that you have access to.

sudo cscli decisions add –ip <YOUR_IP>

Try accessing the flask app from this IP, you should be redirected to 403 view.

Testing Captcha:

Let’s first unban our IP and then add a decision to  captcha the IP

sudo cscli decisions delete –ip <YOUR_IP>
sudo cscl decisions add –ip <YOUR_IP> –type captcha

Try accessing the flask app from this IP, you should be redirected to captcha view.

After solving the captcha you’ll be redirected to the original view. 

Conclusion

In summary, we added CrowdSec’s protection to our flask app. This was done by integrating a middleware that did the work of checking if the IP is malevolent and then taking appropriate action against it.

If you have any ideas, feedback, or suggestions, feel free to contact us using our community channels (Gitter and Discourse)

You also like

January 11th, 2022
4 mn read

CrowdSec on Discord!

It’s finally happening. We’re opening our own Discord server as a replacement for our Gitter. We’ll be keeping our… Read More

AFEB9F8A-65D8-49C4-BF47-4958C484D8C8

Let's make the internet safer together

AFEB9F8A-65D8-49C4-BF47-4958C484D8C8
Download v1.2.2