What is the best way to turn a secure contact form?

3

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 site\n\n";


                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."\r\n".

                'Reply-To: '.$email_from."\r\n" .

                '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>
    
asked by anonymous 12.05.2017 / 14:46

2 answers

4
  

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 ( \r\n ) 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."\r\n". 
           'Reply-To: '.$email_from."\r\n" . 
           'X-Mailer: PHP/' . phpversion();

Note that if the value of $email_from is:

[email protected]\r\nBcc:[email protected]

It will cause:

$headers = 'From: [email protected]\r\n'.
            Bcc:[email protected]\r\n'.   // <<<
           'Reply-To: [email protected]\r\n'.
           'Bcc:[email protected]\r\n" .  // <<<                     
           'X-Mailer: PHP/' . phpversion();

This will be the result.

Proof:

curl ^
-X POST ^
-d "first_name=Nome&telephone=123456789&[email protected]%0ACc:[email protected]&subject=teste3&message=teste3" ^
https://seu-link.com

Look exactly at the value of email , it's what's important:

[email protected]%0ACc:[email protected]

This will cause the email field to equal [email protected] with %0A which is CR at HTML URL Encoding (or %0D%0A ) and finally Cc:[email protected] .

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: [email protected]\r\n Cc: [email protected]");

Return:

BCC: [email protected] Cc: [email protected]

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:

  • Lack of CSRF.
  • Not handling CRLF.
  • The text of the message is not being handled.
  • 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:

    [email protected]@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' . "\r\n";
    

    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 site\n\n";
    
        $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' . "\r\n";
    
        $headers .= 'From: ' . $email_from . "\r\n";
        $headers .= 'Reply-To: ' . $email_from . "\r\n";
        $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>
    
        
    12.05.2017 / 17:52
    0

    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";
    
        
    12.05.2017 / 15:48