吃透浏览器安全(同源限制/XSS/CSRF/中间人攻击)

8,056 阅读17分钟

前言

随着互联网的高速发展,信息安全问题已经成为企业最为关注的焦点之一,而前端又是引发企业安全问题的高危据点。在移动互联网时代,特别是前端人员除了传统的 XSS、CSRF 等安全问题之外,又时常遭遇网络劫持、非法调用 Hybrid API 等新型安全问题。当然,浏览器自身也在不断在进化和发展,不断引入 CSP、SameSite、HttpOnly、Cookies 等新技术来增强安全性,但是仍存在很多潜在的威胁,这需要我们不断进行“查漏补缺”

浏览器安全可以分为三大块:Web页面安全、浏览器网络安全、浏览器系统安全

Web页面安全主要就是同源策略限制

什么是同源策略

同源指的是我们访问站点的:协议域名端口号必须一至,才叫同源

浏览器默认同源之间的站点是可以相互访问资源和操作DOM的,而不同源之间想要互相访问资源或者操作DOM,那就需要加一些安全策略的限制,俗称同源策略

同源策略主要限制了三个方面:

  1. DOM层面:不同源站点之间不能相互访问和操作DOM
  2. 数据层面:不能获取不同源站点的Cookie、LocalStorage、indexDB等数据
  3. 网络层面:不能通过XMLHttpRequest向不同源站点发送请求

当然同源策略限制也不是绝对隔离不同源的站点,比如link、img、script标签都没有跨域限制,这让我们开发更灵活了,但是也同样带来了一些安全问题,也就是浏览器网络安全问题,最典型的就是XSS攻击和CSRF攻击

说一下XSS攻击

XSS攻击是一种代码注入攻击,通过恶意注入脚本在浏览器运行,然后盗取用户信息

造成XSS攻击其实本质上还是因为网站没有过滤恶意代码,与正常代码混在一起之后,浏览器没有办法分辨哪些是可信的,然后导致恶意代码也被执行。然后可能导致以下情况:

  • 页面数据或用户信息被窃取,如DOM、Cookie、LocalStorage
  • 修改DOM,比如伪造登录窗口或在页面生成浮窗广告
  • 监听用户行为,比如在登录或银行等站点用 addEventListener 监听键盘事件,窃取账号密码等信息
  • 流量被劫持向其他网站

XSS攻击有三种类型:存储型反射型DOM型

  1. 存储型:是在有发贴评论等带有数据保存功能的网站的input、textarea将恶意代码提交到网站数据库中,如<script src="http://恶意网站"></script> ,然后比如在显示评论的页面就会从数据获取,并直接执行这个script标签里的恶意代码

  2. 反射型:是攻击者将恶意JS脚本作为用户发送给网站请求中的一部分,然后网站又把恶意脚本返回给用户,这时候就会在页面中被执行。比如打开包含带恶意脚本的链接,当打开后会向服务器请求后,服务器会获取URL中的数据然后拼接在HTML上返回,然后执行。它和存储型不同的是不会储存在服务器里

  3. 基于DOM型:就是攻击者通过一些劫持手段,在页面资源传输过程中劫持并修改页面的数据,插入恶意代码

怎么防范XSS攻击,有什么解决办法?

  • 就是对输入框的内容进行过滤或使用转义符进行转码

    字符转义后的字符
    <&lt;
    &gt;
    "&quot;
    '&#x27;
    /&#x2F
    &&amp;
  • 使用CSP,就是白名单,告诉浏览器哪些外部资源可以加载执行,让即使插入进来恶意代码的也不会执行,或者可以向哪些第三方站点提交数据。开启白名单的方式有两种:

    • 使用 meta 标签 <meta http-equiv="Content-Security-Policy">
    • 设置http头部的 Content-Security-Policy
  • 对一些敏感信息进行保护,在Cookie信息中添加httpOnly,告诉浏览器在保存Cookie,且不要对客户端脚本开放访问权限,然后就不能通过document.cookie获取cookie了

Set-Cookie: widget_session=123456; httpOnly
  • 使用验证码,避免脚本伪装成用户执行一些操作

说一下CSRF攻击

