一、会话控制简介
在前面学习了HTTP协议,知道了HTTP是无状态无连接的协议,为了解决无状态这个问题,通过在HTTP头部字段中的Cookie字段来标识当前连接用户标识。一个网站鉴别用户信息是通过Cookie或Session进行鉴别。
Cookie分为2种:
内存cookie:也叫做非持久Cookie,是保存在用户浏览器中,一旦浏览器关闭,内存中的cookie也会被丢失,但并未被销毁。
硬盘cookie:也就做持久Cookie,是保存在用户硬盘中的,有过期时间,浏览器再次打开,通过读取硬盘中的Cookie信息保持持久化。
Session:
因为Cookie是保存在客户端中,为了鉴别用户,需要把用户相关信息如用户密码信息写入到Cookie中给客户端浏览器保存,这样如果加密不安全,则导致安全问题(篡改、拆解),而Session则有效解决了这个问题。Session信息是保存在服务器上,一个用户信息对应一个Session_ID,服务器只需要把Session_ID发送给客户端浏览器,服务器校检用户信息通过Session_ID找到对应的用户密码信息进行校检,有效提高了安全性。
二、PHP中Cookie操作
在PHP中,对Cookie的操作是用setcookie()函数,获取Cookie信息是用$_COOKIE超全局变量。
bool setcookie ( string $name [, string $value = "" [, int $expire = 0 [, string $path = "" [, string $domain = "" [, bool $secure = false [, bool $httponly = false ]]]]]] )
参数解释:
name:Cookie 的名称
value:Cookie的值
expire:Cookie的过期时间,采用时间戳表示
path:Cookie的有效服务器路径,如果设置了"/"为根路径,则表示对当前所有路径生效
domain:Cookie的有效域名,为了安全,一般设置了当前二级域名有效。
secure:如果设置为True,则Cookie信息只能通过HTTPS协议进行传递。
httponly:防止XSS攻击,设置为True,使JS脚本不能读取设置。
演示例子
<?php
//设置Cookie
$user='admin';
$pwd='123456';
setcookie('user',$user,time()+3600,"/","",False,True);
setcookie('pwd',md5($pwd.'1q2w3e'),time()+3600,"/","",False,True);
//读取Cookie
echo $_COOKIE['user'].'<br />';
echo $_COOKIE['pwd'];
echo '<hr />';
//修改COOKIE
$user='root';
setcookie('user',$user,time()+3600,"/","",False,True);
//删除COOKIE:
//1、可以通过设置过期时间使客户端浏览器COOKIE过期
setcookie('user',$user,time()-2,"/","",False,True);
setcookie('pwd',md5($pwd.'1q2w3e'),time()-2,"/","",False,True);
//2、设置Cookie为空,会告知客户端浏览器删除Cookie
setcookie('user','');
//但以上两种,如果攻击者通过抓包工具提交COOKIE,不借助浏览器,Cookie将不会失效
?>
服务器通过Set-Cookie字段,将Cookie信息发送给客户端,以后客户端请求该信息会将此COOKIE附带提交。
三、PHP中Session操作
1、session_start()
会创建新会话或者重用现有会话。 如果通过 GET 或者 POST 方式,或者使用 cookie 提交了会话 ID, 则会重用现有会话。
<?php
session_start();
?>
第一次访问,服务器会返回一个Session_ID给客户端
当服务器在次访问并附带此session_ID进行提交后,服务器校检此ID有效(存在),则不在返回新的ID。
2、Session操作
<?php
session_start();
if($_GET['pwd']=='admin'){
$_SESSION['admin']='admin';
$_SESSION['pwd']=md5('123456'.'1q2w3e');
}
//http://127.0.0.1/?pwd=admin
print_r($_SESSION); //Array ( [admin] => admin [pwd] => cb0d164dcffd1605ee9754ae48988164 )
//销毁session会话
session_destroy();
$_SESSION='';
print_r($_SESSION);
?>
四、实例操作
登陆表单-index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>登陆</title>
</head>
<body>
<form action="./login.php" method="post">
用户名:<input type="text" name="user" id=""><br />
密码:<input type="password" name="pwd" id=""><br />
验证码:<input type="text" name="code" id=""><img src="code.php" onclick="this.src='code.php?'+new Date().getTime();" width="100" height="35">
<input type="submit" name='submit' value="登陆">
</form>
</body>
</html>
验证码-code.php
<?php
session_start();
//1.创建黑色画布
$image = imagecreatetruecolor(100, 30);
//2.为画布定义(背景)颜色
$bgcolor = imagecolorallocate($image, 255, 255, 255);
//3.填充颜色
imagefill($image, 0, 0, $bgcolor);
// 4.设置验证码内容
//4.1 定义验证码的内容
$content = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
//4.1 创建一个变量存储产生的验证码数据,便于用户提交核对
$captcha = "";
for ($i = 0; $i < 4; $i++) {
// 字体大小
$fontsize = 10;
// 字体颜色
$fontcolor = imagecolorallocate($image, mt_rand(0, 120), mt_rand(0, 120), mt_rand(0, 120));
// 设置字体内容
$fontcontent = substr($content, mt_rand(0, strlen($content)), 1);
$captcha .= $fontcontent;
// 显示的坐标
$x = ($i * 100 / 4) + mt_rand(5, 10);
$y = mt_rand(5, 10);
// 填充内容到画布中
imagestring($image, $fontsize, $x, $y, $fontcontent, $fontcolor);
}
$_SESSION["code"] = $captcha;
//4.3 设置背景干扰元素
for ($$i = 0; $i < 200; $i++) {
$pointcolor = imagecolorallocate($image, mt_rand(50, 200), mt_rand(50, 200), mt_rand(50, 200));
imagesetpixel($image, mt_rand(1, 99), mt_rand(1, 29), $pointcolor);
}
//4.4 设置干扰线
for ($i = 0; $i < 3; $i++) {
$linecolor = imagecolorallocate($image, mt_rand(50, 200), mt_rand(50, 200), mt_rand(50, 200));
imageline($image, mt_rand(1, 99), mt_rand(1, 29), mt_rand(1, 99), mt_rand(1, 29), $linecolor);
}
//5.向浏览器输出图片头信息
header('content-type:image/png');
//6.输出图片到浏览器
imagepng($image);
//7.销毁图片
imagedestroy($image);
登陆验证-login.php
<?php
header('content-type:text/html;charset=utf-8');
session_start();
if(isset($_POST['submit'])){
$user=$_POST['user'];
$pwd=$_POST['pwd'];
$code= strtolower($_POST['code']);
$code1= strtolower($_SESSION['code']);
if(strcmp($code,$code1)!=0){
echo '<script>alert("验证码错误");location.href="index.html"</script>';
}else if($user=='admin' && $pwd=='123456'){
$_SESSION['user']=$user;
$_SESSION['key']=sha1($pwd);
echo '<script>alert("登陆成功");location.href="home.php"</script>';
}else{
echo '<script>alert("用户名或密码错误");location.href="index.html"</script>';
}
}
用户首页-home.php
<?php
header('content-type:text/html;charset=utf-8');
session_start();
if($_SESSION['user']=='admin' && $_SESSION['key']==sha1('123456')){
echo '欢迎'.$_SESSION['user'];
echo '<a href="loginout.php">注销</a>';
}else{
echo '<script>location.href="index.html"</script>';
}
用户注销-loginout.php
<?php
header('content-type:text/html;charset=utf-8');
session_start();
session_destroy();
$_SESSION='';
echo '<script>alert("注销成功");location.href="index.html"</script>';
验证码重放攻击
在login.php页面中,对验证码错误或用户密码错误情况,没有做验证码注销操作,导致可利用同一验证码进行多次验证,这也是大多数开发者忽略的问题。
漏洞修复:
对登陆失败的情况下,注销当前session,如果检测到了无此sessionID就表示遇到了重放攻击
<?php
header('content-type:text/html;charset=utf-8');
session_start();
if(isset($_POST['submit'])){
$user=$_POST['user'];
$pwd=$_POST['pwd'];
$code= strtolower($_POST['code']);
if(empty($_SESSION['code'])){
echo '<script>alert("检测到验证码重放攻击");location.href="index.html"</script>';
exit;
}
$code1= strtolower($_SESSION['code']);
if(strcmp($code,$code1)!=0){
session_destroy();
echo '<script>alert("验证码错误");location.href="index.html"</script>';
}else if($user=='admin' && $pwd=='123456'){
$_SESSION['user']=$user;
$_SESSION['key']=sha1($pwd);
echo '<script>alert("登陆成功");location.href="home.php"</script>';
}else{
session_destroy();
echo '<script>alert("用户名或密码错误");location.href="index.html"</script>';
}
}
固定会话攻击
虽然对登陆失败情况下进行了注销session,但是没有对登陆成功进行注销。再就是登陆前和登陆后采用的session是同一个,容易导致会话固定攻击。
漏洞修复:
登陆成功后,使用新的sessionID替换旧的sessionID。
session_regenerate_id():使用新生成的会话 ID 更新现有会话 ID
可看到,登陆成功后返回了新的sessionID标识
因为登陆成功后,已删除原sessionID,所以重放登陆也会被检测到。