Đối với mã bảo mật, vui lòng không tạo mã thông báo của bạn theo cách này: $token = md5(uniqid(rand(), TRUE));
Thử thứ này đi:
Tạo mã thông báo CSRF
PHP 7
session_start();
if (empty($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['token'];
Ghi chú bên lề: Một trong những dự án mã nguồn mở của chủ nhân của tôi là một sáng kiến để hỗ trợ random_bytes()
và random_int()
đưa vào các dự án PHP 5. Nó đã được MIT cấp phép và có sẵn trên Github và Composer dưới dạng paragonie / random_compat .
PHP 5.3+ (hoặc với ext-mcrypt)
session_start();
if (empty($_SESSION['token'])) {
if (function_exists('mcrypt_create_iv')) {
$_SESSION['token'] = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
} else {
$_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(32));
}
}
$token = $_SESSION['token'];
Xác minh mã thông báo CSRF
Đừng chỉ sử dụng ==
hoặc thậm chí ===
, hãy sử dụng hash_equals()
(chỉ dành cho PHP 5.6+, nhưng có sẵn cho các phiên bản trước đó với thư viện hash-compat ).
if (!empty($_POST['token'])) {
if (hash_equals($_SESSION['token'], $_POST['token'])) {
// Proceed to process the form data
} else {
// Log this as a warning and keep an eye on these attempts
}
}
Tiến xa hơn với Token Per-Form
Bạn có thể hạn chế thêm mã thông báo để chỉ có sẵn cho một hình thức cụ thể bằng cách sử dụng hash_hmac()
. HMAC là một hàm băm có khóa đặc biệt an toàn để sử dụng, ngay cả với các hàm băm yếu hơn (ví dụ: MD5). Tuy nhiên, tôi khuyên bạn nên sử dụng họ hàm băm SHA-2 để thay thế.
Đầu tiên, tạo mã thông báo thứ hai để sử dụng làm khóa HMAC, sau đó sử dụng logic như sau để hiển thị nó:
<input type="hidden" name="token" value="<?php
echo hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
?>" />
Và sau đó sử dụng thao tác đồng dư khi xác minh mã thông báo:
$calc = hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
if (hash_equals($calc, $_POST['token'])) {
// Continue...
}
Các mã thông báo được tạo cho một biểu mẫu không thể được sử dụng lại trong ngữ cảnh khác mà không biết $_SESSION['second_token']
. Điều quan trọng là bạn phải sử dụng mã thông báo riêng biệt làm khóa HMAC thay vì mã bạn vừa thả trên trang.
Phần thưởng: Phương pháp tiếp cận lai + Tích hợp cành cây
Bất kỳ ai sử dụng công cụ tạo khuôn mẫu Twig đều có thể hưởng lợi từ chiến lược kép được đơn giản hóa bằng cách thêm bộ lọc này vào môi trường Twig của họ:
$twigEnv->addFunction(
new \Twig_SimpleFunction(
'form_token',
function($lock_to = null) {
if (empty($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(32));
}
if (empty($_SESSION['token2'])) {
$_SESSION['token2'] = random_bytes(32);
}
if (empty($lock_to)) {
return $_SESSION['token'];
}
return hash_hmac('sha256', $lock_to, $_SESSION['token2']);
}
)
);
Với chức năng Twig này, bạn có thể sử dụng cả hai mã thông báo mục đích chung như vậy:
<input type="hidden" name="token" value="{{ form_token() }}" />
Hoặc biến thể bị khóa:
<input type="hidden" name="token" value="{{ form_token('/my_form.php') }}" />
Twig chỉ quan tâm đến việc kết xuất khuôn mẫu; bạn vẫn phải xác nhận đúng các mã thông báo. Theo tôi, chiến lược Twig mang lại sự linh hoạt và đơn giản hơn, trong khi vẫn duy trì khả năng bảo mật tối đa.
Mã thông báo CSRF sử dụng một lần
Nếu bạn có yêu cầu bảo mật rằng mỗi mã thông báo CSRF được phép sử dụng chính xác một lần, thì chiến lược đơn giản nhất sẽ tạo lại nó sau mỗi lần xác thực thành công. Tuy nhiên, làm như vậy sẽ làm mất hiệu lực mọi mã thông báo trước đó vốn không kết hợp tốt với những người duyệt nhiều tab cùng một lúc.
Paragon Initiative Enterprises duy trì một thư viện Anti-CSRF cho những trường hợp góc khuất này. Nó hoạt động với các mã thông báo một lần sử dụng cho mỗi biểu mẫu. Khi đủ số lượng mã thông báo được lưu trữ trong dữ liệu phiên (cấu hình mặc định: 65535), nó sẽ chuyển ra các mã thông báo cũ nhất chưa được quy đổi trước.
token_time
được sử dụng cho?