How does the built-in CSRF protection work?

1,523 views
Skip to first unread message

Rayne

unread,
Jan 1, 2015, 9:24:08 PM1/1/15
to f3-fra...@googlegroups.com
I have read the source code of \Session and \DB\SQL\Session and grepped (case insensitive) for cookie and csrf but still don't know how the CSRF protection works.

The session classes verify that sessions, IP addresses and user agents match. Otherwise an 403 error gets thrown.

What I am missing is a CSRF token validation.

Example

$session = new \Session();

$f3->route('GET /*', function(\Base $f3) {
    $dump = ['session_before' => $f3->get('SESSION')];
    $f3->set('SESSION.key', 'value');
    $dump['session_after'] = $f3->get('SESSION');
    var_dump($dump);
});

Response

HTTP/1.1 200 OK
Host: 127.0.0.1:1234
Connection: close
Set-Cookie: PHPSESSID=…; path=/; HttpOnly
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Pragma: no-cache
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Powered-By: Fat-Free Framework
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, must-revalidate
Content-type: text/html; charset=UTF-8

Where and when does F3 use $session->csrf()?

xfra35

unread,
Jan 3, 2015, 11:23:48 AM1/3/15
to f3-fra...@googlegroups.com
Hi @Rayne, very good question. I never used that feature, just looked at the code and couldn't understand either how to use it.

I would have expected something like:
<form action="/myurl" method="post">
  <input type="hidden" name="csrf" value="{{$SESSION->csrf()}}"/>
</form>

and then
$f3->route('POST /myurl',function($f3){
  $session=new Session();
  if ($f3->get('POST.csrf') == $session->csrf()) {
    // ok: process data
  } else
    // suspicious: don't do anything
});

But at the time we call $session->csrf() on the POST route, the token has already been regenerated...

Rayne

unread,
Apr 26, 2015, 6:34:40 PM4/26/15
to f3-fra...@googlegroups.com
I am still interested how the built-in protection works. Any news?

ikkez

unread,
Apr 27, 2015, 2:31:57 AM4/27/15
to f3-fra...@googlegroups.com
the trick is to put the csrf token into the session and compare that value with the POST.csrf


<form action="/myurl" method="post">
  <input type="hidden" name="csrf" value="{{$csrf}}"/>
</form>

in controller

$f3->route('GET /myurl',function($f3){
  $s = new Session();
  $f3->set('SESSION.csrf',$s->csrf());
  echo \Template::instance()->render('form.html');
});

