Para o código de segurança, não gere seus tokens desta forma: $token = md5(uniqid(rand(), TRUE));
Experimente isto:
Gerando um token CSRF
PHP 7
session_start();
if (empty($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['token'];
Sidenote: Um dos projetos de código aberto do meu empregador é uma iniciativa de backport random_bytes()
e random_int()
em projetos PHP 5. É licenciado pelo MIT e está disponível no Github e no Composer como paragonie / random_compat .
PHP 5.3+ (ou com 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'];
Verificando o token CSRF
Não use apenas ==
ou mesmo ===
, use hash_equals()
(PHP 5.6+ apenas, mas disponível para versões anteriores com a biblioteca 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
}
}
Indo além com tokens por formulário
Você pode restringir ainda mais os tokens para que fiquem disponíveis apenas para um determinado formulário usando hash_hmac()
. HMAC é uma função de hash com chave específica que é segura de usar, mesmo com funções de hash mais fracas (por exemplo, MD5). No entanto, recomendo usar a família SHA-2 de funções hash.
Primeiro, gere um segundo token para usar como uma chave HMAC e, em seguida, use uma lógica como esta para renderizá-lo:
<input type="hidden" name="token" value="<?php
echo hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
?>" />
E, em seguida, usando uma operação congruente ao verificar o token:
$calc = hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
if (hash_equals($calc, $_POST['token'])) {
// Continue...
}
Os tokens gerados para um formulário não podem ser reutilizados em outro contexto sem conhecimento $_SESSION['second_token']
. É importante que você use um token separado como uma chave HMAC do que aquele que você acabou de colocar na página.
Bônus: Abordagem Híbrida + Integração Twig
Qualquer pessoa que usa o mecanismo de modelagem Twig pode se beneficiar de uma estratégia dupla simplificada, adicionando este filtro ao ambiente Twig:
$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']);
}
)
);
Com esta função Twig, você pode usar os tokens de uso geral como:
<input type="hidden" name="token" value="{{ form_token() }}" />
Ou a variante bloqueada:
<input type="hidden" name="token" value="{{ form_token('/my_form.php') }}" />
O Twig está preocupado apenas com a renderização do template; você ainda deve validar os tokens corretamente. Na minha opinião, a estratégia Twig oferece maior flexibilidade e simplicidade, mantendo a possibilidade de máxima segurança.
Tokens CSRF de uso único
Se você tiver um requisito de segurança de que cada token CSRF possa ser usado exatamente uma vez, a estratégia mais simples é regenerá-lo após cada validação bem-sucedida. No entanto, isso invalidará todos os tokens anteriores, o que não combina bem com pessoas que navegam em várias guias ao mesmo tempo.
A Paragon Initiative Enterprises mantém uma biblioteca Anti-CSRF para esses casos secundários. Funciona exclusivamente com tokens de uso único por formulário. Quando tokens suficientes são armazenados nos dados da sessão (configuração padrão: 65535), os tokens não resgatados mais antigos são interrompidos primeiro.
token_time
serve?