就是跨站请求伪造攻击主要就是利用用户的登录状态发起跨站请求,比如邮箱里的乱七八糟的链接,打开链接的时候邮箱肯定是处于登录状态,然后黑客就可以用这个登录状态,伪造带有正确 Cookie 的 http 请求,直接绕过后台的登录验证,然后冒充用户执行一些操作

发起CSRF攻击有三个必要条件:

  1. 目标网站一定要有CSRF漏洞
  2. 用户登录过目标网站,并且浏览器保存了登录状态
  3. 需要用户主动打开第三方站点

本质是利用cookie在同源请求中携带发送给服务器的特点,来实现冒充用户

CSRF攻击也有三种类型:GET类型POST类型链接型

  • 自动发GET类型:比如imgiframe标签等,当用户打开这个网站时会自动发起带Cookie的资源请求
<img src="http://恶意网址" >
  • 自动发POST类型:比如整一个隐藏的表单,在用户进入页面的时候自动提交表单
<form id="hack" action="https://恶意网址" method="post">
    ...
</form>
<script>document.getElementById('hack').submit()</script>
  • 诱导链接型:就是诱导用户主动点击链接,比如a标签
<a href="https://恶意网址">点击领取大礼包</a>
<a href="https://恶意网址">点击下载美女视频</a>

怎么防范CSRF攻击,有什么解决办法?

  1. 在Cookie信息中添加SameSite属性,这个属性有三个值:

    • strict严格模式,完全禁止使用Cookie
    • lax宽松模式,允许部分情况使用Cookie,跨域的都行,a标签跳转,link标签,GET提交表单
    • none:任何情况下都会发送Cookie,但必须同时设置Secure属性,意思是需要安全上下文,Cookie 只能通过https发送,否则无效

    Chrome 80之前默认值是none,之后是lax

    Set-Cookie: widget_session=123456; SameSite=None; Secure
    
  2. 验证请求来源:服务器根据http请求头中的OriginReferer属性判断是否为允许访问的站点,从而对请求进行过滤。优先判断Origin,如果两个都不存在的话就直接阻止。

    • Referer:记录了请求是从哪个链接跳过来的并且包含了路径信息,也就是来源地址,所有请求都会有。不过这家伙不太可靠,所以后来又新增了Origin属性
      Referer: https://juejin.cn/editor/drafts/xxxx
      
    • origin:记录了域名信息,没有具体的URL路径,post请求才会有
      Origin: https://juejin.cn
      
  3. Token验证:服务器向用户返回一个随机数Token,再次请求时在请求头中以参数的形式添加入这个Token,然后服务器验证这个Token,如果没有或者内容不正确,就拒绝请求。缺点是

    • 每个请求都得添加比较繁琐
    • 单方面验证Cookie可能会被冒用,
    • 如果网站不止一台服务器,通过负载均衡转到了其他服务器的话,其他所有服务器中的Session中都得保留Token,不然就验证不了了
  4. 双重验证Cookie:利用攻击者只能利用Cookie,不能获取Cookie的特点,用户访问页面时,服务器向请求域名添加一个Cookie随机字符串,然后,用户再次请求时从Cookie中取出这个字符串,添加到URL参数中,然后服务器通过对Cookie中的数据和参数中的数据对比验证,不一样就拒绝请求。

    缺点是如果网站存在XSS漏洞,这法子就会失效,而且不能做到子域名的隔离

浏览器系统安全 - 安全沙箱

首先,我们设想一下,如果我们下载了一个恶意程序,但是没有执行它,对我们有没有影响?

那肯定没有,而浏览器也是一样的

浏览器可以安全地下载各种网络资源,但是执行的时候就需要谨慎了。比如解析HTML、CSS、执行JS等操作,一不小心黑客就会利用这些操作对有漏洞的浏览器发起攻击

所以需要在渲染进程和操作系统之间建一堵墙,有这堵墙在挡着,黑客能黑进来,也只能获取渲染进的操作权限(如下图),不会影响到外面,而这隔离操作系统和渲染进程的一堵墙就是安全沙箱

f8cc6394832ed238f18a01eff5726f1b.webp

安全沙箱最小的保护单位是进程,并且能限制进程对操作系统资源的访问和修改,这就意味着,安全沙箱所在的进程不能有读写操作系统的功能,比如读写本地文件、发起网络请求,调用GPU接口等

