Search code examples
phpvue.jsemailsecurityphpmailer

How secure is my contact form using JS fetch and PHPMailer?


I'm currently building a small website with no real backend using vue-cli. As I'm a real potatoe when it comes to php (and backend in general), I was wondering how secure was my contact form on this website, and what to do if it's not secure enough.

To summarize, I'm making a fetch() call from my vue app to a mail.php file on my server. This mail.php file uses PHPMailer to send an email from one of my domain's email addresses to another of my addresses. And up to now it works fine.

Here is my JS related to sending mail :

  sendMail () {
    this.sendRequest()
      .then(r => {
        if (r.mailSent === 'ok') {
          // mail sent callback
        }
      })
  },
  async sendRequest () {
    const response = await fetch('./mail.php', {
      method: 'post',
      headers: {
        'content-type': 'application/json'
      },
      body: JSON.stringify({
        subject: 'Message de ' + this.firstNameInput + ' ' + this.lastNameInput + ' via mysite.com',
        body: '<html>' +
          '<body>' +
          '<p>De : ' + this.firstNameInput + ' ' + this.lastNameInput + '</p>' +
          '<p>Email : ' + this.mailInput + '</p>' +
          '<p>Message :<br>' + this.messageInput.replace(/\n/g, '<br>') + '</p>' +
          '</body>' +
          '</html>',
        altBody: 'De : ' + this.firstNameInput + ' ' + this.lastNameInput + ' /// Email : ' + this.mailInput + ' /// Message :' + this.messageInput
      })
    })
    return response.json()
  }

And here is the content of my mail.php file :

<?php

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

require './PHPMailer/src/Exception.php';
require './PHPMailer/src/PHPMailer.php';
require './PHPMailer/src/SMTP.php';

header('Content-Type: application/json');

include './mailSettings.php'; // contains $site, $mailFrom, $mailTo, $senderName and $recipientName

if(@isset($_SERVER['HTTP_REFERER']) && $_SERVER['HTTP_REFERER'] == $site) {
  $contentType = isset($_SERVER["CONTENT_TYPE"]) ?trim($_SERVER["CONTENT_TYPE"]) : '';

  if ($contentType === "application/json") {
    $content = stripslashes(trim(file_get_contents("php://input")));
    $decoded = json_decode($content, true);

    if(!is_array($decoded)) {
      echo '{"status":"error"}';
    } else {
      $mail = new PHPMailer(TRUE);
      try {
        $mail->setFrom($mailFrom, $senderName);
        $mail->addAddress($mailTo, $recipientName);
        $mail->Subject = $decoded['subject'];
        $mail->isHTML(TRUE);
        $mail->Body = $decoded['body'];
        $mail->AltBody = $decoded['altBody'];
        $mail->send();
      }
      catch (Exception $e) {
        echo $e->errorMessage();
      }
      catch (\Exception $e) {
        echo $e->getMessage();
      }
      echo '{ "mailSent": "ok"}';
    }
  } else {
    echo '{"status":"error"}';
  }
} else {
  echo '{"status":"no call accepted from this adress"}';
}
?>

I guessed that I still have stuff to do in order to make this secure, but can you guys help me find out what and why ?


Solution

  • You've done the most important thing right – not using user-submitted addresses – which helps prevent you turning into a spam gateway!

    First of all, for simple data capture like this, I'd avoid HTML altogether. Regular plain text will do the job.

    If you do want to use HTML, I recommend assembling it on the server side - that way you can do much stricter filtering on the simple input fields, for example using strip_tags, or using an allow-list of characters to filter fields (see the link @DigitalDrifter provided), before inserting them into a layout. This is much easier and safer than trying to cleanly filter pre-assembled, user-supplied HTML. This is also really simple to do as the JS and PHP code will be more or less identical.