2

I've created a register and email verification system on my website. The process is as follows:

  1. User submits email and password
  2. Verification link is emailed
  3. User info is swapped from unverified to verified DB if link is visited

However, I've noticed an interesting "bug" if you'd even call it that.

If I simply open the email in Hotmail without even visiting the link, somehow the user info is swapped from unverified to verified as if the link was clicked.

I'm baffled...

How is this so? Why would this be occurring?

You can try it for yourself by visiting http://www.pillar.fyi/redflagreviews/signinup.php/

By default, the slider is in the "register" position, whereas the other position is for logging in.

PHP (it's messy, forgive me)

<?php
  session_start();
  session_regenerate_id();
  
  if ($_SESSION["session"])
  {
    echo "<script type='text/javascript'> location.href = 'http://www.pillar.fyi/redflagreviews/index.php' </script>";
  }
  else
  {    
    if (!empty($_SERVER["QUERY_STRING"])) # ... && regex to filter out junk
    {
      
      parse_str($_SERVER["QUERY_STRING"]);
      
      include "include/connect.php";
      
      # compare token
      $statement = $connect->prepare("SELECT entry_time, account_email, account_password, token FROM users_unverified WHERE account_email = :account_email");
      $statement->bindParam(":account_email", $email);
      $statement->execute();
      $result = $statement->fetch(PDO::FETCH_ASSOC);
      
      if ($result["token"]
      && $result["token"] === $token
      && $result["entry_time"] > time() - 600) # token matches
      {
        # move info to users_verified
        $statement = $connect->prepare("INSERT INTO users_verified (account_email, account_password, joined) VALUES (:account_email, :account_password, :joined)");
        $statement->bindParam(":account_email", $result["account_email"]);
        $statement->bindParam(":account_password", $result["account_password"]);
        $statement->execute(array(
          ":account_email" => $result["account_email"],
          ":account_password" => $result["account_password"],
          ":joined" => date("Y-m-d")
        ));
        
        # delete old entry
        $statement = $connect->prepare("DELETE FROM users_unverified WHERE account_email = :account_email");
        $statement->bindParam(":account_email", $email);
        $statement->execute();
        
        # now redirect to login screen (or update message)
        echo "Your email address has been verified and is now active. Sign in below to begin sharing!";
        
        $connection = null; # may be useless
      }
      else                                     # being hacked or token has expired
      {
        
        if ($result["token"]) { echo 'true'.'<br><br>'; }
        else { echo '1. false -- $result["token"] -- '.$result["token"].'<br><br>'; }
        
        if ($result["token"] === $token) { echo 'true'.'<br><br>'; }
        else { '2. false -- $result["token"] === $token -- ' . $result["token"]. ' === ' .$token.'<br><br>'; }
        
        if ($result["entry_time"] > time() - 600) { echo 'true'.'<br><br>'; }
        else { '3. false -- $result["entry_time"] > time() - 600 -- '. $result["entry_time"] . ' > ' . time() - 600 .'<br><br>'; }
        
        
        echo "Your verification code has expired. Would you like us to send you a new verification link?";
        # if yes, then... (add num_id? to SELECT query above)
        #####################################################
        #####################################################
      }
    }
    else # might cause issues as ELSE
    {
if ($_SERVER["QUERY_STRING"]) echo $_SERVER["QUERY_STRING"];
else echo 'query string is false<br><br>';
      if ($_SERVER["REQUEST_METHOD"] === "POST")
      {
        include "include/connect.php";
        
        if ($_POST["action"] === "0") # register
        { 
          ######################################
          ###                                ###
          ###    prohibit certain domains    ###
          ###    such as spamgourmet.org     ###
          ###                                ###
          ######################################
          
          $statement = $connect->prepare("SELECT account_email FROM users_verified WHERE account_email = :account_email");
          $statement->bindParam(":account_email", $_POST["email"]);
          $statement->execute();
          $result = $statement->fetch(PDO::FETCH_ASSOC);
          
          if ($result["account_email"]) # email already in use
          {
            echo "The email account " . $result['account_email'] . " has been registered already.";
          }
          else                          # new account, unknown email address
          {
          
            # first, check users_unverified to see if verification process is active
            
            $statement = $connect->prepare("SELECT entry_time FROM users_unverified WHERE account_email = :account_email");
            $statement->bindParam(":account_email", $_POST["email"]);
            $statement->execute();
            $result = $statement->fetch(PDO::FETCH_ASSOC);
            
            if ($result["entry_time"]
            && $result["entry_time"] > time() - 600) # verification process is already active
            {
              echo "Your account is awaiting verification. The verification code will remain active for another "."###"." Would you like us to resend the verification code?";
            }
            else                                                               # initiate the verification process
            {
              # delete old entry
              $statement = $connect->prepare("DELETE FROM users_unverified WHERE account_email = :account_email");
              $statement->bindParam(":account_email", $_POST["email"]);
              $statement->execute();
              
              # send verification code
              echo "An email with a verification link has been sent to your email address. Please verify your ownership of the email account by clicking the link in that email. The verification code will expire in 10 minutes!";
              
              $password  = password_hash($_POST["password"], PASSWORD_DEFAULT);
              $token     = md5($password, FALSE);
              $statement = $connect->prepare("INSERT INTO users_unverified (entry_time, account_email, account_password, token) VALUES (:entry_time, :account_email, :account_password, :token)");
              $statement->execute(array(
                ":entry_time" => time(),
                ":account_email" => $_POST["email"],
                ":account_password" => $password,
                ":token" => $token
              ));
              
              # send verification email
              include "include/sanitize.php";
              
              $email = sanitize($_POST["email"]);
              
              mail($_POST["email"],
              "Account Verification",
              "Please click the following link to activate your account: http://www.pillar.fyi/redflagreviews/signinup.php/?email=".$email."&token=".$token,
              "From: aj@pillar.fyi",
              "-f aj@pillar.fyi");
            }
          }
          $connect = null;
        }
        else # sign in
        {
          $statement = $connect->prepare("SELECT account_password FROM users_verified WHERE account_email = :account_email");
          $statement->bindParam(":account_email", $_POST["email"]);
          $statement->execute();
          $result = $statement->fetch(PDO::FETCH_ASSOC);
          
          if ($result["account_password"]
          && password_verify($_POST["password"], $result["account_password"])) # successfully logged in, redirect to main page
          {
            $connect = null;
            
            $_SESSION["session"] = $_POST["email"];
            
            echo "<script type='text/javascript'> location.href = 'http://www.pillar.fyi/redflagreviews/index.php' </script>";
          }
          else if ($result["account_password"])                                # failed login
          {
            echo "That is the wrong password.";
            
            $idPersist = $_POST["email"]; # maybe sanitize?
          }
          else {
            echo "That account does not exist.";
          }
        }
      }
    }
  }
    
?>
<!DOCTYPE html>
<html>
<head>
  <title>&lrm;</title>
  <meta charset="UTF-8">
  <!--link href="css/index.css" rel="stylesheet"-->
</head>
<body>
  <form accept-charset="UTF-8" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>" enctype="application/x-www-form-urlencoded" method="POST">
    <input type="range"    name="action"
           min="0" max="1" step="1" value="0" id="action"
           > <!-- use PHP variable to set as register or sign in -->
    <input type="text"     name="email"    placeholder="email"    value="<?php echo $idPersist; ?>" <?php if (!$idPersist) echo "autofocus"; ?> required>
    <input type="password" name="password" placeholder="password"                                   <?php if ($idPersist) echo "autofocus"; ?>  required autocomplete>
    <input type="submit"                                          value="&#10149;">
  </form>
  <!--script type="text/javascript" src="js/index.js"></script-->
</body>
</html>
Community
  • 1
  • 1
oldboy
  • 5,729
  • 6
  • 38
  • 86
  • A little bit of context to how you're doing it would be very helpful – Sam Mar 23 '18 at 01:14
  • @Samuel added the code. you can try it for yourself! – oldboy Mar 23 '18 at 01:16
  • 2
    This does not sound like it is related to php, it is very possible the links are followed and check for malicious content by hotmail. Have you tested outlook or thunderbird? – Alex Barker Mar 23 '18 at 01:18
  • @AlexBarker that was my initial thought, but why would Hotmail scan it **at the very moment** i open it?? the info literally isn't swapped until the moment i open the email in the Hotmail client :/ No idea what thunderbird is?? Haven't tested any other platforms, just Hotmail/Outlook from the browser. – oldboy Mar 23 '18 at 01:20
  • I have no idea, but I would definitely try something simpler for your mail client. You could look at the network tab in your browsers debugger to see if it is indeed calling. – Alex Barker Mar 23 '18 at 01:22
  • @AlexBarker something simpler? what do you mean? would you mind testing it via your email client? you should be able to immediately log in by opening the email but without even clicking the link – oldboy Mar 23 '18 at 01:24
  • 1
    "why would Hotmail scan it at the very moment i open it?" Doing so means they don't have to spend resources scanning all the emails you delete *without* opening. It also means that scan is as up-to-date as possible against their latest database of malware websites/techniques. – ceejayoz Mar 23 '18 at 01:26
  • @ceejayoz makes sense, but if they do detect that it's malicious at that point in time, what can they possibly do at that time to prevent me from clicking the link? – oldboy Mar 23 '18 at 01:29
  • @Anthony It'd be absolutely trivial to intercept that click with JavaScript and present a warning. – ceejayoz Mar 23 '18 at 01:30
  • if any of you guys don't use hotmail/outlook, can one of you guys test it with whatever email client you use? – oldboy Mar 23 '18 at 01:30
  • @ceejayoz of course, but it would take longer to inject the JS than it would for me to click the link once i've opened the email? likewise, it's a verification email, ppl or at least i personally open such emails and click almost immediately – oldboy Mar 23 '18 at 01:32
  • @Anthony I'm not inclined to do a round of 20 questions here. I've told you what I suspect it is, and given a reasonable explanation of why and how it'd work. – ceejayoz Mar 23 '18 at 01:36
  • @ceejayoz lol huh? – oldboy Mar 23 '18 at 01:38
  • @Anthony why not make the user login to their account to confirm their email address? – Sam Mar 23 '18 at 13:57
  • @Samuel What do you mean? Log in to which account? Log in before being able to verify by email? Your comment is kind of ambiguous – oldboy Mar 23 '18 at 15:56
  • @Anthony once the user signs up, he can follow your link with the token you provided to them; you then allow them to login, if they login successfully verify their account - if not account stays unverified. I'm not sure I can make any simpler than that. – Sam Mar 23 '18 at 15:58
  • @Samuel that makes sense. i like that idea. – oldboy Mar 23 '18 at 20:32

0 Answers0