Protect a Symfony2 project against bruteforce attacks

To protect my brand-new blog against bruteforce attacks, I was about to create a bundle, like I did for symfony1 with the sfAntiBruteForcePlugin. Then, during my server building, I've found a tutorial that shows how to use Fail2ban to prevent SSH bruteforce attacks. For your information, Fail2ban watches log files and looks for abnormal activities. When it founds one of them, by default, it blocks the client IP during 10 minutes using iptables, and sends you an e-mail.

Custom filters can be added to Fail2ban, that's what I did to protect the login page of my blog admin zone. Here's how.

First of all, you need to know where the web server access logs are. I use nginx, so they're here: /var/log/nginx*/*access*.log.

Then, you need to know what's going on when a login attempt is made. I use the FOSUserBundle, so there's a POST request that is made on the /login_check URL. It appears in the access log this way: - - [29/Aug/2012:11:36:30 +0200] "POST /login_check HTTP/1.1" 302 ...

We don't need to know if the authentication has been successful or not, we consider that if the page is reached X times during a 10 minutes lapse, it's an attack. So we have all the pieces we need tout write our filter. Let's create this file: /etc/fail2ban/filter.d/nginx-login.conf.

# Blocks IPs that access to authenticate using web application's login page
# Scan access log for POST /login_check
failregex = <HOST> -.*POST /login_check
ignoreregex =

You can see that the POST requests on URL starting with "/login_check" are watched. The last thing to do is to modify the Fail2ban configuration to use this filter. Add the following configuration at the end of /etc/fail2ban/jail.conf:


enabled  = true
filter   = nginx-login
port     = http,https
logpath  = /var/log/nginx*/*access*.log
maxretry = 5

So we tell the log files to watch, the filter to use, and the number of allowed attempts.

Don't forget to reload the service to take the new configuration into account: sudo service fail2ban force-reload. Check the logs to be sure that it's OK: /var/log/fail2ban.log (if your regex is invalid, it will be written here). Then try to bruteforce your site (warning, you'll be banned for 10 minutes if it works!).

Here we are, you feel better now!

Tags: Symfony, bruteforce, security, nginx

HTTP cache management with Symfony2

Before reading this post, you should read the Symfony documentation about cache. As usual, it's clear, and it allows me to not have to explain everything :).

For this blog, the strategy I've chosen is this: to display a post, I use the its last modification date to define the "Last-Modified" HTTP header. If I see through the request that the client has already this version in the cache, then I can stop the process and return the 304 "Not Modified" HTTP code. Otherwise, I continue the process to serve the page to the client, so he can store it in his cache.

So my controller looks like this:

public function displayAction(Post $post)
    $response = new Response();

    if ($response->isNotModified($this->getRequest())) {
        return $response; // this will return the 304 if the cache is OK

    // Do some stuff here...

    return $this->render('...:display.html.twig', array(
        'post' => $post,
        // ...
    ), $response);

The first 3 lines allow to define the strategy used for cache management. Note the setPublic() call to define that the cache is the same for all the users, and not private to the current user. It allows the shared caches (proxy caches and gateway caches) to also store the cache. Thereby, if user A has already displayed a post, user B can use the cache generated by user A, without generating the whole page. In my case, Symfony2 is the gateway cache.

Then I call the $response->isNotModified() method, giving the request as parameter. So it's the framework that decides if a 304 code must be returned or not, by comparing the "Last-Modified" header date (we've just defined it) with the "If-Modified-Since" header date provided by the client (the date of the post version he has in the cache). If the post hasn't been modified since it has been cached, we just have to return the response object, that represent the 304 "Not Modified" code.

Otherwise, we continue the controller process. In my case, I just need to prepare the comment form. Then I render the template. Don't forget to give the $response object as 3rd parameter of the $this->render() method, so the Last-Modified header will be added to the response; if you forget it, Symfony will create a default Response object, without any cache control header, so the clients will never store the page in the cache!

I'll soon talk about the edge side includes to handle the blog left column caching strategy, and about the event listeners to handle the post modification date, taking the comments into account...

See you later!

Tags: Symfony, cache is now powered by Symfony2

Here is the new blog! Many changes: new hosting, new look'n'feel, new engine, and new topic: Symfony2 time! It was high time to rejuvenate, now it's done.

About the hosting, I was on a low cost OVH shared hosting: easy to use, but too restrictive when you want to have some fun... So I've switched to a Virtual Private Server (Kimsufi / OVH), small version, (well advised by @bourvill, OVH expert ;)). With few RAM (512MB), I've chosen nginx (+ php-fpm) instead of apache, and I really don't regret! It's light and fast, as you can see! For the rest of the "sys admin" part, I'm not an expert, so I've followed this tutorial (FR).

About the look'n'feel, it's simple. You might have recognized the twitter bootstrap. Simple, clean, nice: I like. And W3C valid!

About the backend, I've said goodbye to wordpress, and hello to Symfony2 of course! As a basis, I've used the tutorial from le site du zero (FR), then I've added the functionalities I need: multilingual management, cache management, admin part, RSS feed, ... I'll explain some of these points in the next posts.

And if you notice any bug, think about enhancements or anything else, please tell me on github!

Tags: Symfony, ovh, github, nginx