[Symfony] Coupling sfDoctrineGuardPlugin and fail2ban

Recently I created a quite sensible application using symfony 1.4. As many people, I chose sfDoctrineGuardPlugin as authentication/password recovery/account managing system. But even with this piece of software, you are still vulnerable to bruteforce attacks.

I though of hacking sfDoctrineGuardPlugin's code to create some rules and attack detections. But those systems are quite tricky to create and calibrate. It must not be too aggressive nor too permissive. I wanted something that can bail out someone after $some tries in a $certain time. That implied that I would have to record every try with the current time, and calculate a ratio within which a user can login or not. I seemed simple, then it got too complicated.

Then I remembered that fail2ban was installed on my server, which served that kind of purpose for ssh and various other systems. I tried to figure out a way to combine those two applications. Fail2ban reads the system's logs to determine if a connection must be bailed out or not.

So I modified sfDoctrineGuardPlugin to send messages in those logs. Each time that someone was using a wrong password, an entry in the log will be sent. For that, I had to modify plugins/sfDoctrineGuardPlugin/modules/sfGuardAuth/lib/BasesfGuardAuthActions.class.php .

if ($request->isMethod('post'))
    {
      $this->form->bind($request->getParameter('signin'));
      if ($this->form->isValid())
      {
        $values = $this->form->getValues();
        $this->getUser()->signin($values['user'], array_key_exists('remember', $values) ? $values['remember'] : false);
 
        // always redirect to a URL set in app.yml
        // or to the referer
        // or to the homepage
        $signinUrl = sfConfig::get('app_sf_guard_plugin_success_signin_url', $user->getReferer($request->getReferer()));
 
        return $this->redirect('' != $signinUrl ? $signinUrl : '@homepage');
      }
      else
      {
        $values = $request->getParameter('signin');
        openlog('mySystem', LOG_PID, LOG_AUTH);
        syslog(LOG_WARNING, sprintf('Failed password for %s from %s port 80 mySystem', $values['username'], $request->getRemoteAddress()));
        closelog();
      }
    }

The diff :

--- BasesfGuardAuthActions.class-orig.php	2011-05-07 14:01:46.907823042 +0200
+++ BasesfGuardAuthActions.class.php	2011-03-12 11:07:26.107752306 +0100
@@ -41,10 +41,17 @@
         // or to the homepage
         $signinUrl = sfConfig::get('app_sf_guard_plugin_success_signin_url', $user->getReferer($request->getReferer()));
 
         return $this->redirect('' != $signinUrl ? $signinUrl : '@homepage');
       }
+      else
+      {
+        $values = $request->getParameter('signin');
+        openlog('mySystem', LOG_PID, LOG_AUTH);
+        syslog(LOG_WARNING, sprintf('Failed password for %s from %s port 80 mySystem', $values['username'], $request->getRemoteAddress()));
+        closelog();
+      }
     }
     else
     {
       if ($request->isXmlHttpRequest())
       {

Now let's configure Fail2ban. I created I file called mySystem.conf in /etc/fail2ban/filter.d :

# Fail2Ban configuration file
#
# Author: Yoda-BZH
#
# $Revision: 728 $
#
 
[INCLUDES]
 
# Read common prefixes. If any customizations available -- read them from
# common.local
before = common.conf
 
 
[Definition]
 
_daemon = mySystem
 
# Option:  failregex
# Notes.:  regex to match the password failures messages in the logfile. The
#          host must be matched by a group named "host". The tag "<HOST>" can
#          be used for standard IP/hostname matching and is only an alias for
#          (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values:  TEXT
#
#failregex = ^%(__prefix_line)s(?:error: PAM: )?Authentication failure for .* from <HOST>\s*$
#            ^%(__prefix_line)s(?:error: PAM: )?User not known to the underlying authentication module for .* from <HOST>\s*$
failregex =  ^%(__prefix_line)sFailed (?:password|publickey) for .* from <HOST>(?: port \d*)?(?: mySystem\d*)?$
#            ^%(__prefix_line)sROOT LOGIN REFUSED.* FROM <HOST>\s*$
#            ^%(__prefix_line)s[iI](?:llegal|nvalid) user .* from <HOST>\s*$
#            ^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers$
#            ^%(__prefix_line)sauthentication failure; logname=\S* uid=\S* euid=\S* tty=\S* ruser=\S* rhost=<HOST>(?:\s+user=.*)?\s*$
#            ^%(__prefix_line)srefused connect from \S+ \(<HOST>\)\s*$
#            ^%(__prefix_line)sAddress <HOST> .* POSSIBLE BREAK-IN ATTEMPT!*\s*$
#            ^%(__prefix_line)sUser .+ from <HOST> not allowed because none of user's groups are listed in AllowGroups\s*$
 
# Option:  ignoreregex
# Notes.:  regex to ignore. If this regex matches, the line is ignored.
# Values:  TEXT
#
ignoreregex =

Make fail2ban use this filter in /etc/fail2ban/jail.conf

[mySystem]
enabled  = true
port     = 80
filter   = mySystem
logpath  = /var/log/auth.log
maxretry = 5

Now, just reload Fail2ban using /etc/init.d/fail2ban restart and that's it !

Nota: Yeah it would be better to override the executeSignin method in a separate class, but I was a bit lazy.

Haut de page