I believe you need to protect all your forms against CSRF attacks, even those that need authentication to gain access, as the attacker can create an account, login and attack.
Any type of entry in your system should be handled, no matter how insignificant it may seem, especially when that entry depends on data you fill in.
As for the choice of the GET or POST method, we first have to know:
GET can be cached, this would be bad if you do not want private information exposed in the URL.
GET should never be used with sensitive data, such as passwords, bank details, etc., since it is exposed in the URL and cached by the browser.
GET has length restriction, meaning your URL is limited to X characters (if I'm not mistaken there are differences between browsers).
POST will never be cached, so all the information submitted exists only at that time.
POST has no length restrictions.
The data is encapsulated in the body of the HTTP request, so it is not exposed in the URL, for example.
POST is slightly slower by encapsulating the message.
POST accepts other data types, such as binary, in turn GET only accepts ASCII characters.
In short, if you want the user to know the URL (routes), for example, access news meusite.com?pagina=noticias&id=12
or meusite.com/news/12
, then use GET, otherwise use POST.
Although the question code uses only POST, the example below can be used for both GET and POST.
We'll use two methods to help prevent attacks on GET and POST requests.
1st Method: use of token
This method consists of including a random token (string) on each request, that is, a hidden field is added to each existing form with the token as value.
This token is generated by PHP and stored in a session for when a request exists, the system compares with the value that was automatically filled in (or malicious) on the form.
Further down we will join the 2 methods and create a script to prevent the attack. For now let's talk about the second method.
2nd Method: fields with random names
This method uses random names for each form field. The random value for each field is stored in a session variable. Each form submission, just like the token, generates a new random name for the field.
See an example of a POST request passing fields with the fixed name. I used the nome
and email
fields for example:
POST /form.php HTTP/1.1
Host: testes.loc
Cache-Control: no-cache
Postman-Token: d2b73c66-68fe-8dc6-4660-010a41a8c9b0
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="name"
filipe
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="email"
[email protected]
----WebKitFormBoundary7MA4YWxkTrZu0gW
Now using the technique described above, see how%% of each field was left:
//...
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="C345Gdfbn56789mnbg"
filipe
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="ERfbj567Mb867Jpknl6h"
[email protected]
----WebKitFormBoundary7MA4YWxkTrZu0gW
When making a new request, we see that name
has been changed again:
//...
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="Hgfkpso456jnbJYBV097"
filipe
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="670JK7hgdTJb60Kjh0I6T"
[email protected]
----WebKitFormBoundary7MA4YWxkTrZu0gW
Implementing the above methods:
You can improve the system and organize everything in classes to later reuse and even for organization / maintenance issues, for now let's just modify your code.
Modifying the code name
.
<?php
session_start();
if(isset($_SESSION['Token'], $_SESSION['TokenFieldName'], $_SESSION['LoginFieldName'], $_SESSION['SenhaFieldName'])) {
if (isset($_POST[$_SESSION['TokenFieldName']], $_POST[$_SESSION['LoginFieldName']], $_POST[$_SESSION['SenhaFieldName']])) {
if ($_POST[$_SESSION['TokenFieldName']] === $_SESSION['Token']) {
/*Valida $_POST['login'] e $_POST['senha']*/
} else {
echo 'Requisição invalida';
}
} else {
echo 'Faltam dados no Form';
}
}
//Apaga o token e os campos
//Isso é necessário, caso contrário bastava o atacante fazer um inspecionar elemento e ver os respectivos names no formulário e apenas executar um POST Request para o 'autenticar.php'.
//O token e os respectivos nomes serão gerados novamente no 'login.php', sendo assim terá sempre que passar pelo formulário.
unset($_SESSION['Token']);
unset($_SESSION['TokenFieldName']);
unset($_SESSION['LoginFieldName']);
unset($_SESSION['SenhaFieldName']);
Now, let's change the autenticar.php
:
<?php
//Função para gerar código randômico.
function generateRandomString($length = 15) {
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++)
$randomString .= $characters[rand(0, $charactersLength - 1)];
return $randomString;
}
//Gera um novo token
$_SESSION['Token'] = generateRandomString();
//Gera um nome aleatório para cada campo do formulário
$_SESSION['TokenFieldName'] = generateRandomString();
$_SESSION['LoginFieldName'] = generateRandomString();
$_SESSION['SenhaFieldName'] = generateRandomString();
?>
<form method="POST" action="autenticar.php">
<input type="hidden" name="<?=$_SESSION['TokenFieldName']?>" value="<?=$_SESSION['Token']?>" />
<input type="text" name="<?=$_SESSION['LoginFieldName']?>" placeholder="login"><br>
<input type="password" name="<?=$_SESSION['SenhaFieldName']?>" placeholder="senha"><br>
<button type="submit">Logar</button>
</form>
Ready, every time the user accesses the form, each field will have a name different from the previous one and a new token will be generated.