PHPCSRF

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.comque possui um formulário oculto. E esse formulário é enviado no carregamento da página para yourbank.com/transfer-fundo 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-fundimplementar 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.comenvia o formulário, o yourbank.com/transfer-fundformulário compara o token com aquele no yourbank.comservidor 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.comtenta 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 à $_SESSIONvariá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_POSTe 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:

demonstração php csrf

Primeiro, crie o seguinte arquivo e diretório:

.
├── css
│   └── style.css
├── inc
│   ├── footer.php
│   ├── get.php
│   ├── header.php
│   └── post.php
└── index.phpLinguagem de código:  PHP  ( php )

cabeçalho.php

O header.phparquivo 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.phparquivo contém as tags de fechamento correspondentes às tags de abertura do header.phparquivo:

     </main>
</body>
</html>Linguagem de código:  PHP  ( php )

arquivo index.php

Coloque o seguinte código no index.phparquivo:

<?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.phpfunciona.

Primeiro, inicie uma nova sessão chamando a session_start()função; use o $errorsarray para armazenar as mensagens de erro e o $inputsarray deve armazenar valores de entrada higienizados.

A seguir, mostre o formulário no get.phparquivo 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:

c4724e490407a1770efcc4ea19776c06e0bd4614a9dd37900f5eb001581dffee9b377aLinguagem de código:  PHP  ( php )

Em seguida, carregue o código do post.phparquivo 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, $errorsconterá as mensagens de erro.

Por fim, exiba uma mensagem do message.phparquivo 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.phpfunciona.

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 falsese o FILTER_SANITIZE_STRINGfiltro não conseguir filtrar o token.

Segundo, compare o token higienizado com aquele armazenado na $_SESSIONvariá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 $_SESSIONpara evitar ataques CSRF.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *