Cross-site request forgery - PHP

Cross-site request forgery - PHP

Need

Protection against cross-site request forgery attacks

Context

  • Requirement of PHP 7.0 or later for running the application
  • Usage of Illuminate Routing for handling URL routing in a PHP application
  • Usage of Illuminate/Http for handling HTTP requests and responses in PHP
  • Usage of Illuminate/Auth for authentication and authorization in Laravel
  • Usage of Illuminate/Database for database operations and query building
  • Usage of illuminate/support for providing support and utility functions in Laravel framework

Description

Non compliant code

        Route::post('/update-profile', 'ProfileController@update');

class ProfileController extends Controller
{
    public function update(Request $request)
    {
        $user = Auth::user();
        $user->name = $request->input('name');
        $user->email = $request->input('email');
        $user->save();

        return redirect('/profile');
    }
}
        
        

The above PHP code is a typical example of a Laravel controller that updates a user's profile. The user's new name and email are taken from the request inputs and saved to the user's profile in the database.

However, this code is vulnerable to Cross-Site Request Forgery (CSRF) attacks. Here's why:

1. The code doesn't check for a CSRF token in the request. In a CSRF attack, an attacker tricks a victim into submitting a malicious request. It inherits the identity and privileges of the victim to perform an undesired function on their behalf. In most cases, the user is not aware that this has happened.

2. The update function doesn't verify if the request is coming from a trusted source. It blindly trusts the incoming request and performs the update operation.

3. Since this function can change critical user information such as name and email, a successful CSRF attack can lead to serious consequences. For example, an attacker can change the victim's email to their own email and then use the "forgot password" function to take over the account.

The lack of CSRF protection in this code means that any authenticated user can be tricked into sending a request to this endpoint, which will update their profile with the attacker's provided details.

Steps

  • Add a CSRF token to your form. This token is used to verify that the authenticated user is the one actually making the requests to the application.
  • Use the CSRF field in your form. Laravel provides a simple way to add this token via the @csrf Blade directive.
  • Validate the CSRF token on the server side. Laravel automatically verifies that the token in the request input matches the token stored in the session.
  • Ensure that the CSRF token is sent with AJAX requests. If you are using libraries like Axios or jQuery, you can set up a global AJAX request interceptor to send the CSRF token.
  • Rotate CSRF tokens per session or per form request for added security. This can be done in Laravel by using the regenerateToken method on the session.

Compliant code

        Route::post('/update-profile', 'ProfileController@update');

class ProfileController extends Controller
{
    public function update(Request $request)
    {
        $this->validate($request, [
            '_token' => 'required',
        ]);

        $user = Auth::user();
        $user->name = $request->input('name');
        $user->email = $request->input('email');
        $user->save();

        return redirect('/profile');
    }
}
        
        

The updated code now includes a CSRF token in the form that is submitted when updating the profile. This token is generated by Laravel using the @csrf Blade directive. This token is then included in the POST request when the form is submitted.

In the update method of the ProfileController, we now validate that the _token field is present in the request. This is the field where Laravel automatically stores the CSRF token. If the _token field is not present or does not match the token stored in the session, Laravel will throw an Illuminate\\Session\\TokenMismatchException, effectively preventing the request from being processed.

This way, we ensure that the request is indeed made by the authenticated user and not by a potential attacker. This is because the CSRF token is unique per session and is unknown to potential attackers.

For AJAX requests, you should include the CSRF token in the header of your requests. If you are using Axios, you can do this globally like this:

                axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content');

            


For jQuery, you can do it like this:

                $.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

            


Remember to include the CSRF token meta tag in your HTML:

                <meta name="csrf-token" content="{{ csrf_token() }}">

            


For added security, you can rotate CSRF tokens per session or per form request. This can be done in Laravel by using the regenerateToken method on the session:

                session()->regenerateToken();

            

References