安全沙箱怎么影响各个模块功能

  • 持久存储
    • 存储Cookie的读写,浏览器内核会维护一个存放所有Cookie的Cookie数据库,在渲染进程通过JS读取Cookie时,渲染进程会通过IPC将读取Cookie的信息发送给内核,浏览器内核读取Cookie之后再将内容通过IPC返回给渲染进程
    • 缓存文件的读写也是由浏览器内核实现
  • 网络访问:渲染进程不能直接访问网络,也需要通过浏览器内核,而浏览器内核在处理URL请求之前,会检查渲染进程有没有权限请求该URL,比如有没有跨域
  • 用户交互
    • 输入时:操作系统会将输入事件传给浏览器内核,内核判断如果是地址栏输入事件就直接在内核处理,如果是页面里的就转发给渲染进程
    • 渲染时:渲染进程渲染出位图后,需要将生成好的位图发送给浏览器内核,再由内核将位图复制到屏幕上显示

站点隔离你知道吗

我们知道一个标签页会有一个渲染进程,假如这个标签页里面有多个不同站点的 iframe 呢? 这就会导致多个不同站点的内容运行在同一个渲染进程中,这肯定不行

所有操作系统中都存在两个A级漏洞:幽灵(Spectre)和熔毁(Meltdown),这是处理器架构导致的,很难修补

如果银行页面中有一个恶意 iframe ,这个恶意站点利用两个A级漏洞入侵渲染进程,那恶意程序就可以读取银行站点渲染进程内所有内容了,这对用户来说风险就很大了,如果没有沙箱保护,甚至还可以对操作系统发起攻击

所以后来Chrome对渲染进程进行重构,将标签级的渲染进程改成 iframe 级渲染进程,就是说一个标签页内可能运行多个渲染进程,并且相互隔离,这样恶意 iframe 就无法访问页面中其他的内容了,也就无法攻击其他站点了,这就是站点隔离

中间人攻击

在 http 数据提交给 TCP 层之后,会经过用户电脑、路由器、运营商、服务器,这中间每一个环节,都不是安全的

一句话就是:在 http 传输过程中容易被中间人窃取、伪造、篡改,这种攻击方式称为中间人攻击

那怎么让数据可以更安全的传输呢?

就是使用 https ,利用 https 安全层对数据进行加解密操作,以保证数据安全。

关于 https性能优化、版本、优缺点、SSL/TLS、握手(RSA、TLS1.2、TLS1.3)三个版本及优化等等,文章太长这里就不展开了,可以看我另一篇文章有详细介绍

那么 https 是如何对数据加解密的呢?这要先说一下它的算法

对称加密算法

就是加密和解密使用同一个密钥。如AES、DES。加解密过程:

  1. 浏览器给服务器并发送一个随机数client-random加密套件(一个支持的加密方法列表)
  2. 服务器生成给浏览器返回另一个随机数server-random加密套件
  3. 两边分别返回确认消息。然后两者用加密方法将两个随机数混合生成密钥,这就是通信双上加解密的密钥

有了密钥之后就可以对数据进行加密传输了

问题是client-randomserver-random都是明文的,双方如何安全的传递两个随机数和加密方法呢?直接传给客户端,那过程中就很可能被窃取,中间人还是能解密拿到数据,往下看

不对称加密算法

就是一对密钥,有公钥(public key)和私钥(private key),其中一个密钥加密后的数据,只能用另一个密钥进行解密。如RSAECDHE。加解密过程:

  1. 浏览器给服务器发送加密套件
  2. 服务器选好支持的加密方法公钥(明文) 传给浏览器
  3. 两边分别返回确认消息。然后浏览器用公钥对数据进行加密,这个密钥只能用私钥解密

这是不是看上去很完美了

其实还存在很严重的问题

  1. 使用公钥反推出私钥是非常困难,但不是做不到,随着计算机运算能力提高,非对称密钥至少要2048位才能保证安全性,这就导致加解密速度慢,效率太低
  2. 无法保证服务器发送给浏览器的数据安全。因为浏览器可以用公钥来加密,而浏览器就只能用私钥加密,公钥是明文传输的,中间人可以获取到,这样服务器端的数据安全就得不到保证了

所以!

混合加密

