Combatting Login CSRF with Symfony
Cross-site Request Forgery (CSRF) is one of the traditional vulnerabilities that web applications have to deal with. Every web framework - including Symfony - supports CSRF protection out of the box. A lesser known vulnerability is Login CSRF, a special kind of CSRF attack.
How a CSRF Attack Works
A CSRF attack takes advantage of the sessions in the browser. If you login to a website, your authentication token is stored in a session. This means that you’re still logged in if you switch to another page of the website.
The browser also remembers this session when visiting other websites. This is nice, as it allows you to go back to the website and still be logged in. However, attackers are using this to make requests from their website to your website using the remembered authentication session. This means that, without CSRF protection, it can use your website as if it was the user.
For instance, an attacker can trick the user into thinking they are on your website and show them a form that looks like the login:
1
2
3
4
5
6
7
8
9
<form action="https://yourwebsite.com/profile/change_password" method="POST">
<label>Username <input type="text" name="username"></label>
<label>Password <input type="password" name="current_password"></label>
<input type="hidden" name="new_password" value="...">
<button type="submit">Login</button>
</form>
In reality (looking at the action
and type="hidden"
input), this form
changes the password of the user to something that is known only by the
attacker, giving them access to their account. Attacks can also do any
other action on your website, like buying goods or transfering money to
their account.
How Login CSRF Attacks are Different
The CSRF attacks described above are using the authenticated session to act as a user. Login CSRF attacks are the opposite: they force an authenticated session to let the user act as them.
How does that work? When someone visits the website of an attacker, they
make a request to yourwebsite.com/logout
to force an unauthenticated
session and then make a request to yourwebsite.com/login
with
credentials of an account that they control.
This can have serious consequences for the user, depending on the functionality of your website. E.g. “Robust Defenses for Cross-Site Request Forgery” (Barth, Jackson, Mitchell, 2008) mention that this attack is used to let users unknowingly connect their creditcard to the PayPal account of the attacker. It is also used to get access to a user’s Google account, or using it to track the user’s search terms and locations.
The risk of Login CSRF attacks is often considered to be lower. Most users are expected to notice when they are logged in to someone else’s account on the website (hence it’s a good idea to make this visible on all pages!). However, the consequences can be very high when a user doesn’t notice. If you’re using Symfony, protecting against them is very easy and I would recommend always doing this.
Protecting against Login CSRF
A login CSRF attack can include two steps: first a call to logout to force the website in a known state, and then a login with the attackers credentials. Let’s protect both steps!
Protecting Login
Protecting against login CSRF attacks is similar to protecting against other CSRF attacks. When rendering the login form, you create a randomized token which you submit along with the rest of the form. When handling the submission, this token is checked for validity.
When using Symfony’s built-in form_login
authenticator, generating and
validating this randomized token is mostly automated for you. First, enable
CSRF protection in the authenticator:
1
2
3
4
5
6
7
security:
# ...
firewalls:
main:
form_login:
# ...
enable_csrf: true
And then, add a hidden CSRF token to your login form template:
1
2
<!-- csrf_token() takes care of correctly generating and storing the CSRF token -->
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
That’s it! Your users are safe against this attack now!
When you’re using a custom login form authenticator, you’ll have to add
the CsrfTokenBadge
with the CSRF token from the request. The
security system will then validate this token for you.
Protecting Logout
You can protect logout in a similar way by enabling CSRF protection in the config:
1
2
3
4
5
6
7
security:
# ...
firewalls:
main:
logout:
# ...
enable_csrf: true
If you’re already using the logout_path()
Twig helper function to
generate a URL to your logout route, this is all you had to do! The Twig
function generates a unique CSRF token and attaches it to the URL.
Otherwise, replace links to the logout page with this Twig function in your Twig templates:
1
<a href="{{ logout_path() }}">Logout</a>
Take Homes
- Make sure your login/logout actions are also protected against CSRF attacks
- While cookies have become more secure in recent years, CSRF protection still needs attention