当前位置:主页>科 研>学术交流>

跨站请求伪造CSRF防护方法(2)

  首先判断 session 中有没有 csrftoken,如果没有,则认为是第一次访问,session 是新建立的,这时生成一个新的 token,放于 session 之中,并继续执行请求。如果 session 中已经有 csrftoken,则说明用户已经与服务器之间建立了一个活跃的 session,这时要看这个请求中有没有同时附带这个 token,由于请求可能来自于常规的访问或是 XMLHttpRequest 异步访问,我们分别尝试从请求中获取 csrftoken 参数以及从 HTTP 头中获取 csrftoken 自定义属性并与 session 中的值进行比较,只要有一个地方带有有效 token,就判定请求合法,可以继续执行,否则就转到错误页面。生成 token 有很多种方法,任何的随机算法都可以使用,Java 的 UUID 类也是一个不错的选择。

  除了在服务器端利用 filter 来验证 token 的值以外,我们还需要在客户端给每个请求附加上这个 token,这是利用 js 来给 html 中的链接和表单请求地址附加 csrftoken 代码,其中已定义 token 为全局变量,其值可以从 session 中得到。

  清单 3. 在客户端对于请求附加 token

  function appendToken(){

  updateForms();

  updateTags();

  }

  function updateForms(){

  // 得到页面中所有的 form 元素

  var forms =document.getElementsByTagName('form');

  for(i=0;i

  var url =forms[i].action;

  // 如果这个 form 的 action 值为空,则不附加 csrftoken

  if(url ==null||url =="")continue;

  // 动态生成 input 元素,加入到 form 之后

  var e =document.createElement("input");

  e.name ="csrftoken";

  e.value =token;

  e.type="hidden";

  forms[i].appendChild(e);

  }

  }

  function updateTags(){

  var all =document.getElementsByTagName('a');

  var len =all.length;

  // 遍历所有 a 元素

  for(var i=0;i

  var e =all[i];

  updateTag(e,'href',token);

  }

  }

  function updateTag(element,attr,token){

  var location =element.getAttribute(attr);

  if(location !=null&&location !=''''){

  var fragmentIndex =location.indexOf('#');

  var fragment =null;

  if(fragmentIndex !=-1){

  //url 中含有只相当页的锚标记

  fragment =location.substring(fragmentIndex);

  location =location.substring(0,fragmentIndex);

  }

  var index =location.indexOf('?');

  if(index !=-1){

  //url 中已含有其他参数

  location =location +'&csrftoken='+token;

  }else{

  //url 中没有其他参数

  location =location +'?csrftoken='+token;

  }

  if(fragment !=null){

  location +=fragment;

  }

  element.setAttribute(attr,location);

  }

  }

  在客户端 html 中,主要是有两个地方需要加上 token,一个是表单 form,另一个就是链接 a。这段代码首先遍历所有的 form,在 form 最后添加一隐藏字段,把 csrftoken 放入其中。然后,代码遍历所有的链接标记 a,在其 href 属性中加入 csrftoken 参数。注意对于 a.href 来说,可能该属性已经有参数,或者有锚标记。因此需要分情况讨论,以不同的格式把 csrftoken 加入其中。

  如果你的网站使用 XMLHttpRequest,那么还需要在 HTTP 头中自定义 csrftoken 属性,利用 dojo.xhr 给 XMLHttpRequest 加上自定义属性代码如下:

  清单 4. 在 HTTP 头中自定义属性

  var plainXhr =dojo.xhr;// 重写 dojo.xhr 方法

  dojo.xhr =function(method,args,hasBody){

  // 确保 header 对象存在

  args.headers =args.header ||{};

  tokenValue ='<%=request.getsession(false).getattribute("csrftoken")%>';

<%=request.getsession(false).getattribute("csrftoken")%>

  var token =dojo.getObject("tokenValue");

  // 把 csrftoken 属性放到头中

  args.headers["csrftoken"]=(token)?token :"";

  returnplainXhr(method,args,hasBody);

  };

  这里改写了 dojo.xhr 的方法,首先确保 dojo.xhr 中存在 HTTP 头,然后在 args.headers 中添加 csrftoken 字段,并把 token 值从 session 里拿出放入字段中。

  PHP代码示例:

  请看下面一个简单的应用,它允许用户购买钢笔或铅笔。界面上包含下面的表单:

  Item:

  pen

  pencil

  Quantity:

  下面的buy.php程序处理表单的提交信息:

  session_start();

  $clean =array();

  if(isset($_REQUEST['item']&&isset($_REQUEST['quantity']))

  {

  /* Filter Input ($_REQUEST['item'], $_REQUEST['quantity']) */

  if(buy_item($clean['item'],$clean['quantity']))

  {

  echo '<  session_start();

  $clean =array();

  if(isset($_REQUEST['item']&&isset($_REQUEST['quantity']))

  {

  /* Filter Input ($_REQUEST['item'], $_REQUEST['quantity']) */

  if(buy_item($clean['item'],$clean['quantity']))

  {

  echo '<  session_start();

  $clean =array();

  if(isset($_REQUEST['item']&&isset($_REQUEST['quantity']))

  {

  /* Filter Input ($_REQUEST['item'], $_REQUEST['quantity']) */

  if(buy_item($clean['item'],$clean['quantity']))

  {

  echo '

  Thanks for your purchase.

  ';

  }

  else

  {

  echo '

  There was a problem with your order.

  ';

  }

  }?>

  攻击者会首先使用这个表单来观察它的动作。例如,在购买了一支铅笔后,攻击者知道了在购买成功后会出现感谢信息。注意到这一点后,攻击者会尝试通过访问下面的URL以用GET方式提交数据是否能达到同样的目的:

  http://store.example.org/buy.php?item=pen&quantity=1

  如果能成功的话,攻击者现在就取得了当合法用户访问时,可以引发购买的URL格式。在这种情况下,进行跨站请求伪造攻击非常容易,因为攻击者只要引发受害者访问该URL即可。

  请看下面对前例应用更改后的代码:

  php

  session_start();

  $token =md5(uniqid(rand(),TRUE));

  $_SESSION['token']=$token;

  $_SESSION['token_time']=time();?>

  表单:

  Item:

  pen

  pencil

  Quantity:

  通过这些简单的修改,一个跨站请求伪造攻击就必须包括一个合法的验证码以完全模仿表单提交。由于验证码的保存在用户的session中的,攻击者必须对每个受害者使用不同的验证码。这样就有效的限制了对一个用户的任何攻击,它要求攻击者获取另外一个用户的合法验证码。使用你自己的验证码来伪造另外一个用户的请求是无效的。

  该验证码可以简单地通过一个条件表达式来进行检查:

  if(isset($_SESSION['token'])&&$_POST['token']==$_SESSION['token'])

  {

  /* Valid Token */

  }?>  if(isset($_SESSION['token'])&&$_POST['token']==$_SESSION['token'])

  {

  /* Valid Token */

  }?>  if(isset($_SESSION['token'])&&$_POST['token']==$_SESSION['token'])

  {

  /* Valid Token */

  }?>

  你还能对验证码加上一个有效时间限制,如5分钟:

  $token_age =time()-$_SESSION['token_time'];

  if($token_age <  $token_age =time()-$_SESSION['token_time'];

  if($token_age <  $token_age =time()-$_SESSION['token_time'];

  if($token_age <=300)

  {

  /* Less than five minutes has passed. */

  }?>

  通过在你的表单中包括验证码,你事实上已经消除了跨站请求伪造攻击的风险。可以在任何需要执行操作的任何表单中使用这个流程。

(责任编辑:安博涛)

分享到:

更多
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
  • 微笑/wx
  • 撇嘴/pz
  • 抓狂/zk
  • 流汗/lh
  • 大兵/db
  • 奋斗/fd
  • 疑问/yw
  • 晕/y
  • 偷笑/wx
  • 可爱/ka
  • 傲慢/am
  • 惊恐/jk
用户名: 验证码:点击我更换图片
资料下载专区
图文资讯

容器是如何让“一切都是代码”成为现实的

容器是如何让“一切都是代码”成为现实的

现代应用的发展在很大程度上要归功于DevOps运动的蓬勃兴起以及该运动所产生的各种自动...[详细]

如何快速掌握一门新技术/语言/框架

如何快速掌握一门新技术/语言/框架

IT行业中的企业特点是都属于知识密集型企业。这种企业的核心竞争力与员工的知识和技能...[详细]

建高效数据中心有径可循

建高效数据中心有径可循

能耗问题一直是各大数据中心的心头之痛。有数据表明,2015年我国数据中心能耗预计将高...[详细]

2015黑帽大会:网络灾难后 重建IT安全

2015黑帽大会:网络灾难后 重建IT安全

在遭遇网络灾难后重建IT安全似乎是不可能完成的任务,但根据安全专家Christina Kubeck...[详细]

面对DNS劫持 企业移动应用该如何防护?

面对DNS劫持 企业移动应用该如何防护?

DNS(Domain Name System)劫持又称域名劫持,是指对正常的域名解析请求加以拦截,转而...[详细]

返回首页 返回顶部