验证HTTP Referer字段

2023-10-08 22:58
文章标签 http 验证 字段 referer

本文主要是介绍验证HTTP Referer字段,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

CSRF(Cross-site request forgery跨站请求伪造,也被称成为“one click attack”或者session riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。

1 CSRF攻击原理

CSRF攻击原理比较简单,如图1所示。其中Web A为存在CSRF漏洞的网站,Web B为攻击者构建的恶意网站,User C为Web A网站的合法用户。

 

1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;

2.在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;

3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;

4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;

5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。

2 CSRF漏洞防御

CSRF漏洞防御主要可以从三个层面进行,即服务端的防御、用户端的防御和安全设备的防御。

2.1      服务端的防御

2.1.1  验证HTTP Referer字段

根据HTTP协议,在HTTP头中有一个字段叫Referer,它记录了该HTTP请求的来源地址。在通常情况下,访问一个安全受限页面的请求必须来自于同一个网站。比如某银行的转账是通过用户访问http://bank.test/test?page=10&userID=101&money=10000页面完成,用户必须先登录bank.test,然后通过点击页面上的按钮来触发转账事件。当用户提交请求时,该转账请求的Referer值就会是转账按钮所在页面的URL(本例中,通常是以bank. test域名开头的地址)。而如果攻击者要对银行网站实施CSRF攻击,他只能在自己的网站构造请求,当用户通过攻击者的网站发送请求到银行时,该请求的Referer是指向攻击者的网站。因此,要防御CSRF攻击,银行网站只需要对于每一个转账请求验证其Referer值,如果是以bank. test开头的域名,则说明该请求是来自银行网站自己的请求,是合法的。如果Referer是其他网站的话,就有可能是CSRF攻击,则拒绝该请求。

2.1.2 在请求地址中添加token并验证

CSRF攻击之所以能够成功,是因为攻击者可以伪造用户的请求,该请求中所有的用户验证信息都存在于Cookie中,因此攻击者可以在不知道这些验证信息的情况下直接利用用户自己的Cookie来通过安全验证。由此可知,抵御CSRF攻击的关键在于:在请求中放入攻击者所不能伪造的信息,并且该信息不存在于Cookie之中。鉴于此,系统开发者可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务器端建立一个拦截器来验证这个token,如果请求中没有token或者token内容不正确,则认为可能是CSRF攻击而拒绝该请求。

2.1.3 在HTTP头中自定义属性并验证

自定义属性的方法也是使用token并进行验证,和前一种方法不同的是,这里并不是把token以参数的形式置于HTTP请求之中,而是把它放到HTTP头中自定义的属性里。通过XMLHttpRequest这个类,可以一次性给所有该类请求加上csrftoken这个HTTP头属性,并把token值放入其中。这样解决了前一种方法在请求中加入token的不便,同时,通过这个类请求的地址不会被记录到浏览器的地址栏,也不用担心token会通过Referer泄露到其他网站。

3.3      其他防御方法

1.  CSRF攻击是有条件的,当用户访问恶意链接时,认证的cookie仍然有效,所以当用户关闭页面时要及时清除认证cookie,对支持TAB模式(新标签打开网页)的浏览器尤为重要。

2.  尽量少用或不要用request()类变量,获取参数指定request.form()还是request. querystring (),这样有利于阻止CSRF漏洞攻击,此方法只不能完全防御CSRF攻击,只是一定程度上增加了攻击的难度。

代码示例:

Java 代码示例

下文将以 Java 为例,对上述三种方法分别用代码进行示例。无论使用何种方法,在服务器端的拦截器必不可少,它将负责检查到来的请求是否符合要求,然后视结果而决定是否继续请求或者丢弃。在 Java 中,拦截器是由 Filter 来实现的。我们可以编写一个 Filter,并在 web.xml 中对其进行配置,使其对于访问所有需要 CSRF 保护的资源的请求进行拦截。

在 filter 中对请求的 Referer 验证代码如下

清单 1. 在 Filter 中验证 Referer

 // 从 HTTP 头中取得 Referer 值

 String referer=request.getHeader("Referer");

 // 判断 Referer 是否以 bank.example 开头

 if((referer!=null) &&(referer.trim().startsWith(“bank.example”))){

    chain.doFilter(request, response);

 }else{

request.getRequestDispatcher(“error.jsp”).forward(request,response);

 } 

以上代码先取得 Referer 值,然后进行判断,当其非空并以 bank.example 开头时,则继续请求,否则的话可能是 CSRF 攻击,转到 error.jsp 页面。

如果要进一步验证请求中的 token 值,代码如下

清单 2. 在 filter 中验证请求中的 token

HttpServletRequest req = (HttpServletRequest)request;

 

 HttpSession s = req.getSession();

 

 // 从 session 中得到 csrftoken 属性

 

 String sToken = (String)s.getAttribute(“csrftoken”);

 

 if(sToken == null){

 

    // 产生新的 token 放入 session 中

 

    sToken = generateToken();

 

    s.setAttribute(“csrftoken”,sToken);

 

    chain.doFilter(request, response);

 

 } else{

 

    // 从 HTTP 头中取得 csrftoken

 

    String xhrToken = req.getHeader(“csrftoken”);

 

    // 从请求参数中取得 csrftoken

 

    String pToken = req.getParameter(“csrftoken”);

 

    if(sToken != null && xhrToken != null && sToken.equals(xhrToken)){

        chain.doFilter(request, response);

    }else if(sToken != null && pToken != null && sToken.equals(pToken)){

        chain.doFilter(request, response);

    }else{

request.getRequestDispatcher(“error.jsp”).forward(request,response);

    }

 

 } 

首先判断 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<forms.length; 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<len; 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")%>';

 

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

 

   

 

    // 把 csrftoken 属性放到头中

 

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

 

    return plainXhr(method,args,hasBody);

 

 }; 

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

