Using csrf: is this good practice?

170 views
Skip to first unread message

v.

unread,
Dec 18, 2017, 6:18:14 AM12/18/17
to Fat-Free Framework
Hi guys,

I am building a new project with this excellent framework.
I am implementing csrf on my pages. In stead of manually adding it on every single page and in every single controller I thought to do this in my beforeroute function of my standard controller which extends all other controllers ("one controller to rule them all"!)
This seems to work pretty well, but I am uncertain of any unexpected behavior I have not thought about.
Is it good practice to do things this way?
My code is as follows:

index.php:
...
$f3
->set('CACHE', true);
$f3
->session = new Session();
$f3
->run();


Controller (which extends all other controllers):
    function beforeroute() {
       
if( NULL === $this->f3->get('POST.session_csrf') ||                              //for pages with no forms on them
           
((NULL !== $this->f3->get('POST.session_csrf')) &&                           // check is csrf is posted, if so: next line
           
($this->f3->get('POST.session_csrf') ==  $this->f3->get('SESSION.csrf') ) )  // if the csrf is posted, check if it corresponds to the session csrf
           )
       
{                                                       // Things check out! No CSRF attack was detected.
            $this
->f3->set('CSRF', $this->f3->session->csrf()); // Now reset csrf token for inclusion in the page we are about to load
            $this
->f3->copy('CSRF','SESSION.csrf');             // copy the token to the variable
       
}
       
else{                                                   // DANGER: CSRF attack!
            $this
->f3->set('ERROR.text', "Something went terribly wrong!"); // this line does not work
            $this
->f3->set('ERROR.code', 405); // I am not sure if this is the correct HTTP code to use.
            $this
->f3->reroute('/error');
       
}
   
}

In my forms i add:
<input type="hidden" name="session_csrf" value="{{ @CSRF }}" />

This seems to work very well, but is this a good way of handling this?

Also: setting the ERROR message and code does not seem to work. Is this not possible doing it this way?
I always get a 404 error with the above code.

xfra35

unread,
Dec 19, 2017, 3:27:46 PM12/19/17
to Fat-Free Framework
Hi,

There's a flaw in your code: posting the form without sending any token should not validate.

What about the following:

function beforeRoute($f3) {
 
if ($f3->VERB=='POST' && $f3->get('POST.session_csrf')!=$f3->get('SESSION.csrf')) {
    $f3
->error(403);
 
}
  $f3
->CSRF=$f3->session->csrf();
  $f3
->copy('CSRF','SESSION.csrf');
}

As for the correct HTTP header, 403 (Forbidden) seems to be the most appropriate.

NB: you don't need to reroute to /error. $f3->error(403) is perfect. It will just set the correct header, display the error text (403 Forbidden) and die.

v.

unread,
Dec 20, 2017, 4:57:26 AM12/20/17
to Fat-Free Framework
Hi,

Thank you very much for your answer and corrections.
You are right about the error, I should have spotted that!
In the meantime I also found that using the csrf like in my previous post will give unexpected errors when opening pages in tabs and then posting the first tab.
I have modified your solution a bit so that the token is regenerated after every POST request only, I think it is supposed to work this way and it seems to be working fine now.
My code now is:
    function beforeroute() {
       
if( NULL === $this->f3->get('POST.session_csrf') ) // If no csrf is available we need to set it (this will happen on the first page that is accessed)
       
{
            $this
->f3->CSRF = $this->f3->session->csrf();
            $this
->f3->copy('CSRF','SESSION.csrf');
       
}
       
if ($this->f3->VERB=='POST')
       
{
           
if ($this->f3->get('POST.session_csrf') ==  $this->f3->get('SESSION.csrf') )
           
{                                                        // Things check out! No CSRF attack was detected.
                $this
->f3->set('CSRF', $this->f3->session->csrf()); // Reset csrf token for next post request
                $this
->f3->copy('CSRF','SESSION.csrf');             // copy the token to the variable

           
}
           
else{  // DANGER: CSRF attack!

                $this
->f3->error(403);
           
}
       
} // else: no post request, keep the existing token, if users perform get requests the token remains valid
   
}

It is mandatory that every single form on the website will contain the csrf token with this solution.
Reply all
Reply to author
Forward
0 new messages