Question :
I have a working contact form but it has a cross-sectional security hole in sitelock. Any help with overcoming the problem?
Thanks
<?php
header('Content-Type: text/html; charset=utf-8');
if(isset($_POST['email'])) {
// EDIT THE 2 LINES BELOW AS REQUIRED
$email_to = "mail que recebe";
$email_subject = "Mensagem vindo do site";
$first_name = $_POST['first_name']; // required
$email_from = $_POST['email']; // required
$telephone_from = $_POST['telephone']; // required
$subject = $_POST['subject']; // required
$comments = $_POST['message']; // required
$email_message = "Mensagem vindo do sitenn";
function clean_string($string) {
$bad = array("content-type","bcc:","to:","cc:","href");
return str_replace($bad,"",$string);
}
$email_message .= "Name: ".clean_string($first_name)."n";
$email_message .= "Email Address: ".clean_string($email_from)."n";
$email_message .= "Telephone: ".clean_string($telephone_from)."n";
$email_message .= "Subject: ".clean_string($subject)."n";
$email_message .= "Message: ".clean_string($comments)."n";
// create email headers
$headers = 'From: '.$email_from."rn".
'Reply-To: '.$email_from."rn" .
'X-Mailer: PHP/' . phpversion();
@mail($email_to, $email_subject, $email_message, $headers);
?>
<?php
}
?>
<form id="contact-form" action="contact.php" name="contactform" class="row" method="post">
<div id="input_name" class="col-md-6">
<input type="text" name="first_name" id="name" class="form-control triggerAnimation animated" data-animate="bounceIn" placeholder="Nome">
</div>
<div id="input_telephone" class="col-md-6">
<input type="text" name="telephone" id="telephone" class="form-control triggerAnimation animated" data-animate="bounceIn" placeholder="Telefone">
</div>
<div id="input_email" class="col-md-12">
<input type="text" name="email" id="email" class="form-control triggerAnimation animated" data-animate="bounceIn" placeholder="Email">
</div>
<div id="input_subject" class="col-md-12">
<input type="text" name="subject" id="subject" class="form-control triggerAnimation animated" data-animate="bounceIn" placeholder="Assunto">
</div>
<div id="input_message" class="col-md-12">
<textarea class="form-control triggerAnimation animated" data-animate="bounceIn" name="message" id="comments" rows="6" placeholder="Gostaria de ser contactado(a) para mais informações"></textarea>
</div>
<div id="form_btn" class="col-md-12">
<div class="text-center">
<div id='recaptcha' class="g-recaptcha"
data-sitekey=""
data-callback="onSubmit"
data-size="invisible">
<script>onload();</script>
<input type="submit" value="Quero ser contactado(a)" id="submit" class="btn btn-theme triggerAnimation animated" data-animate="bounceIn">
</div>
</div>
<input type="hidden" name="CSRF" value="<?= $_SESSION['CSRF'] ?>">
</form>
Answer :
I do not guarantee that this answer will correct all problems, there may be other security problems!
This is the function:
bool mail ( string $to , string $subject , string $message [, string $additional_headers [, string $additional_parameters ]] )
What happens is EXACTLY in the last two parameters of mail()
, which is $additional_headers
and $additional_parameters
, this fifth parameter is even worse and I will not even talk about it.
$additional_headers
informs the split headers by a CRLF ( rn
) exactly as you are doing at the end of each line.
How to attack:
As the attack works, the best way to protect yourself is to know how exactly the attack works.
This is what you have:
$headers = 'From: '.$email_from."rn".
'Reply-To: '.$email_from."rn" .
'X-Mailer: PHP/' . phpversion();
Note that if the value of $email_from
is:
meu@email.comrnBcc:seu@email.com
It will cause:
$headers = 'From: meu@email.comrn'.
Bcc:seu@email.comrn'. // <<<
'Reply-To: meu@email.comrn'.
'Bcc:seu@email.comrn" . // <<<
'X-Mailer: PHP/' . phpversion();
This will be the result.
Proof:
curl ^
-X POST ^
-d "first_name=Nome&telephone=123456789&email=meu@email.com%0ACc:seu@email.com&subject=teste3&message=teste3" ^
https://seu-link.com
Look exactly at the value of email
, it’s what’s important:
email=meu@email.com%0ACc:email-de@outra-pessoa.com
This will cause the email
field to equal meu@email.com
with %0A
which is CR
at HTML URL Encoding (or %0D%0A
) and finally Cc:seu@email.com
.
With this another person will also receive the email, which can even mention more than one.
But why clean_string
does not work to remove the Cc
you used? Simple, it is case-sensitive, only str_ireplace
is case-insensitive. The Cc
is different from cc
. In general, blacklist does not work . It is much easier to say what is allowed than to list everything that is forbidden!
See this:
function clean_string($string) {
$bad = array("content-type","bcc:","to:","cc:","href");
return str_replace($bad,"",$string);
}
echo clean_string("BCC: fulano@email.comrn Cc: ciclano@email.com");
Return:
BCC: fulano@email.com Cc: ciclano@email.com
This is USEFUL
How to protect:
Do not use the last argument, nor will I mention it here because I honestly do not even know how to protect it. However the fourth argument has salvation, but first I will list all the errors that the code has:
1. CSRF:
Add the CSRF-Token, simply use a good CSPRNG and save it in the session, for example:
session_start();
if(!isset($_SESSION['CSRF'])){
$_SESSION['CSRF'] = unpack('H*', random_bytes(64))[1];
}
Then add:
<input type="hidden" name="CSRF" value="<?= $_SESSION['CSRF'] ?>">
Then compare both values, which was sent and stored in the session:
if(hash_equals($_SESSION['CSRF'], $_POST['CSRF']){
// O CSRF é igual!
}
Do not use ===
, timing-attack. And of course, do not use ==
, especially if you use json_decode
/ unserialize
, because "qualquer_coisa" == true
is always true
, which was what Laravel 4 (haha) .
2. Remove CRLF:
$email_from = str_ireplace(["r", "n", ':', ','], '', $email_from);
That way my code would drop by replace below. : ‘(Because in this case it would become:
meu@email.comCcemail-de@outra-pessoa.com
I have added :
and ,
so that in the worst case it fails and both are not valid values for e-mail characters, as far as I know.
3. HTML:
The third issue is that it would be ideal to use htmlentities
, like this:
$email_message = htmlentities($email_message, ENT_QUOTES || ENT_HTML5, 'UTF-8');
This would ensure that no messages would have HTML, mainly <
, >
to prevent them from inserting a <script>
, the same technique to avoid XSS, the parameters used are to define which coding is used, For this to work, you must also determine the language and format, for example:
$headers .= 'Content-Type: text/html; charset=UTF-8' . "rn";
In your case:
Requires PHP 7.0.0 +
I believe this would be enough to run the three cases above, there are even possible improvements, like checking if it really is an email, if it really is a phone number and the like. All the edits I’ve made are commented out in the code.
<?php
session_start();
// Adicionado geração de CSRF-Token:
if (!isset($_SESSION['CSRF'])) {
$_SESSION['CSRF'] = unpack('H*', random_bytes(64))[1];
}
header('Content-Type: text/html; charset=utf-8');
// Adicionado comaparação de CSRF-Token e todos os campos são necessários serem preenchidos:
if (isset($_POST)
&& hash_equals($_POST['CSRF'], $_SESSION['CSRF'])
) {
$email_to = "mail que recebe";
$email_subject = "Mensagem vindo do site";
$first_name = $_POST['first_name'];
$email_from = $_POST['email'];
$telephone_from = $_POST['telephone'];
$subject = $_POST['subject'];
$comments = $_POST['message'];
// Adicionado remoção de CRLF:
$email_from = str_ireplace(["r", "n", ':', ','], '', $email_from);
$email_message = "Mensagem vindo do sitenn";
$email_message .= 'Name: ' . $first_name . "n";
$email_message .= 'Email Address: ' . $email_from . "n";
$email_message .= 'Telephone: ' . $telephone_from . "n";
$email_message .= 'Subject: ' . $subject . "n";
$email_message .= 'Message: ' . $comments . "n";
// Adicionado htmlentitites para mensagem:
$email_message = htmlentities($email_message, ENT_QUOTES || ENT_HTML5, 'UTF-8');
// Adicionado Content-Type:
$headers = 'Content-Type: text/html; charset=UTF-8' . "rn";
$headers .= 'From: ' . $email_from . "rn";
$headers .= 'Reply-To: ' . $email_from . "rn";
$headers .= 'X-Mailer: PHP/' . phpversion();
mail($email_to, $email_subject, $email_message, $headers);
?>
<!-- Message sent! (change the text below as you wish)-->
<div class="container">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<div id="form_response" class="text-center">
<img class="img-responsive" src="../imagens/thumbs/mail_sent.png" title="image" alt="imagem" />
<h1>Parabéns!</h1>
<p>Obrigado <b><?= htmlentities($first_name, ENT_QUOTES || ENT_HTML5, 'UTF-8'); ?></b>, a sua mensagem foi enviada</p>
<a class="btn btn-theme" href="">Voltar</a>
</div>
</div>
</div>
</div>
<!--End Message Sent-->
<?php
}
?>
<form id="contact-form" action="contact.php" name="contactform" class="row" method="post">
<div id="input_name" class="col-md-6">
<input type="text" name="first_name" id="name" class="form-control triggerAnimation animated"
data-animate="bounceIn" placeholder="Nome">
</div>
<div id="input_telephone" class="col-md-6">
<input type="text" name="telephone" id="telephone" class="form-control triggerAnimation animated"
data-animate="bounceIn" placeholder="Telefone">
</div>
<div id="input_email" class="col-md-12">
<input type="text" name="email" id="email" class="form-control triggerAnimation animated"
data-animate="bounceIn" placeholder="Email">
</div>
<div id="input_subject" class="col-md-12">
<input type="text" name="subject" id="subject" class="form-control triggerAnimation animated"
data-animate="bounceIn" placeholder="Assunto">
</div>
<div id="input_message" class="col-md-12">
<textarea class="form-control triggerAnimation animated" data-animate="bounceIn" name="message" id="comments"
rows="6" placeholder="Gostaria de ser contactado(a) para mais informações"></textarea>
</div>
<!-- Adicionado campo 'CSRF' -->
<input type="hidden" name="CSRF" value="<?= $_SESSION['CSRF'] ?>">
</form>
PHP has the filter_var () function to filter specific content.
In the case of the mail () function, it is interesting to use FILTER_SANITIZE_STRING for body and headers and use a FILTER_SANITIZE_EMAIL specifically for sender, recipient, and other email addresses.
It’s very important that you know what you want to receive in the email. Use the strip_tags () function to remove all unwanted HTML content from the body of the message, or allow only a restricted set of tags necessary to its purpose, thus avoiding maliciously the user inserting tags in the body of the email. What would specifically address Cross Scripting.
Since it’s always better to be overly secure, I’ve added @Inkeliz’s comment to your code, removing line breaks:
// limpa dados para o corpo da mensagem
function clean_string($string, $extra) {
$bad = array("content-type","bcc:","to:","cc:","href");
$tags_permitidas = "<p><br>";
if (is_array($extra)) {
$bad = array_merge($bad, $extra);
}
$string = filter_var($string, FILTER_SANITIZE_STRING);
return strip_tags(str_replace($bad,"",$string), $tags_permitidas);
}
// limpa dados para os cabeçalhos
function clean_string_eol($string) {
return clean_string($string, array("r", "n"));
}
$email_message .= "Name: ".clean_string_eol($first_name)."n";
$email_message .= "Email Address: ".filter_var($email_from, FILTER_SANITIZE_EMAIL)."n";
$email_message .= "Telephone: ".clean_string_eol($telephone_from)."n";
$email_message .= "Subject: ".clean_string_eol($subject)."n";
$email_message .= "Message: ".clean_string($comments)."n";