PHP代码示例:

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

<form action="buy.php" method="POST">

 

  <p>

 

  Item:

 

  <select name="item">

 

    <option name="pen">pen</option>

 

    <option name="pencil">pencil</option>

 

  </select><br />

 

  Quantity: <input type="text" name="quantity" /><br />

 

  <input type="submit" value="Buy" />

 

  </p>

 

</form> 

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

<?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 '<p>Thanks for your purchase.</p>';

 

    }

 

    else

 

    {

 

      echo '<p>There was a problem with your order.</p>';

 

    }

 

  }

 

?> 

攻击者会首先使用这个表单来观察它的动作。例如,在购买了一支铅笔后,攻击者知道了在购买成功后会出现感谢信息。注意到这一点后,攻击者会尝试通过访问下面的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();

 

?> 

表单:

<form action="buy.php" method="POST">

 

  <input type="hidden" name="token" value="<?php echo $token; ?>" />

 

  <p>

 

  Item:

 

  <select name="item">

 

    <option name="pen">pen</option>

 

    <option name="pencil">pencil</option>

 

  </select><br />

 

  Quantity: <input type="text" name="quantity" /><br />

 

  <input type="submit" value="Buy" />

 

  </p>

 

</form> 

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

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

<?php

 

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

 

  {

 

    /* Valid Token */

 

  }

 

?> 

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

 <?php

 

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

 

  if ($token_age <= 300)

 

  {

 

    /* Less than five minutes has passed. */

 

  }

 

?> 

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

这篇关于验证HTTP Referer字段的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/168566

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

BUUCTF靶场[web][极客大挑战 2019]Http、[HCTF 2018]admin

目录   [web][极客大挑战 2019]Http 考点:Referer协议、UA协议、X-Forwarded-For协议 [web][HCTF 2018]admin 考点:弱密码字典爆破 四种方法:   [web][极客大挑战 2019]Http 考点:Referer协议、UA协议、X-Forwarded-For协议 访问环境 老规矩,我们先查看源代码

【Linux】应用层http协议

一、HTTP协议 1.1 简要介绍一下HTTP        我们在网络的应用层中可以自己定义协议,但是,已经有大佬定义了一些现成的,非常好用的应用层协议,供我们直接使用,HTTP(超文本传输协议)就是其中之一。        在互联网世界中,HTTP(超文本传输协议)是一个至关重要的协议,他定义了客户端(如浏览器)与服务器之间如何进行通信,以交换或者传输超文本(比如HTML文档)。

C++ | Leetcode C++题解之第393题UTF-8编码验证

题目: 题解: class Solution {public:static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num &

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目: 题解: static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num & MASK1) == 0) {return

如何确定 Go 语言中 HTTP 连接池的最佳参数?

确定 Go 语言中 HTTP 连接池的最佳参数可以通过以下几种方式: 一、分析应用场景和需求 并发请求量: 确定应用程序在特定时间段内可能同时发起的 HTTP 请求数量。如果并发请求量很高,需要设置较大的连接池参数以满足需求。例如,对于一个高并发的 Web 服务,可能同时有数百个请求在处理,此时需要较大的连接池大小。可以通过压力测试工具模拟高并发场景,观察系统在不同并发请求下的性能表现,从而

easyui同时验证账户格式和ajax是否存在

accountName: {validator: function (value, param) {if (!/^[a-zA-Z][a-zA-Z0-9_]{3,15}$/i.test(value)) {$.fn.validatebox.defaults.rules.accountName.message = '账户名称不合法(字母开头,允许4-16字节,允许字母数字下划线)';return fal

easyui 验证下拉菜单select

validatebox.js中添加以下方法: selectRequired: {validator: function (value) {if (value == "" || value.indexOf('请选择') >= 0 || value.indexOf('全部') >= 0) {return false;}else {return true;}},message: '该下拉框为必选项'}

Anaconda 中遇到CondaHTTPError: HTTP 404 NOT FOUND for url的问题及解决办法

最近在跑一个开源项目遇到了以下问题,查了很多资料都大(抄)同(来)小(抄)异(去)的,解决不了根本问题,费了很大的劲终于得以解决,记录如下: 1、问题及过程: (myenv) D:\Workspace\python\XXXXX>conda install python=3.6.13 Solving environment: done.....Proceed ([y]/n)? yDownloa

web群集--nginx配置文件location匹配符的优先级顺序详解及验证

文章目录 前言优先级顺序优先级顺序(详解)1. 精确匹配(Exact Match)2. 正则表达式匹配(Regex Match)3. 前缀匹配(Prefix Match) 匹配规则的综合应用验证优先级 前言 location的作用 在 NGINX 中,location 指令用于定义如何处理特定的请求 URI。由于网站往往需要不同的处理方式来适应各种请求,NGINX 提供了多种匹