Search code examples
phpsecuritymodel-view-controllerreplay

a good approach to prevent replay attack in websites with php


I'm a noobie in web development. i'm designing an MVC login page and to prevent replay attacks I create a session whenever the user requests the login page. My code is as below:

public function login(){
  if($_SERVER['REQUEST_METHOD'] == 'GET') {
  session_start();
  $_SESSION["requestToken"] = generateToken(3);

}else if ($_SERVER['REQUEST_METHOD'] == 'POST'){
  session_start();
  if (isset($_SESSION["requestToken"])){
     if(successfulLogin()){
     unset($_SESSION["requestToken"]);
     session_destroy();
    }
}else{

    header("location:localhost:public/users/login");
}
}

I read an article in which a similar method was implemented to prevent replay attacks but it said, I quote, "This is far from a complete solution. It has flaws and pending issues". Here

can you please help me figure out the issues of my approach and tell me which approach is the best to apply?


Solution

  • I am late but I hope my answer will help some others who are trying to protect their Webpages / APIs from replay attack. I use the following method to protect my APIs from replay attacks

    App side

    1. Generate a unique device_id/token on server for a device when your app is used for the first time or if your app requires login then you can generate token on login
    2. Whenever app makes a call to server it adds the following in HTTP headers
      • Token
      • 128 characters long random value (Random1)
      • Current Unix Timestamp of device
      • SHA256 of "Token+|||+Random1+|||+Timestamp" (Random2)

    Server side

    When API receives a call it verifies headers like this

    • Check if Token is a valid token that exists in database (you can implement expiry mechanism to make it more secure) -

    • Check if Timestamp is valid

    • Generate SHA256 of "Token+|||+Random1+|||+Timestamp" and compare it with Random2 of header.

    • Check if this SHA256 result does not already exists in your database

    • If all checks are passed then save this SHA256 result in database against this token.

    You can make it more secure by adding more random numbers. In my example you can see I have 3 pipes hardcoded twice. You can make number of pipes random.

    Sample Server Side PHP Code

    //This function will perform all the steps to validate the request
    //returns TRUE if all good else FALSE
    function headers_valid(){
        $headers=getallheaders();
        if (!isset($headers["Token"])
            || !isset($headers["Random1"]) 
            || !isset($headers["Timestamp"]
            || !isset($headers["Random2"])
            ){
                return FALSE;
            }
        
        $token=$headers["Token"];
        $random1=$headers["Random1"];
        $timestamp=$headers["Timestamp"];
        $random2=$headers["Random2"];
    
        if (!is_numeric($timestamp)){
            return FALSE;
        }
    
        $ts=intval($timestamp);
        $tsNow=time();
        $diffTs=$tsNow-$ts;
        if ($diffTs<0){
            $diffTs*=-1;
        }
        //extra check to make sure that timestamp is not off 
        //by 1 or more minutes
        if ($diffTs>=60){
            return FALSE;
        }
    
        $hashInput=$token."|||".$random1."|||".$timestamp;
        $calcHash=strtolower(hash("sha256",$hashInput));
        if (strtolower($random2)!=$calcHash){
            return FALSE;
        }
        //add your database code. following is a dummy database code
        $row=resultof("SELECT hash FROM request_hash WHERE hash='".$calcHash."'");
        //if row is returned by this query then return false
        if (row_exists($row))
            return FALSE;
        //all good now you can store this hash in your database
        execute_query("INSERT INTO request_hash(hash) VALUES('".$calcHash."')");
        return TRUE;
    }
    

    I hope my answer will help others who are looking to protect against Replay Attacks