Resumo : neste tutorial, você aprenderá sobre ataques de falsificação de solicitação entre sites (CSRF) e como evitá-los em PHP.
O que é CSRF
CSRF significa falsificação de solicitação entre sites. É um tipo de ataque em que um hacker força você a executar uma ação contra um site onde você está logado.
Por exemplo, você visita o malicious-site.com
que possui um formulário oculto. E esse formulário é enviado no carregamento da página para yourbank.com/transfer-fund
o formulário.
Como você está conectado no momento yourbank.com
, a solicitação transfere silenciosamente um fundo de sua conta bancária.
Se yourbank.com/transfer-fund
implementar o CSRF corretamente, ele gera um token único e insere o token no formulário de transferência de fundos como este:
<input type="hidden"
name="token"
value="b3f44c1eb885409c222fdb78c125f5e7050ce4f3d15e8b15ffe51678dd3a33d3a18dd3">
Linguagem de código: PHP ( php )
Quando malicious-site.com
envia o formulário, o yourbank.com/transfer-fund
formulário compara o token com aquele no yourbank.com
servidor do.
Se o token não existir nos dados enviados ou não corresponder ao token no servidor, o formulário de transferência de fundos rejeitará o envio e retornará um erro.
Quando malicious-site.com
tenta enviar o formulário, o token provavelmente não estará disponível ou não corresponderá.
Como implementar o token CSRF em PHP
Primeiro, crie um token único e adicione-o à $_SESSION
variável:
$_SESSION['token'] = md5(uniqid(mt_rand(), true));
Linguagem de código: PHP ( php )
Segundo, adicione um campo oculto cujo valor seja o token e insira-o no formulário:
<input type="hidden" name="token" value="<?php echo $_SESSION['token'] ?? '' ?>">
Linguagem de código: PHP ( php )
Terceiro, quando o formulário for enviado, verifique se o token existe INPUT_POST
e compare-o com $_SESSION['token']
:
<?php
$token = filter_input(INPUT_POST, 'token', FILTER_SANITIZE_STRING);
if (!$token || $token !== $_SESSION['token']) {
// return 405 http status code
header($_SERVER['SERVER_PROTOCOL'] . ' 405 Method Not Allowed');
exit;
} else {
// process the form
}
Linguagem de código: PHP ( php )
Se o token não existir ou não corresponder, retorne o código de status HTTP 405 e saia.
Exemplo PHP CSRF
Criaremos um formulário simples de transferência de fundos para demonstrar como prevenir um ataque CSRF:
Primeiro, crie o seguinte arquivo e diretório:
.
├── css
│ └── style.css
├── inc
│ ├── footer.php
│ ├── get.php
│ ├── header.php
│ └── post.php
└── index.php
Linguagem de código: PHP ( php )
cabeçalho.php
O header.php
arquivo contém o código que mostra a primeira seção da página:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/style.css">
<title>PHP CSRF - Fund Transfer Demo</title>
</head>
<body>
<main>
Linguagem de código: PHP ( php )
rodapé.php
O footer.php
arquivo contém as tags de fechamento correspondentes às tags de abertura do header.php
arquivo:
</main>
</body>
</html>
Linguagem de código: PHP ( php )
arquivo index.php
Coloque o seguinte código no index.php
arquivo:
<?php
session_start();
require __DIR__ . '/inc/header.php';
$errors = []; // for storing the error messages
$inputs = []; // for storing sanitized input values
$request_method = strtoupper($_SERVER['REQUEST_METHOD']);
if ($request_method === 'GET') {
// generate a token
$_SESSION['token'] = bin2hex(random_bytes(35));
// show the form
require __DIR__ . '/inc/get.php';
} elseif ($request_method === 'POST') {
// handle the form submission
require __DIR__ . '/inc/post.php';
// re-display the form if the form contains errors
if ($errors) {
require __DIR__ . '/inc/get.php';
}
}
require __DIR__ . '/inc/footer.php';
Linguagem de código: PHP ( php )
Como index.php
funciona.
Primeiro, inicie uma nova sessão chamando a session_start()
função; use o $errors
array para armazenar as mensagens de erro e o $inputs
array deve armazenar valores de entrada higienizados.
A seguir, mostre o formulário no get.php
arquivo se a solicitação HTTP for GET.
O seguinte gera o token único:
$_SESSION['token'] = bin2hex(random_bytes(35));
Linguagem de código: PHP ( php )
O random_bytes(35)
gera uma string aleatória com 35 caracteres. E a bin2hex()
função retorna a representação hexadecimal da string aleatória. O token ficará assim:
c4724e490407a1770efcc4ea19776c06e0bd4614a9dd37900f5eb001581dffee9b377a
Linguagem de código: PHP ( php )
Em seguida, carregue o código do post.php
arquivo para lidar com o envio do formulário se a solicitação HTTP for POST.
Depois disso, mostre o formulário novamente com mensagens de erro se os dados do formulário forem inválidos. Observe que se o formulário contiver erros, $errors
conterá as mensagens de erro.
Por fim, exiba uma mensagem do message.php
arquivo informando que o fundo foi transferido com sucesso.
obter.php
A seguir, cria-se o formulário de transferência de fundos com dois campos de entrada, valor da transferência e conta do destinatário:
<form action="<?= htmlspecialchars($_SERVER['PHP_SELF']) ?>" method="post">
<header>
<h1>Fund Transfer</h1>
</header>
<div>
<label for="amount">Amount (between $1-$5000):</label>
<input type="number" name="amount" value="<?= $inputs['amount'] ?? '' ?>" id="amount" placeholder="Enter the transfered amount">
<small><?= $errors['amount'] ?? '' ?></small>
</div>
<div>
<label for="recipient_account">Recipient Account:</label>
<input type="number" name="recipient_account" value="<?= $inputs['recipient_account'] ?? '' ?>" id="recipient_account" placeholder="Enter the recipient account">
<small><?= $errors['recipient_account'] ?? '' ?></small>
</div>
<input type="hidden" name="token" value="<?= $_SESSION['token'] ?? '' ?>">
<button type="submit">Transfer Now</button>
</form>
Linguagem de código: PHP ( php )
post.php
O código a seguir valida o token e os dados do formulário:
<?php
$token = filter_input(INPUT_POST, 'token', FILTER_SANITIZE_STRING);
if (!$token || $token !== $_SESSION['token']) {
// show an error message
echo '<p class="error">Error: invalid form submission</p>';
// return 405 http status code
header($_SERVER['SERVER_PROTOCOL'] . ' 405 Method Not Allowed');
exit;
}
// Validate amount
$amount = filter_input(INPUT_POST, 'amount', FILTER_SANITIZE_NUMBER_INT);
$inputs['amount'] = $amount;
if ($amount) {
$amount = filter_var(
$amount,
FILTER_VALIDATE_INT,
['options' => ['min_range' => 1, 'max_range' => 5000]]
);
if (!$amount) {
$errors['amount'] = 'Please enter a valid amount (from $1 to $5000)';
}
} else {
$errors['amount'] = 'Please enter the transfered amount.';
}
// validate account (simple)
$recipient_account = filter_input(INPUT_POST, 'recipient_account', FILTER_SANITIZE_NUMBER_INT);
$inputs['recipient_account'] = $recipient_account;
if ($recipient_account) {
$recipient_account = filter_var($recipient_account, FILTER_VALIDATE_INT);
if (!$recipient_account) {
$errors['recipient_account'] = 'Please enter a valid recipient account';
}
// validate the recipient account against the database
// ...
} else {
$errors['recipient_account'] = 'Please enter the recipient account.';
}
Linguagem de código: PHP ( php )
Como post.php
funciona.
Primeiro, limpe o token do INPUT_POST:
$token = filter_input(INPUT_POST, 'token', FILTER_SANITIZE_STRING);
Linguagem de código: PHP ( php )
A filter_input()
função retorna nulo se o token estiver incluído nos dados enviados. Ele retorna false
se o FILTER_SANITIZE_STRING
filtro não conseguir filtrar o token.
Segundo, compare o token higienizado com aquele armazenado na $_SESSION
variável:
if (!$token || $token !== $_SESSION['token']) {
// process error
}
Linguagem de código: PHP ( php )
Se não corresponderem, retornamos o código de status HTTP 405 (método não permitido) ao cliente usando a header()
função e interrompemos imediatamente o script.
header($_SERVER['SERVER_PROTOCOL'] . ' 405 Method Not Allowed');
Linguagem de código: PHP ( php )
O código restante limpa e valida o valor e a conta do destinatário. Se não houver erro, mostramos uma mensagem de confirmação:
<?php if (!$errors) : ?>
<section>
<div class="circle">
<div class="check"></div>
</div>
<h1 class="message">You've transfered</h1>
<h2 class="amount">$<?= $amount ?></h2>
<a href="<?= htmlspecialchars($_SERVER['PHP_SELF']) ?>" rel="prev">Done</a>
</section>
<?php endif ?>
Linguagem de código: HTML, XML ( xml )
Resumo
- Os ataques CSRF forçam os usuários a executar uma ação no site onde estão logados no momento.
- Use o
bin2hex(random_bytes(35))
para gerar o token único. - Verifique o token enviado com aquele armazenado no
$_SESSION
para evitar ataques CSRF.