跳到內容

CSRF 防護實作方式

編輯此頁面

CSRF,或 跨站請求偽造,是一種惡意行為者欺騙使用者在網頁應用程式上執行他們不知情或不同意的動作的攻擊類型。

這種攻擊是基於網頁應用程式對使用者瀏覽器的信任(例如,對 session cookies 的信任)。以下是一個 CSRF 攻擊的真實範例:惡意行為者可以建立以下網站

1
2
3
4
5
6
7
8
9
10
11
12
<html>
    <body>
        <form action="https://example.com/settings/update-email" method="POST">
            <input type="hidden" name="email" value="malicious-actor-address@some-domain.com"/>
        </form>
        <script>
            document.forms[0].submit();
        </script>

        <!-- some content here to distract the user -->
    </body>
</html>

如果您訪問此網站(例如,透過點擊某些電子郵件連結或某些社群網路貼文),並且您已經登入 https://example.com 網站,則惡意行為者可以更改與您的帳戶關聯的電子郵件地址(有效地接管您的帳戶),而您甚至沒有意識到。

防止 CSRF 攻擊的有效方法是使用反 CSRF 令牌。這些是添加到表單作為隱藏欄位的唯一令牌。合法的伺服器會驗證它們,以確保請求來自預期的來源,而不是其他惡意網站。

安裝

Symfony 提供了產生和驗證反 CSRF 令牌所需的所有功能。在使用它們之前,請在您的專案中安裝此套件

1
$ composer require symfony/security-csrf

然後,使用 csrf_protection 選項啟用/停用 CSRF 防護(請參閱 CSRF 配置參考 以取得更多資訊)

1
2
3
4
# config/packages/framework.yaml
framework:
    # ...
    csrf_protection: ~

用於 CSRF 防護的令牌旨在讓每位使用者都不同,並且它們儲存在 session 中。這就是為什麼當您使用 CSRF 防護渲染表單時,會自動啟動 session。

此外,這表示您無法完全快取包含 CSRF 保護表單的頁面。作為替代方案,您可以

  • 將表單嵌入未快取的 ESI 片段 中,並快取頁面內容的其餘部分;
  • 快取整個頁面,並透過未快取的 AJAX 請求載入表單;
  • 快取整個頁面,並使用 hinclude.js 透過未快取的 AJAX 請求載入 CSRF 令牌,並將表單欄位值替換為它。

Symfony 表單中的 CSRF 防護

Symfony 表單 預設包含 CSRF 令牌,而 Symfony 也會自動為您檢查它們。因此,當使用 Symfony 表單時,您不必做任何事情即可受到 CSRF 攻擊的保護。

預設情況下,Symfony 將 CSRF 令牌新增到名為 _token 的隱藏欄位中,但是可以(1)針對所有表單全域自訂,以及(2)在每個表單的基礎上自訂。全域而言,您可以在 framework.form 選項下配置它

1
2
3
4
5
6
7
# config/packages/framework.yaml
framework:
    # ...
    form:
        csrf_protection:
            enabled: true
            field_name: 'custom_token_name'

在每個表單的基礎上,您可以在每個表單的 setDefaults() 方法中配置 CSRF 防護

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/Form/TaskType.php
namespace App\Form;

// ...
use App\Entity\Task;
use Symfony\Component\OptionsResolver\OptionsResolver;

class TaskType extends AbstractType
{
    // ...

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class'      => Task::class,
            // enable/disable CSRF protection for this form
            'csrf_protection' => true,
            // the name of the hidden HTML field that stores the token
            'csrf_field_name' => '_token',
            // an arbitrary string used to generate the value of the token
            // using a different string for each form improves its security
            'csrf_token_id'   => 'task_item',
        ]);
    }

    // ...
}

您也可以自訂 CSRF 表單欄位的渲染,方法是建立自訂的 表單主題,並使用 csrf_token 作為欄位的前綴(例如,定義 {% block csrf_token_widget %} ... {% endblock %} 以自訂整個表單欄位內容)。

手動產生和檢查 CSRF 令牌

雖然 Symfony 表單預設提供自動 CSRF 防護,但您可能需要手動產生和檢查 CSRF 令牌,例如在使用非 Symfony 表單元件管理的常規 HTML 表單時。

考慮一個為了允許刪除項目而建立的 HTML 表單。首先,使用 csrf_token() Twig 函式 在模板中產生 CSRF 令牌,並將其儲存為隱藏的表單欄位

1
2
3
4
5
6
<form action="{{ url('admin_post_delete', { id: post.id }) }}" method="post">
    {# the argument of csrf_token() is an arbitrary string used to generate the token #}
    <input type="hidden" name="token" value="{{ csrf_token('delete-item') }}">

    <button type="submit">Delete item</button>
</form>

然後,在控制器動作中取得 CSRF 令牌的值,並使用 isCsrfTokenValid() 方法檢查其有效性

1
2
3
4
5
6
7
8
9
10
11
12
13
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
// ...

public function delete(Request $request): Response
{
    $submittedToken = $request->getPayload()->get('token');

    // 'delete-item' is the same value used in the template to generate the token
    if ($this->isCsrfTokenValid('delete-item', $submittedToken)) {
        // ... do something, like deleting an object
    }
}

或者,您可以使用控制器動作上的 IsCsrfTokenValid 屬性

1
2
3
4
5
6
7
8
9
10
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid;
// ...

#[IsCsrfTokenValid('delete-item', tokenKey: 'token')]
public function delete(): Response
{
    // ... do something, like deleting an object
}

假設您想要每個項目一個 CSRF 令牌,因此在模板中,您有類似以下內容的程式碼

1
2
3
4
5
6
<form action="{{ url('admin_post_delete', { id: post.id }) }}" method="post">
    {# the argument of csrf_token() is a dynamic id string used to generate the token #}
    <input type="hidden" name="token" value="{{ csrf_token('delete-item-' ~ post.id) }}">

    <button type="submit">Delete item</button>
</form>

IsCsrfTokenValid 屬性也接受評估為 id 的 Expression 物件

1
2
3
4
5
6
7
8
9
10
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid;
// ...

#[IsCsrfTokenValid(new Expression('"delete-item-" ~ args["post"].getId()'), tokenKey: 'token')]
public function delete(Post $post): Response
{
    // ... do something, like deleting an object
}

7.1

IsCsrfTokenValid 屬性是在 Symfony 7.1 中引入的。

CSRF 令牌與壓縮側通道攻擊

BREACHCRIME 是針對使用 HTTP 壓縮時 HTTPS 的安全性漏洞。攻擊者可以利用壓縮洩露的資訊來恢復目標部分的純文字。為了減輕這些攻擊,並防止攻擊者猜測 CSRF 令牌,隨機遮罩會被預先添加到令牌中並用於擾亂它。

這項工作,包括程式碼範例,已根據 Creative Commons BY-SA 3.0 授權條款授權。
目錄
    版本