Предотвратить перехват сеанса, фиксацию, инъекцию и т. д.

Я создаю систему входа в систему и много читал о мерах безопасности, необходимых для предотвращения перехвата сеанса, фиксации, инъекций и т. д. Я определенно не эксперт по безопасности — я собрал многое из этого с помощью из сообщений на этом сайте и различных других. Эти два сообщения о переполнении стека были особенно полезны: Link Ссылка 2

Прочитав их, я в итоге внес много изменений и улучшений в свою систему, но я не уверен, что защищен от всего. Ниже то, что я придумал до сих пор. Я подозреваю, что что-то из этого будет правильным, что-то неправильным, а что-то может быть излишним. Вот основной поток того, что происходит:

<?php

// convert password to hash
function passwordHash($string) {
  return password_hash($string, PASSWORD_DEFAULT);
}
// compare password to hash
function passwordVerify($string, $hash) {
  return password_verify($string, $hash);
}


// authenticate login password
function login($submitted_password, $password, $username) {
  global $link;

  if(passwordVerify($submitted_password, $password)) {
    ini_set('session.use_trans_sid', FALSE);
    ini_set('session.entropy_file', '/dev/urandom');
    ini_set('session.hash_function', 'whirlpool');
    ini_set('session.use_only_cookies', TRUE);
    ini_set('session.cookie_httponly', TRUE);
    ini_set('session.cookie_lifetime', 1200);
    ini_set('session.cookie_secure', TRUE);
    session_start();

    $link->query("SELECT id, password, username, user_level FROM users WHERE username = :username");
    $link->bind(':username', $username);
    $link->execute();
    $row = $link->getOneRow();
    $link->closeStream();

    $id = $row['id'];
    $username = $row['username'];
    $user_level = $row['user_level'];

    $_SESSION['userID'] = $id;
    $_SESSION['username'] = $username;
    $_SESSION['user_level'] = $user_level;
    $_SESSION['user_ip'] = $_SERVER['REMOTE_ADDR'];
    $_SESSION['HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'];

    // store in db to use in page_protect()
    $user_ip = $_SERVER['REMOTE_ADDR'];
    $useragent_hash = passwordHash($_SESSION['HTTP_USER_AGENT']);

    $link->query("UPDATE users SET user_ip = :user_ip, useragent_hash = :useragent_hash WHERE id = :id");
    $link->bind(':user_ip', $user_ip);
    $link->bind(':useragent_hash', $useragent_hash);
    $link->bind(':id', $id);
    $link->execute();
    $link->closeStream();
    header("Location: dashboard.php");
    exit();   
  } else {
    $error = "Username or password error"; // password fails
  }
}


// function called by every page requiring you to be logged in
function page_protect() {
  global $link;

  if (session_status() == PHP_SESSION_NONE) {
    session_start();
  }

  if(!isset($_SESSION['user_ip']) || $_SESSION['user_ip'] != $_SERVER['REMOTE_ADDR']) {
    logout();
    exit();
  }

  // referenced in question #5 below
  if (!isset($_SESSION['HTTP_USER_AGENT']) || $_SERVER['HTTP_USER_AGENT'] != $_SESSION['HTTP_USER_AGENT']) {
    logout();
    exit();
  }   

  // check that IP address/useragent hash stored in db at login match current session variables
  $link->query("SELECT useragent_hash, user_ip FROM users WHERE username = :username");
  $link->bind(':username', $_SESSION['username']);
  $link->execute();
  $row = $link->getOneRow();
  $link->closeStream();

  $user_ip = $row['user_ip'];
  $useragent_hash = $row['useragent_hash'];

  if($_SESSION['user_ip'] != $user_ip || !passwordVerify($_SESSION['HTTP_USER_AGENT'], $useragent_hash)) {
    logout();
    exit();
  }

  session_regenerate_id(true);
}


function logout() {
  if (session_status() == PHP_SESSION_NONE) {
    session_start();
  }

  unset($_SESSION);
  session_unset();
  session_destroy();
  header("Location: index.php");
  exit();
}

?>

1) Я что-то упустил или что-то не так?

2) Я не уверен, что использую session_regenerate_id() в нужном месте. Я знаю, что его нужно вызывать периодически, поэтому я решил, что каждый раз, когда вызывается защищенная страница, было бы хорошо.

3) Все ini_set в #4 появляются только перед session_start() в функции входа в систему. Однако в верхней части каждой страницы, защищенной с помощью page_protect(), есть файл session_start(). Должны ли ему предшествовать одни и те же ini_set на каждой странице, или после того, как они установлены во время первоначального входа в систему, они остаются установленными?

4) Должен ли я удалить эту строку? ini_set('session.cookie_lifetime', 1200); Это потребует от пользователя повторного входа в систему через 20 минут. Думаю, было бы хорошо, если бы пользователь вышел из системы после 20 минут бездействия, а не после 20 минут перемещения по сайту.

5) В page_protect, когда я проверяю хэш ip и пользовательского агента, должен ли я использовать && вместо ||? Если оба условия соблюдены, что-то определенно не так.

Любая помощь приветствуется.


person John    schedule 30.12.2018    source источник
comment
codereview.stackexchange.com   -  person    schedule 30.12.2018
comment
Я не знал об этом, спасибо.   -  person John    schedule 30.12.2018
comment
codereview.stackexchange.com/questions/ 210584/   -  person John    schedule 30.12.2018
comment
Ваш код БД неверен, как и некоторые другие вещи.   -  person ArtisticPhoenix    schedule 30.12.2018