Questa è una risposta alla mia domanda precedente, Questa area di amministrazione è sufficientemente sicura? .
Ci sono state alcune risposte molto utili lì, per le quali sono molto grato. Così sono tornato al tavolo da disegno ed ecco la mia nuova domanda, perché sono nuovo per il lato sicurezza delle cose e non sono sicuro di aver capito o coperto tutti i buchi del ciclo correttamente .
I problemi che sto cercando di proteggere dal sistema sono:
- Man in the Middle attacks
- attacchi SQL injection
- attacchi CSRF
- attacchi XSS
- Click jacks
- Attacchi di forza bruta
Note
- Le password sono memorizzate con questa implementazione di bcrypt
- L' intero sito utilizzerà il protocollo https: //
- Flags session.cookie_secure e session.cookie_httponly essere impostato
- L' oggetto PDO di PHP viene utilizzato in tutte le query SQL, tutti gli input sono parametrizzati
- Qualsiasi variabile di input dell'utente verrà disinfettata
- Ogni pagina inizierà con un'intestazione http
x-frame-options: deny
- Ogni pagina con restrizioni inizia con uno script di autorizzazione (vedere la figura 1)
- Tutti i moduli saranno autorizzati solo se la variabile impostata
$_SESSION["auth_key"]
corrisponde alla chiave inviata con il modulo (vedi figura 1)
Fig.1: Autorizzazione & Script di accesso
Sperochequestodiagrammasiaperlopiùautoesplicativo.Tuttavia,cercheròdichiarirealcunefunzioni.
- controllatoken:untokengeneratounavoltacheunutenteaccede,memorizzatoin
$_SESSION['auth_token']
einviatoconqualsiasiformachel'utenteinvia. - verificasessione:sel'utentehauntokenvalido,estraeirelatividettaglidaldatabaseutilizzandoPHPSESSID(ilnomeènecessarioperdareilbenvenutoall'utente).Sel'IDdisessionenonvienetrovatooiltokennonèvalido,lalorosessioneèscadutaedevonoeffettuarenuovamenteillogin.
- Regen.sessione:unavoltachel'utentehaeffettuatol'accessoconsuccesso,dagliunnuovoIDdisessione(
session_regenerate_id()
)eaggiornaildatabaseconilnuovoIDdisessione.Imposta$_SESSION['auth_token']=md5(mt_rand(1,10000).$username);
.Ilnomeutenteèunico,quindiquestodovrebbegenerareuntokendiautenticazioneunivocoinmodochepossanoinviareiproprimoduli.Nonsonosicurodiaverloimplementatocorrettamente,perfavorevediesempio qui - verifica login: controlla semplicemente che la password briptata corrisponda all'ingresso briptato e che i nomi utente corrispondano.
- tentativi falliti: questo lato viene aggiunto per rallentare gli attacchi di forza bruta; prima viene aggiunto un captcha, quindi l'IP viene temporaneamente bloccato e l'utente / titolare dell'account riceve un'e-mail di notifica.
- Aggiungerò ulteriori dettagli se necessario / correggi questi dettagli in caso di errori
Implementazione (finora):
Pagina con restrizioni: inizia con il requisito di authenticate.php
<?php require_once "authenticate.php"; ?>
<p>Welcome <?php echo $_SESSION["guest"]; ?>!
<a href="index.php?action=logout">Logout</a></p>
<p><a href="index.php?token=<?php echo $_SESSION["auth_token"] ?>">Try again.</a></p>
Authenticate.php: reindirizza a login.php
se non autenticato
Nota: ho lasciato fuori la crittografia (durante il test) perché bcrypt
non è disponibile nella mia versione di PHP ( 5.2.17) quindi sto cercando SHA256 o SHA512.
<?php
session_start();
// load database abstraction layer
require_once "../../dal.php";
unset($_SESSION["login_errors"]);
function loginError($str) {
if (isset($_SESSION["login_errors"]))
$_SESSION["login_errors"] = "" . $_SESSION["login_errors"] . "; " . $str;
else
$_SESSION["login_errors"] = $str;
}
function hasValidToken() {
return (isset($_SESSION["auth_token"]) && isset($_GET["token"])
&& $_SESSION["auth_token"] == $_GET["token"]);
}
function hasValidSession() {
return (session_id()? hasOpenSession() : false);
}
function hasOpenSession() {
$sql = "SELECT * FROM tbl_store_admin WHERE php_sesskey=?;";
$data = array(session_id());
return (dbRowsCount($sql, $data) == 1);
}
function updateUserSession() {
$sql = "UPDATE tbl_store_admin SET php_sesskey=? WHERE username=?";
$data = array(session_id(), $_SESSION["uid"]);
dbQuery($sql, $data);
return (dbRowsAffected() == 1);
}
function hasLoggedIn() {
if (isset($_POST["uid"]) && isset($_POST["key"])) {
$uid = htmlspecialchars($_POST["uid"]);
$key = htmlspecialchars($_POST["key"]);
return (getUser($uid, $key));
} else {
return false;
}
}
function wrongCredentials() {
if (isset($_SESSION["login_attempts"])) {
$_SESSION["login_attempts"] = $_SESSION["login_attempts"]+1;
} else {
$_SESSION["login_attempts"] = 1;
}
logout();
loginError("Bad Credentials");
return false;
}
function getUser($uid, $key) {
$sql = "SELECT * FROM tbl_store_admin WHERE username=? AND keycode=? LIMIT 1;";
$data = array($uid, $key);
$rows = dbRowsCount($sql, $data);
if ($rows == 1) {
dbQuery($sql, $data);
$user = dbFetch();
// store data in session
$_SESSION["uid"] = $user["username"];
$_SESSION["guest"] = $user["nickname"];
return true;
} else {
return wrongCredentials();
}
}
function logout() {
if (isset($_SESSION["auth_token"])) {
unset($_SESSION["auth_token"]);
}
if (isset($_SESSION["uid"])) {
$sql = "UPDATE tbl_store_admin SET php_sesskey=NULL WHERE username=?;";
$data = array($_SESSION["uid"]);
dbQuery($sql, $data);
unset($_SESSION["uid"]);
}
if (isset($_SESSION["guest"])) {
unset($_SESSION["guest"]);
}
}
function hasAuthenticated() {
if (hasValidToken()) {
if (hasValidSession()) {
return true;
} else {
logout();
return false;
}
} else {
logout();
return hasLoggedIn();
}
}
function regenerateSession() {
session_regenerate_id();
updateUserSession();
$token = "" . mt_rand(1,10000) . $_SESSION["uid"];
$_SESSION["auth_token"] = md5($token);
}
if (isset($_GET["action"]) && $_GET["action"]=="logout") {
logout();
}
if (!hasAuthenticated()) {
header('Location: login.php');
} else {
regenerateSession();
}
?>
Login.php: un semplice modulo di accesso
<?php
session_start();
function getRequestURL() {
if (isset($_SESSION['HTTP_REFERER'])) {
return $_SESSION['HTTP_REFERER'];
}
return "index.php?token=".$_SESSION["auth_token"];
}
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="X-Frame-Options" content="deny">
<title>Admin Login</title>
<style type="text/css">
.errorbox {
position:absolute;
top:0px;
left:0px;
width:100%;
height:100px;
background-color:#FFAAAA;
border:1px solid #FF0000;
}
.loginbox {
width:600px;
height:300px;
border:5px solid #000000;
margin:100px auto;
border-radius:10px;
-moz-border-radius:10px;
-webkit-border-radius:10px;
background-color:#CCCCCC;
box-shadow:4px 4px 2px #999999;
-moz-box-shadow:4px 4px 2px #999999;
-webkit-box-shadow:4px 4px 2px #999999;
}
.formbox {
width:400px;
height:200px;
margin:0px auto;
}
.label {
width:120px;
height:40px;
text-align:right;
line-height:40px;
float:left;
font-weight:bold;
}
.textinput {
height:40px;
width:260px;
font-size:30px;
line-height:40px;
}
.submitbtn {
height:50px;
width:100px;
font-size:30px;
line-height:40px;
}
</style>
</head>
<body style="font-family: Arial;font-size:20px;">
<?php
if (isset($_SESSION["login_errors"])) {
?>
<div class="errorbox">
Error: <?php echo ($_SESSION["login_errors"]); ?>
</div>
<?php
}
?>
<div class="loginbox">
<h1 align="center">Admin Area</h1>
<div class="formbox">
<form action="<?php echo getRequestURL(); ?>" method="POST">
<p><big>
<div class="label">Name:</div>
<input class="textinput" type="text" name="uid"/><br>
<div class="label">Password:</div>
<input class="textinput" type="password" name="key"/><br>
</big></p>
<p align="right"><input class="submitbtn" type="submit" value="Log In"/></p>
</form>
</div>
</div>
</body>
</html>