CSRF Laravel

Đã đôi lần trong series này chúng ta đề cập đến CSRF trong Laravel và hôm nay chúng ta giành hẳn 1 bài để tìm hiểu về nó. Bắt đầu thôi nào

I. CSRF là gì? (What is CSRF?)

Trước khi bắt đầu tìm hiểu về CSRF trong Laravel chúng ta sẽ tìm hiểu sơ lược về kiểu tấn công CSRF. CSRF và viết tắt tiếng Anh của "cross-site request forgery" (đại khái nghĩa là "giả mạo yêu cầu trên trang web"). Đây là một loại tấn công sẽ thực hiện các request trái phép thông qua user đã được xác thực (tức là các user đã đăng nhập trên hệ thống).

Hình thức tấn công này rất đơn giản có thể mô tả như sau:

  • Hacker mò được đường dẫn lệnh xóa bài viết trên trang của bạn
  • Ví dụ: {id}/delete">http://myapp.com/admin/posts/{id}/delete với method GET chẳng hạn. 
  • Hacker sẽ dụ bạn click vào link này hoặc chuyển hướng bạn đến nó.
  • Nếu session đăng nhập của bạn trong hệ thống vẫn chưa hết hạn thì bài viết sẽ bị xóa

Hãy thử tưởng tượng nếu một hệ thống lớn như ngân hàng, mạng xã hội không có cơ chế bảo mật CSRF và người dùng bị tấn công như vậy thì sẽ nghiêm trọng thế nào?

Chính vì vậy, mỗi website luôn phải trang bị cơ chế bảo mật kiểu tấn công CSRF này, nếu người sử dụng không hiểu biết thì sẽ trở thành một hacker gián tiếp, gây ảnh hưởng nghiêm trọng đến hệ thống.

II. CSRF Laravel

Đương nhiên Laravel dễ dàng bảo vệ ứng dụng của bạn khỏi kiểu tấn công CSRF này. Việc Laravel làm chỉ là tự động tạo một "token" cho mỗi phiên hoạt động của người dùng, được quản lý bởi framework. Token này dùng để xác minh rằng user đã đăng nhập là user thực sự thực hiện các request tới ứng dụng.

Khi tạo một form HTMl bất kỳ trong Laravel, các bạn nên khai báo thẻ @csrf để tạo token. Khi đó middleware VerifyCsrfToken có thể xác thực request.

<form method="POST" action="/profile">
    @csrf
    ...
</form>

Middleware VerifyCsrfToken đã được thêm trong nhóm middleware web, nó sẽ tự động kiểm tra token đầu vào của request khớp với token được lưu trữ trong session.

1. CSRF token & Javascript

Theo mặc định thì file resources/js/bootstrap.js sẽ lấy token từ thẻ meta với name csrf-token để đăng ký cho thư viện Axios HTTP. Lúc này mọi request đều được gửi cùng với token.

resources/js/bootstrap.js

/**
 * Next we will register the CSRF Token as a common header with Axios so that
 * all outgoing HTTP requests automatically have it attached. This is just
 * a simple convenience so we don't have to attach every token manually.
 */


let token = document.head.querySelector('meta[name="csrf-token"]');


if (token) {
    window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
    console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}

Nếu bạn không sử dụng thư viện này, bạn phải cấu hình thủ công. Chẳng hạn nếu bạn sử dụng thư viện Ajax jQuery thay cho Axios HTTP thì có thể làm như sau:

Đầu tiên tạo thẻ meta với name là csrf-token trong thẻ head.

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

Tiếp đó chỉ việc đăng ký ở file JS:

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

Như vậy, mỗi lần gửi request với ajax, bạn đã kèm theo token csrf rồi đấy.

Để kiểm chứng bạn có thể tạo một blade view home với nội dung sau:

<html>
    <head>
        <title>CSRF Laravel</title>


        <meta name="csrf-token" content="{{ csrf_token() }}">
    </head>
    <body>
        <form action="/post" method="POST">
            <input type="submit" value="Send">
        </form>


        <script src="https://code.jquery.com/jquery.min.js"></script>
        <script>
            $.ajaxSetup({
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                }
            });


            $('form').submit(function(e) {
                e.preventDefault();


                $.ajax({
                    url: '/post',
                    type: 'POST',
                    success: function(res) {
                        console.log(res);
                    } 
                });
            });
        </script>
    </body>
</html>

Trong đoạn code trên mình đã khai báo thẻ meta chứa token csrf ở thẻ head. Sau đó tạo một form HTML, nhưng không khai báo thẻ @csrf đển tạo input token. Về Javascript, mình import thư việc jQuery, thực hiện setup như ở trên rồi thực thi gửi request với ajax đơn giản.

Để test thì chúng ta đăng ký các route sau:

/views/home.blade.php

Route::get('/', function () {
    return view('home');
});

Route::post('/post', function () {
    return 'Posted';
});

Đây là kết quả sau khi submit form:

CSRF Laravel 1

2. Ngoại trừ các URI khỏi bảo vệ CSRF (Excepting URIs from CSRF protection)

Bạn có thể ngoại trừ URI nào đó không cần đến cơ chế bảo vệ CSRF của Laravel bằng cách liệt kê chúng vào $except trong middleware VerifyCsrfToken.

app/Http/Middlewares/VerifyCsrfToken.php

 protected $except = [
    'stripe/*',
    'http://example.com/foo/bar',
    'http://example.com/foo/*',
];

Lưu ý: CSRF protection sẽ tự động tắt khi đang chạy testing

Lời kết:

CSRF sẽ vô dụng trong Laravel nếu người dùng có khai báo @csrf. Thông thường với Laravel rất ít xảy ra CSRF nếu người dùng chịu khó để ý một chút.

Bình luận