老生常谈-跨域问题

1,040 阅读11分钟

👉 跨域专栏,只介绍比较主流的解决方案 github地址,附带demo

👉 什么是跨域

跨域

当前发起请求的域与该请求指向的资源所在的域不一样

http 默认端口https 默认端口
80443

example:发起请求的域为 https://www.qixin.com

请求资源所在域:

  • https://www.qixin.com/user/claimed (同域)
  • https://www.qixin.com:443/user/claimed (同域)
  • 端口不同:https://www.qixin.com:8000/user/claimed (跨域)
  • 协议不同:http://www.qixin.com/user/claimed (跨域)
  • 域名换对应ip:https://119.45.202.168/user/claimed (跨域)
  • 子域不同:https://x.qixin.com/user/claimed (跨域)
  • 域名不同:https://www.qixin001.com/user/claimed (跨域)

特殊的场景:

  • http://127.0.0.1:8000/user/claimedhttp://localhost:8000/user/claimed (跨域)
  • http://localhost:80/user/claimedhttp://localhost/user/claimed (同域)
  • https://localhost:443/user/claimedhttps://localhost/user/claimed (同域)

👉 为什么会跨域这个概念

浏览器的同源策略:同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互,这是一个用于隔离潜在恶意文件的重要安全机制。

同源策略的目的:保证用户信息的安全,防止恶意的网站窃取数据。

如何才算同源:如果两个 URL 的 protocol、port 和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。