$f3->route('POST /myurl',function($f3){
  $s = new Session();
  if ($f3->get('POST.csrf') == $f3->get('SESSION.csrf') {
    // ok: process data
  } else
    // suspicious: don't do anything
});

Sascha

unread,
Apr 27, 2015, 3:09:18 AM4/27/15
to f3-fra...@googlegroups.com
Just wanted to mention, ikkez already added this to the documention a month ago:

Rayne

unread,
Apr 29, 2015, 10:32:06 AM4/29/15
to f3-fra...@googlegroups.com
Thank you for the update and the documentation.

Dim Kas

unread,
Jun 27, 2015, 9:24:53 AM6/27/15
to f3-fra...@googlegroups.com
So what about the automatic csrf check mentioned here?

Does it make sense to use csrf manually after all? Is it handled internally somehow?

SESSION check is not enough since CSRF attacks jump into your session. CSRF is supposed to prevent POST/GET that piggybacks on your SESSION but is not coming from your own application forms. 

ikkez

unread,
Jul 1, 2015, 1:47:02 AM7/1/15
to f3-fra...@googlegroups.com
I think the SESSION check only protects you from SESSION Hijacking / Riding, which is a special form of CSRF. If you have any XSS vulnerability on your side, an injected JS code can still use your privileges and is able to do harmfull actions under your name. So basically, if you can assure that you have no XSS leak, than you don't need no hidden form tokens, since all action-routes like POST,PUT,DELETE must be made through an ajax request signed with your session (only GET works in an iframe or he's doing clickjacking, but F3 already has the XFRAME shield build-in for this). And that's just possible if your session was hijacked (session cookie was stolen), or the request comes from your own domain (xss leak + ajax). Since we have auto-escaping in our templates, XSS leaks should be minimized and session hijacking is detected by the framework's session check.You can add hidden form tokens or URL tokens for GET-based action routes, using the csrf token from the session class as an additional security layer (don't delete an own account using a GET request, or salt this route with the csrf-token like /user/58/delete/dh37g6cfh38), but even that is useless when you have malicious code in your site. (imagine some js code, that was injected through a GET parameter on the search-results page,.. it just loads the member account page, reads the delete user link or form tokens, and calls the right request with ajax)

Niklas Baumstark

unread,
Aug 31, 2015, 7:08:03 PM8/31/15
to Fat-Free Framework
Just my two cents, because I just came across this thread:


On Wednesday, July 1, 2015 at 7:47:02 AM UTC+2, ikkez wrote:
I think the SESSION check only protects you from SESSION Hijacking / Riding, which is a special form of CSRF. If you have any XSS vulnerability on your side, an injected JS code can still use your privileges and is able to do harmfull actions under your name. So basically, if you can assure that you have no XSS leak, than you don't need no hidden form tokens, since all action-routes like POST,PUT,DELETE must be made through an ajax request signed with your session (only GET works in an iframe or he's doing clickjacking, but F3 already has the XFRAME shield build-in for this).

Now there's at least two misconceptions here:
(1) You can have an CSRF vuln without an XSS vuln. It's enough to be able to send HTTP requests to the domain, which will get the session cookie appended automatically. So the session cookie being correct does not ensure that the sending origin is your own domain. Of course cross-origin you can't read the HTTP responses, but in a CSRF attack you only care about the action triggered by your request, not the result.
(2) "all action-routes like POST,PUT,DELETE must be made through an ajax request..." Not really, you can also use forms to make cross-origin requests. No XHR needed, although that's of course more convenient, but can be detected by looking at the X-Requested-With request header.
(3) "X-Frame-Options: sameorigin" can not possibly protect against POST requests made in an IFrame, because by the time the response header is received, it's already to late, the request was already parsed and processed by the target server.

Of course it is true that XSS > CSRF, but you have to protect against both. Comparing a token sent along the state-changing request to a randomly generated token associated with the session is a rather robust solution.

Suki

unread,
Jan 3, 2017, 11:39:57 AM1/3/17
to Fat-Free Framework
I recently came across this problem. I followed the suggestions on the docs but everytime I ended up with a different token even though I was storing the token on a session var.

I don't know if this is because I'm using a DB session handler but the only way I could make it work was to put an if check to see if the session var wasn't already set:


$f3->set('USER', new \DB\SQL\Session($f3->get('DB'), 'user_ses'));
if (!$f3->exists('SESSION.csrf'))
$f3->set('SESSION.csrf', $f3->get('USER')->csrf());

Otherwise the code will generate a new token whenever a new POST request was made and it will be saved on  SESSION.csrf overwriting what was previously set which means the check  if ($f3->get('POST.token')!= $f3->get('SESSION.csrf'))  will always fail.

Bellegueulle Damien

unread,
Jan 19, 2017, 2:40:08 PM1/19/17
to Fat-Free Framework
Example ( simply ) :

$f3->route('GET /',
   
function($f3) {
        $f3
->set('IP',$f3->get('IP'));
        $f3
->set('AGENT',$f3->get('AGENT'));
        $rand
= mt_rand(79, 7900);
        $f3
->set('csrf',$rand);
        $f3
->set('SESSION.csrf',$rand);    
        echo
\Template::instance()->render('login.html');
   
}
,0,64);

///
if ($f3->get('POST.csrf') == $f3->get('SESSION.csrf')) {
balalalala
}
Reply all
Reply to author
Forward
0 new messages