TLS实际用的是两种算法的混合加密通过 非对称加密算法 交换 对称加密算法 的密钥,交换完成后,再使用对称加密进行加解密传输数据。这样就保证了会话的机密性。过程如下

  1. 浏览器给服务器发送一个随机数client-random对称和非对称加密套件
  2. 服务器把另一个随机数server-random加密套件公钥传给浏览器
  3. 浏览器又生成另一个随机数pre-random,并用公钥对 pre-random 加密后传给服务器
  4. 服务器再用私钥解密,得到pre-random,并返回确认消息
  5. 这样浏览器和服务器都有三个随机数了,然后各自将三个随机数用加密方法混合生成最终的对称密钥

然后开始数据加密传输

这样即便被截持,中间人没有私钥就拿不到pre-random,就无法生成最终密钥

这样就安全了吗?

emmmm......还没

因为问题又来了,如果一开始DNS就被中间人劫持,那么请求被中间人截获,中间人把他自己的服务器公钥给了浏览器,浏览器收到公钥就把信息发给中间人了,中间人解密拿到数据,并干了一些见不得人的勾当之后,再请求实际服务器,拿到服务器公钥,再把加密处理过后的数据发给服务器

这样不知不觉间信息就被人窃取了,所以在结合对称和非对称加密的基础上,还需要服务器向浏览器证明身份,那怎么证明呢?

所以数字证书来了,往下看

如何保证数据是否被篡改?

数字证书(数字签名)

它可以帮我们验证服务器身份,而且数字证书里包含了公钥,而数字证书需要向有权威的认证机构(CA)获取授权给服务器。

相比之前就变成了

  • 服务器不直接返回公钥给浏览器,而是返回数字证书,而公钥就在数字证书中
  • 浏览器这边多了一步证书验证,验证成功才能继续后续流程

那么如何申请数字证书呢?

  • 首先,服务器准备一套公钥私钥,私钥留着自己用
  • 服务器将公钥和站点等信息提交给CA认证,这个是要钱
  • CA验证服务器提供的信息
  • 审核通过后签发认证的数字证书,包含了公钥CA信息有效时间证书序列等这些都是明文的,还有一个CA生成的签名

CA的签名过程

  • CA也有一套公钥私钥
  • CA使用摘要算法计算服务器提交的明文信息并得出信息摘要
  • 然后CA再用它的私钥和特定的算法对信息摘要加密,生成签名
  • 签名服务器公钥等信息打包放入数字证书,并返回给服务器
  • 服务器配置好证书,以后浏览器连接服务器,都先把证书发给客户端验证

摘要算法:主要用于保证信息的完整性。常见的MD5算法散列函数hash函数都属于这类算法,其特点就是单向性无法反推原文

浏览器如何验证数字证书

  • 浏览器连接服务器,都先把证书发给客户端验证
  • 使用CA公钥和声明的签名算法对CA中的签名进行解密,得到服务器的摘要内容和服务器公钥
  • 再用摘要算法对证书里的服务器公钥生成摘要,再把这个摘要和上一步得到的摘要对比
  • 然后将两个信息摘要对比,如果是一致的,就说明证书是合法的,即证明了服务器自己,否则就是非法的

证书认证又分为单向认证双向认证

单向认证:服务器发送证书,客户端验证证书
双向认证:服务器和客户端分别提供证书给对方,并互相验证对方的证书

不过大多数https服务器都是单向认证,如果服务器需要验证客户端的身份,一般通过用户名、密码、手机验证码等之类的凭证来验证。只有更高级别的要求的系统,比如大额网银转账等,就会提供双向认证的场景,来确保对客户身份提供认证性

另外在申请和使用证书的过程中,需要注意

  • 申请数字证书是不需要提供私钥的,要确保私钥永远只能由服务器掌握
  • 数字证书最核心的是CA使用它的私钥生成的数字签名
  • 内置CA对应的证书称为根证书,根证书是最权威的机构,它们自己为自己签名,这称为自签名证书

有了这些之后就安全了吗?

emmmmm.....没有

虽然不是绝对安全,但是现行架构下最安全的解决文案了,大大增加了中间人的攻击成本

结语

点赞支持、手留余香、与有荣焉

感谢你能看到这里,加油哦!

参考

浏览器工作原理与实践

HTTPS:网络安全攻坚战