👉 不采用同源策略会带来什么危害

  • 跨站脚本攻击(XSS/Cross Site Scripting)

    最早迫使浏览器采用不信任互联网应用的设计思想

    是一种允许攻击者在另一个用户的浏览器中执行恶意脚本的脚本注入式攻击(类似于sql注入)

    分类:

    • 非持久型 XSS 攻击
      • 反射型 XSS
        • 定义:攻击者诱导用户访问一个带有恶意代码的 URL 后,服务器端接收数据后处理,然后把带有恶意代码的数据发送到浏览器端,浏览器端解析这段带有 XSS 代码的数据后当做脚本执行,最终完成 XSS 攻击。
        • 步骤:
          1. 攻击构造出特殊的 URL ,其中包含恶意代码。
          2. 用户被诱导打开带有恶意代码的 URL,服务器端将恶意代码从 URL 中取出当做参数处理,然后返回给用户带有恶意代码的数据。
          3. 用户浏览器接收到响应解析执行,混在其中的恶意代码也被执行。
          4. 恶意代码窃取用户敏感数据发送给攻击者,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
        • 举例:电影搜索
          1. 打开首页,输入搜索内容《我的姐姐》
          2. 如果没有搜索结果,后端如果没有进行处理,会返回 没有《我的姐姐》相关影片
          3. 然后前端将 没有《我的姐姐》相关影片 直接在页面上进行展示
          4. 如果攻击者将搜索内容换成 <script>alert("xss")</script>
          5. 前端直接弹出窗口 xss
      • DOM 型 XSS
        • 定义:通过修改页面的 DOM 节点形成的 XSS,取出和执行恶意代码都由浏览器端完成,属于前端自身的安全漏洞。
        • 步骤:
          1. 攻击者构造出特殊的 URL,其中包含恶意代码。
          2. 用户被诱导打开带有恶意代码的 URL。
          3. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
          4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
        • 举例:通过快递编号查询物流信息
          1. 用户通过访问 https://www.ceshi.com/index.html?number=YT40359134268305获取该编号的具体数据。
          2. 构造网址:https://www.ceshi.com/index.html?number=<script>alert("xss")</script>
          3. 由于前端工程师直接从 URL 中取值展示导致的XSS攻击问题。
    • 持久型 XSS 攻击
      • 存储型 XSS
        • 定义:存储型 XSS 的恶意代码存在服务器上,反射型 XSS 的恶意代码存在 URL 里。存储型 XSS 攻击时恶意脚本会存储在目标服务器上。当浏览器请求数据时,脚本从服务器传回并执行。它是最危险的一种跨站脚本,比反射性 XSS 和 DOM 型 XSS 都更有隐蔽性,因为它不需要用户手动触发。任何允许用户存储数据的 Web 程序都可能存在存储型 XSS 漏洞。若某个页面遭受存储型 XSS 攻击,所有访问该页面的用户都会被 XSS 攻击。
        • 步骤:
          1. 攻击者把恶意代码提交到目标网站的服务器中。
          2. 目标网站服务器没有做任何校验保存了该恶意代码。
          3. 用户一旦打开目标网站,网站服务器端把带有恶意代码的数据取出,当做正常数据返回给用户。
          4. 用户浏览器接收到响应解析执行,混在其中的恶意代码也被执行。
          5. 恶意代码窃取用户敏感数据发送给攻击者,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
        • 举例:一个博客下面的评论
          1. 攻击者在一个文章下发布一条评论 <script>alert('xss')</script>
          2. 任何用户进入到该文章下都会加载评论列表。
          3. 由于恶意代码被浏览器执行,直接弹出 xss。
          4. 当然也可以是执行一段js脚本,或者窃取用户 cookie 等。

    解决方法: 转义、过滤、内容安全策略(CSP)[ 外部资源白名单 ]等等

  • 跨站请求伪造(CSRF/XSRF/Cross-site request forgery)

    攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

    攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账等等。造成的问题包括:个人隐私泄露以及财产安全。

    CSRF

    流程:

    1. 受害者登录网站A,拿到了登录凭证 Cookie。
    2. 攻击者引诱用户访问钓鱼网站B。
    3. 网站B向网站A发送了一个请求,默认携带了网站A的 Cookie。
    4. 网站A拿到用户的Cookie,以为用受害者自己发送的请求。
    5. 网站A以受害者的名义执行了一系列的操作(如果涉及到虚拟货币的话)。
    6. 攻击完成,在受害者毫不知情的情况下,冒充了受害者。

    分类:

    • GET 类型的 CSRF
      • 举例:
        1. 用户访问<img src="http://a.com/withdraw?amount=10000&for=hacker">
        2. 在受害者访问含有这个 img 的页面后,浏览器会自动向 http://a.com 发出一次HTTP请求。http://a.com 就会收到包含受害者登录信息的一次跨域请求。
    • POST 类型的 CSRF
      • 举例:
        1. 这种类型的 CSRF 利用起来通常使用的是一个自动提交的表单
        2. 访问该页面后,表单会自动提交,相当于模拟用户完成了一次POST操作
          <form action="http://bank.example/withdraw" method=POST>
            <input type="hidden" name="account" value="xiaoming" />
            <input type="hidden" name="amount" value="10000" />
            <input type="hidden" name="for" value="hacker" />
          </form>
          <script> document.forms[0].submit(); </script> 
          
    • 链接类型的 CSRF
      • 需要用户点击才能触发,没有前两种打开页面就中招来的爽。

    解决方法:

    • 自动防御:阻止不明外域的访问。
    • 主动防御:提交时要求附加本域才能获取的信息。

