这是对我之前的问题的回应,这个管理区域是否足够安全?.
那里有一些非常有用的答案,对此我非常感激。所以我回到绘图板上,这是我的新问题,因为我对事物的安全方面不熟悉,我不确定我是否正确理解或覆盖了所有的漏洞。
我试图保护系统免受的问题是:
笔记
- 密码与bcrypt 的这种实现一起存储
- 在整个网站会使用https://协议
- 标志session.cookie_secure 和 session.cookie_httponly将被设置
- PHP 的PDO 对象用于所有 SQL 查询,所有输入都是参数化的
- 任何用户输入变量都将被清理
- 每个页面都以
x-frame-options: denyhttp 标头开头 - 每个受限页面都以授权脚本开头(见图 1)
- 只有当设置的变量
$_SESSION["auth_key"]与表单发送的密钥匹配时,所有表单才会被授权(见图 1)
图 1:授权和登录脚本

我希望这个图表主要是不言自明的。但是,我将尝试澄清某些功能。
- 检查令牌:用户登录后生成的令牌,存储在
$_SESSION['auth_token']用户提交的任何表单中并随其一起发送。 - 检查会话:如果用户拥有有效的令牌,则使用 PHPSESSID 从数据库中提取他们的详细信息(欢迎用户返回时需要该名称)。如果没有找到会话 id 或令牌无效,则他们的会话已过期,需要重新登录。
- 再生。session:一旦用户成功登录,给他们一个新的 session id (
session_regenerate_id()) 并使用新的 session id 更新数据库。设置$_SESSION['auth_token'] = md5(mt_rand(1,10000).$username);. 用户名是唯一的,因此这应该生成一个唯一的身份验证令牌,以便他们可以提交自己的表单。我不确定我是否正确地实现了这一点 - 请参阅此处的示例 - 检查登录:只需检查 bcrypted 密码是否与 bcrypted 输入匹配,并且用户名是否匹配。
- 失败尝试:添加这一面是为了减缓蛮力攻击;首先添加验证码,然后暂时阻止 IP 并向用户/帐户持有人发送通知电子邮件。
- 如有必要,我将添加更多细节/如果有任何错误,请更正这些细节
实施(到目前为止):
受限页面:以要求开头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:login.php如果未通过身份验证,则重定向到
注意:我忽略了加密(在测试时),因为bcrypt在我的 PHP 版本(5.2.17)中不可用,所以我正在寻找 SHA256 或 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:一个简单的登录表单
<?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>