背景
做PC的,安全方面主要集中在浏览器安全上,浏览器安全有大概分为三块内容:Web页面安全、浏览器网络安全、浏览器系统安全。
同源策略(Same-origin policy)
是最基础,也是最核心的安全策略,是浏览器为了用户安全添加的一个安全措施。
同源
如果两个URL,协议、域名、端口号,这三个都相等,那么认为是同源的网站。
浏览器对于同源的网站,是可以互相访问资源和操作DOM的。相反,如果两个不同源的网站想要相互访问资源,会收到一些安全限制,这个安全限制就是 同源策略。
限制了什么?
1. DOM
从当前页打开一个非同源的新页,新页中是无法操作和访问DOM的,连window对象都是无法访问的。
具体操作:在bing.com的控制台中通过open(https://baidu.com)打开百度的页面,百度页面的控制台中通过opener是可以访问到原页面window对象的,但是因为同源策略的限制,此时是访问不到bing.com页面的window对象。
如果想要对不同源的页面通信,可以采用跨文档消息机制,postMessage等方式。
2. Web数据
同源策略还对cookie、localStorage、IndexDB等存储数据进行了限制,是无法读取这些数据的。
因为cookie在请求时会自动带上,所以非同源时也是有限制的,不会自动带上的,也无法访问到cookie。 具体来说就是发送请求时,判断请求路径和cookie中记录的域名Domain和路径path是否一致。
3. 网络
同源策略也在网络层面做了处理,直接访问不同源的资源会报错,需要对方服务器支持CORS跨域资源共享。
XSS跨站脚本攻击
Cross Site Scripting跨站脚本攻击。
指在一个网站中插入一段恶意脚本,访问网站时脚本就会执行,进而做一些恶意操作来进行攻击。
如何注入恶意脚本的?
存储型XSS
首先将恶意脚本提交到网站的服务器,然后用户请求时服务器就会将带有恶意脚本的网页返回,以此达到了注入恶意脚本的目的。
举个例子来说:通过页面的一些输入框将恶意脚本保存到服务端,然后其他用户访问该资源时,就会携带恶意脚本。比如前端通过输入框来提交信息,其他用户能访问该信息,这时候需要对输入框内容进行关键词过滤。
不过现代页面基本都是通过组件来完成的,已经帮我们处理过了,所以不用太担心该问题。
反射型XSS
恶意脚本属于用户提交到服务器的一部分,然后服务器会将用户提交信息再返回,前端页面展示该信息时,就会将恶意脚本插入到网页中。
举个例子来说:页面用户的注册,用户名改为<script>alert('xss')</script>,会将用户名称提交给服务器,然后服务器再返回到页面中显示,如果不处理的话,这段脚本就会被执行,从而达成攻击目的。
这种攻击不会将恶意脚本存储到服务器。
基于DOM的XSS攻击
这种攻击通常是在网络传输过程中发生的,比如软件劫持后返回一个假的页面,假页面中有一些恶意脚本,进而达到攻击目的。
应对措施
可以看到XSS攻击的主要方式,就是在页面中注入恶意脚本,从而达到窃取用户信息或其他攻击目的的。
恶意脚本:
<script>alert('xss')</script><input onfocus="alert('xss')" autofocus /><img src onerror="alert('xss')" /><svg onload="alert('xss')" /><a href="javascript:alert('xss')" />
可以看到主要原理就是通过事件回调来插入脚本的。
所以常见的应对手段,就是对渲染内容进行转译,转译之后是一个纯字符串不会执行。
在React中,元素是通过JSX创建的,本质是一个VDom:
- Vdom中有一个
$$typeof标记,是一个Symbol类型的,可以有效防止XSS,因为网络传输一般都会通过JSON转译,但是JSON传不了Symbol类型。React校验不通过就不会渲染成DOM节点。 - 另外React在渲染之前,会对所有内容进行转译,也可以防止XSS
但是并不是说完全杜绝了XSS,
- 比如
dangerouslySetInnerHTML,React不会对该内容进行转译,有风险。 - 直接使用用户输入的内容作为回调(a标签的href、img的src等)也可能有风险。
服务端对关键字过滤或编码处理
服务器端接收或者返回时,对一些特殊字符如<script>进行过滤或者转码,这样就起到了一个防范作用。
CSP
为了防止该攻击,浏览器引入了内容安全策略CSP:让服务器来决定浏览器能加载哪些资源,并且决定浏览器是否能执行内联的脚本。
服务端响应时添加Content-Security-Policy: policy响应头,或者前端的meta标签添加:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src https://*; child-src 'none';" />cookie的HttpOnly
通过该属性可以保护Cookie不被JS(document.cookie)操作
CSRF
Cross-site request forgery跨站请求伪造。即通过引诱用户点击第三方链接,利用用户已登录的状态来达成攻击目的。
应对措施
一般已登录状态都是通过cookie来存储的,所以需要设置SameSite属性,这样往不同站点发送请求时就不会自动带上Cookie。
安全沙箱
安全沙箱的目的是为了让浏览器不威胁到操作系统的安全。
所以现代浏览器划分出了浏览器内核和渲染内核两个核心模块,渲染内核负责渲染任务、JS执行等工作,浏览器内核负责浏览器的其他工作,如网络资源的获取和发送、和操作系统的交互;最后两个模块通过IPC等方式来进行数据通信。
所有的网络资源都通过浏览器内核中的网络进程下载,下载完之后将资源通过IPC提交给渲染内核中的渲染进程,等渲染内核执行完之后生成了最终图片,再将最终的图片交给浏览器内核去渲染显示到显示器上。
之所以这么做,是因为浏览器默认网络上的所有资源都是不安全的,执行网络资源的步骤放到渲染内核中隔离,就算有恶意程序也不会影响到操作系统。执行资源的任务和操作系统之间的"墙"就是安全沙箱。
浏览器的沙箱是以进程为最小单位的,利用操作系统的提供的安全技术,让渲染进程在执行时禁止访问或修改操作系统的数据,如果需要访问资源要通过浏览器内核来处理,处理完成后将数据通过IPC发送到渲染内核中。
安全沙箱如何影响渲染内核的功能
应用了安全沙箱的渲染进程是无法读取或修改操作系统数据,所以需要将能读取和修改操作系统数据的api放到浏览器内核中实现。
- 数据的持久存储
渲染进程由于安全沙箱无法直接读取系统数据,但是确实需要做一些数据的存储工作,比如Cookie的读取等。所以浏览器内核中通常会维护一个Cookie相关的数据库,当渲染内核需要读取时通过IPC向浏览器内核申请,浏览器内核处理之后再将数据发送回渲染内核。
- 网络的访问
同样安全沙箱的隔离,渲染内核也是无法直接访问网络的,需要通过浏览器内核。但是浏览器内核处理请求之前,需要确定一下渲染进程是否有权限访问该请求,即同源策略的限制。
- 用户交互事件
操作系统允许应用程序绘制窗口,给应用程序下发的成为窗口句柄,通过窗口句柄可以监听鼠标和键盘事件,让用户能和系统交互。但是安全沙箱的隔离,渲染进程无法访问窗口句柄,也就不能直接监听键鼠事件。
所以也是通过浏览器内核来转发的,操作系统下发的键鼠事件到浏览器内核,浏览器内核判断是否在页面内,如果在页面内将事件发送到渲染进程;如果是导航栏,则由浏览器内核来处理。
站点隔离(Site Isolation)
最初浏览器是以标签页为单位划分渲染进程的,也就是同一个标签页所有内容处于同一个渲染进程中。但是页面内可以嵌入多个不同站点的iframe,这就导致了不同站的iframe也能访问到当前渲染进程的内容。
所以引入了站点隔离,即将同一站点中相关关联的页面放到同一个渲染进程中,简单来说就是以是否同站为区分标准了。
NOTE
有效顶级域名eTLD以及二级域名相同即为同站点。有效顶级域名有一个列表来记录的
HTTPS
HTTP明文传输的特性导致很不安全,所以需要引入一个安全的加密方案,也就是HTTPS。
HTTP是在应用层的,TCP是在下面的传输层,所以想要安全通信,可以在中间加一个安全层,即TSL协议,经过安全层的数据会被加密和解密。
仅仅采用对称加密,安全性太低。如果仅仅采用非对称加密,每次加解密又太慢。所以采用对称加密+非对称加密的方式来完成TSL的加密。
使用对称加密来传输数据,使用非对称加密来加密传输数据时的公钥:
- 浏览器向服务器发送
对称加密套件、非对称加密套件、client-random客户端随机数 - 服务端收到后,选择对称加密和非对称加密算法,然后生成一个service-random服务端随机数,向浏览器发送
选择的加密套件、service-random、公钥A - 浏览器收到后,用
client-random + service-random计算出pre-master,然后用公钥A来加密pre-master发送给服务端 - 服务端用
私钥A解密pre-master数据,并向浏览器端发送确定信息
这样双方都有client-random + service-random + pre-master三个随机数,并且选择的加密套件是一样的,所以双方可以在本地利用这三个随机数生成一个master secret对称密钥。之后用这个对称密钥来传输数据即可。
但是还有一个身份认证的问题,所以需要数字证书:
想要证明身份,需要用CA颁发的数字证书来证明,另外也可以将公钥A放到数字证书中,可以确保未加密前公钥不被盗取。
如果保证CA的身份?这就需要一层层往上验证,只要root CA,一般root CA是内置在操作系统里的。
引入数字证书之后,只需要在上面第二步中,将公钥A换成数字证书,然后第三步执行之前,浏览器会去验证数字证书,验证通过再继续下面的流程。