👉 跨域解决方案

  1. JSONP
  • 最早出现的解决方案(并非是HTML标准)
  • 由 回调函数 和 数据 组成
  • 需要客户端和服务端相互配合
  • 客户端在 “src” 中引入请求的 URL + 回调函数,这样请求服务器返回的数据会交由回调函数处理,这样就实现了跨域请求;服务端在接收到客户端请求后,首先取得客户端要回调的函数名,再生成 JavaScript 代码段返回给浏览器,浏览器在获取到返回结果后直接调用回调函数完成任务。
  • 具体案例可以查看 jsonp.html
  • 优点:不受同源策略的限制;兼容性好;
  • 缺点:只支持GET请求,不支持其他类型的HTTP请求
  1. CORS(Cross-Origin Resource Sharing)
  • 新的 W3C 标准。
  • 跨源资源共享,是一种基于 HTTP 头的机制。新增一组http首部字段,允许浏览器向声明了CORS的服务器发出请求,解决Ajax只能同源使用的限制。
  • http协议新增的header解析:
    • Access-Control-Allow-Origin
      • 定义:服务器允许哪些域可以访问该资源
      • 语法:Access-Control-Allow-Origin: <origin> | *
    • Access-Control-Allow-Methods
      • 定义:用于预检请求的响应,指明实际请求所允许使用的HTTP方法
      • 语法:Access-Control-Allow-Methods: <method>[, <method>]*
    • Access-Control-Allow-Headers
      • 定义:用于预检请求的响应,指明了实际请求中允许携带的首部字段
      • 语法:Access-Control-Allow-Headers: <field-name>[, <field-name>]*
    • Access-Control-Max-Age
      • 定义:用于预检请求的响应,指定了预检请求能够被缓存多久
      • 语法:Access-Control-Max-Age: <delta-seconds>
    • Access-Control-Allow-Credentials(可选,布尔值)
      • 定义:是否允许发送Cookie,如果为true,服务器明确许可,Cookie可以包含在请求中,一起发给服务器
      • 语法:Access-Control-Allow-Credentials: true
      • 注意点:如果设置为true,还需要带上 withCredentials,且Access-Control-Allow-Origin必须指定明确的、与请求网页一致的域名
    • Origin
      • 定义:表明预检请求或实际请求的源站,不管是否为跨域请求,Origin字段总是被发送
      • 语法:Origin: <origin>
    • Access-Control-Request-Method
      • 定义:该首部字段用于预检请求。其作用是,将实际请求所使用的 HTTP 方法告诉服务器
      • 语法:Access-Control-Request-Method: <method>
    • Access-Control-Request-Headers
      • 定义:该首部字段用于预检请求。其作用是,将实际请求所携带的首部字段告诉服务器
      • 语法:Access-Control-Request-Headers: <field-name>[, <field-name>]*
  • 预检请求
    • 定义:浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的跨域请求,否则就报错。
    • 注意点:预检请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。必须包含Access-Control-Request-MethodAccess-Control-Request-Headers字段
    • 触发点:浏览器将CORS请求分为简单请求和复杂请求,只有复杂请求才会触发预检请求。满足以下条件的属于简单请求,其他的都属于复杂请求。
      • 请求方法是以下三种方法之一
        • HEAD
        • GET
        • POST
      • HTTP的头信息不超出以下几种字段
        • Accept
        • Accept-Language
        • Content-Language
        • Last-Event-ID
        • Content-Type:只限于以下三种
          1. application/x-www-form-urlencoded
          2. multipart/form-data
          3. text/plain
  • 具体案列可以查看 cors.html,附带一些简单的header配置
  • 优点:支持所有类型的http请求;更加容易进行错误处理
  • 缺点:兼容性差
  1. nginx 反向代理
  • 原理: 配置一个代理路径替换实际的访问路径,使得浏览器认为访问的资源都是属于相同协议,域名和端口的,而实际访问的并不是代理路径,而是通过代理路径找到实际路径进行访问。
  • 接口转发以及响应都会由nginx来替你完成,你只需要在nginx.conf文件中配置好你的反向代理,确保能够正确代理。
  • 具体案例可以查看 nginx-1.20.1 目录下的 nginx.confindex.html
  1. document.domain + iframe跨域

  2. location.hash + iframe跨域

  3. window.name + iframe跨域

  4. postMessage跨域

  5. WebSocket协议跨域

总结: 以上前3种还是比较典型使用的跨域处理方式,如果是前后端分离开发的项目中比较推荐采用nginx反向代理的方式。


前端开发过程解决跨域

  • vue开发 vue配置
    • 原理:利用服务器访问服务器没有跨域问题的原理
    • 请求过程:先向代理(同源)服务器发起请求,再由代理(同源)服务器请求外部服务器

👉 参考资料

不要再问我跨域的问题了

跨站脚本攻击—XSS

浅谈CSRF攻击方式

美团技术沙龙:如何防止CSRF攻击

跨源资源共享(CORS)

跨域资源共享 CORS 详解

vue开发中使用代理