【问题标题】:How to prevent cross-domain ajax requests?如何防止跨域ajax请求?
【发布时间】:2011-06-14 17:58:11
【问题描述】:

如何检测我的 php 脚本是否从另一个域调用,而另一个域正在非法使用我的脚本?有没有办法防止这种情况?

更新

我在 SO 上找到了this 问题,但它仍然不安全,可以被欺骗。

【问题讨论】:

  • 使用访问令牌可能是您最好的选择。推荐人不可靠且容易伪造。
  • 哦,但我不知道如何实现访问令牌。您能否通过答案对此有所了解?
  • 设置服务的某种授权用户的cookie。在每次调用 ajax 处理脚本时检查此 cookie。或者将令牌嵌入到您的脚本中,以便在您自己的合法代码的每个 AJAX 调用中将其作为参数发送。基本上有一些东西与请求一起发送,以识别来自带宽窃贼之一的合法用户。如果他们变得聪明并复制了令牌,那么只能通过登录机制或其他一些每用户识别系统提供令牌。

标签: php security cross-domain


【解决方案1】:

没有任何绝对万无一失的方法可以防止这种情况发生,因为任何标头信息都可能被欺骗。基于会话的令牌是另一种可能的解决方案,但在这种情况下,您的 javascript 是可公开访问的,因此任何想花一点时间的人都可以确定您的令牌系统如何工作并找出解决方法。

多种方法的组合将为您提供最广泛的保护。您可以查找标头、使用和 .htaccess 文件以及使用令牌。上述所有方法都使得滥用 Web 服务器变得更加困难——大多数滥用来自于试图找到一个容易利用的漏洞的人。要记住的重要一点是,您不能因为部署了“最好的”保护而自满,或者因为您拥有似乎无法破解的多层保护。如果有人真的想要它足够糟糕并且有时间,他们会找到一种方法。这些类型的预防措施实际上只是阻止懒惰、好奇和无知恶意的人。有针对性的攻击是一个完全独立的安全类别,通常更集中于服务器级安全问题。

示例 htaccess。这不会是你放在根目录中的东西,而是放在一个子文件夹中,你的脚本永远不应该从地址栏中调用:

RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http(s)?://(www\.)?_YOUR_DOMAIN_NAME_HERE.com [NC]
RewriteRule \.(php)$ - [NC,F,L]

查看这篇文章了解有关使用令牌系统的信息:https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet

【讨论】:

    【解决方案2】:

    您可以手动拒绝 Origin 标头与您的域名不匹配的每个请求。但是,并非所有浏览器都会发送 Origin 标头。在这些情况下,您可以回退到 Referer[sic] 标头,对其进行解析并找出域名,然后按上述方式进行比较。

    一些 JavaScript 框架还为 AJAX 请求设置了 X-Requested-With 标头。

    这应该会拒绝很大一部分用户(我估计 >95%)。请注意,由于Same-Origin Policy,向您的域发送 AJAX 请求的人唯一得到的就是时间信息。

    【讨论】:

    • 检查更新。顺便说一句,在您的回答中,如果所有浏览器都不发送 Origin 标头,那么还能做什么?
    • `向你的域发送 AJAX 请求的人唯一得到的就是时间信息。`你这是什么意思?
    • @Coding Freak 更新了详细的解释和X-Requested-With。但是,我不知道为什么有人会发送随机 AJAX 请求。
    • 因为最近我在日志中检查了许多 XHR 请求来自外部服务器。
    • @Coding Freak 同源策略阻止您获取对另一个域的 AJAX 调用的结果,除非您明确选择加入跨域资源共享。因此,我无法想象有人向您的网站发送 AJAX 查询的动机。
    【解决方案3】:

    有点像 user3491125 提议的,你可以在调用的页面中设置一个 $_SESSION,如果有 $_SESSION['user'] 设置,你可以在 Ajax 调用的页面上检查它。

    【讨论】:

      【解决方案4】:

      对于 user3491125 的回答,您可以尝试加密会话令牌。我有一个加密功能,可以根据用户端口 80 IP 添加唯一密钥。这不是万无一失的,但它确实让黑客更难做到。

      function encryptString($string, $action, $baseIP = 'false', $extraKey = ''){
          global $flag;
      
          $encryptedIP = '';
      
          if($baseIP){
              $encryptedIP = encryptString(strip_tags(htmlentities($_SERVER['REMOTE_ADDR'])), 'encrypt', false);
          }
      
          $output = false;
      
          $encrypt_method = "AES-256-CBC";
          $secret_key = $flag['encrypt-key'].$encryptedIP.'-'.$extraKey;
          $secret_iv = $flag['encrypt-secret'].$encryptedIP.'-'.$extraKey;
      
          $key = hash('sha256', $secret_key);
          $iv = substr(hash('sha256', $secret_iv), 0, 16);
      
          $output;
      
          if($action == 'encrypt'){
              $output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
              $output = base64_encode($output);
              $output = str_replace('=', '[equal]', $output);
          }else if($action == 'decrypt'){
              $setString = str_replace('[equal]', '=', $string);
              $output = openssl_decrypt(base64_decode($setString), $encrypt_method, $key, 0, $iv);
          }
      
          return $output;
      }
      

      【讨论】:

        【解决方案5】:

        这不是一个可以解决的问题。如果您创建了一个网站,那么根据定义,您就可以将其公开。
        如果您希望您的数据是私密的,则需要某种登录方式。

        如果没有登录/烦人的验证码,就不可能创建一个对用户开放但不对脚本开放的系统。

        【讨论】:

          【解决方案6】:

          我知道这是一篇旧帖子,但目前有一种“万无一失”的方法可以避免这种情况,而且它非常简单...... 首先在将进行 ajax 调用的页面中:

          <?php
          $token = sha1(rand(1000,9999)); 
          $_SESSION['token'] = $token;
          ?>
          

          然后在ajax脚本中

          var formData = new FormData();
          formData.append("token","<?php echo $token;?>");
                  $.ajax({
                      url: 'yourfile.php',
                      type: 'POST',
                      xhr: function() {
                          var myXhr = $.ajaxSettings.xhr();
                          return myXhr; 
                      },
                      success:function(data) {
                          alert(data);
                      },
                      data: formData,
                      cache: false,
                      contentType: false,
                      processData: false
                  }); 
          

          最后在将被调用的 php 页面中:

          <?php
          $token = $_POST['token'];
          if($token === $_SESSION['token'] && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest')
          {
             //Perform your stuff here...
          }
          ?>    
          

          【讨论】:

          • 这里的问题是 rand() 并不是真正随机的。这是一个基于时间的伪随机数。虽然这仍然是首选方法,但绝不是“万无一失”。
          • 但它没有任何影响,因为字符串存储在会话中,因此仅与这台计算机相关联;)感谢您告知
          • 我认为session spoofing 上的答案在某些方面对此有所帮助,但不确定这是否是 OP 正在寻找的理想解决方案。
          • 这也不是万无一失的,因为令牌暴露在脚本中。如果我的目标是对此进行攻击,我可以使用 curl 加载页面,解析出令牌,然后使用好的令牌发送恶意请求。 curl_setopt($ch, CURLOPT_HTTPHEADER, array("X-Requested-With: XMLHttpRequest")); 击败了其他措施,并且 curl 不关心同源策略。正如我所说,这里没有真正的灵丹妙药。
          猜你喜欢
          • 1970-01-01
          • 2019-11-04
          • 1970-01-01
          • 2015-06-08
          • 2019-04-17
          • 2012-04-18
          • 1970-01-01
          • 2013-03-06
          • 1970-01-01
          相关资源
          最近更新 更多