1.常见的浏览器内核有哪些?浏览器的内核可以分成两部分:
渲染引擎和js引擎(注意:我们常说的浏览器内核就是指渲染引擎)
由于js引擎越来越独立,内核就指的只是渲染引擎了,渲染引擎主要用来请求网络页面资源解析排版后呈现给用户
浏览器/runtime内核(渲染引擎)javascript 引擎
chrome blink(28~) webkit(chrome 27) v8
firefox gecko spidermonkey
safari webkit javascriptcore
edge edgehtml chakra(for javascript)
ie trident chakra(for jscript)
opera presto->blink linear a(4.0-6.1)/ linear b(7.0-9.2)/ futhark(9.5-10.2)/ carakan(10.5-)
node.js - v8
2.浏览器的主要组成部分有哪些?用户界面:包括地址栏,前进/后退/刷新/书签等按钮
浏览器引擎:在用户界面和呈现引擎之间传送指令
渲染引擎:用来绘制请求的内容
网络:用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作
javascript解释器:用来解析执行javascript代码
用户界面后端:用于绘制基本的窗口小部件,比如组合框和窗口,底层使用操作系统的用户接口
数据存储:属于持久层,浏览器在硬盘中保存类似cookie的各种数据,html5定义了web database技术,这是一种轻量级完整的客户端存储技术
注意:与大多数浏览器不同的是,谷歌(chrome)浏览器的每个标签页都分别对应一个呈现引擎实例。每个标签页都是一个独立的进程
3.说一说从输入url到页面呈现发生了什么?这个题可以说是面试最常见也是一道可以无限难的题了,一般面试官出这道题就是为了考察你的前端知识深度。
1、浏览器接受url开启网络请求线程(涉及到:浏览器机制,线程与进程等)
2、开启网络线程到发出一个完整的http请求(涉及到:dns查询,tcp/ip请求,5层网络协议等)
3、从服务器接收到请求到对应后台接受到请求(涉及到:负载均衡,安全拦截,后台内部处理等)
4、后台与前台的http交互(涉及到:http头,响应码,报文结构,cookie等)
5、缓存问题(涉及到:http强缓存与协商缓存,缓存头,etag,expired,cache-control等)
6、浏览器接受到http数据包后的解析流程(涉及到html词法分析,解析成dom树,解析css生成cssom树,合并生成render渲染树。然后layout布局,painting渲染,复合图层合成,gpu绘制,外链处理等)
7、css可视化模型(涉及到:元素渲染规则,如:包含块,控制框,bfc,ifc等)
8、js引擎解析过程(涉及到:js解析阶段,预处理阶段,执行阶段生成执行上下文,vo(全局对象),作用域链,回收机制等)
你会发现一个简单的输入url到页面呈现,之间会发生这么多过程,是不是瞬间觉得崩溃了?(别急,这一章我们不讲这么深,先教你如何回答这个问题,后面这一节单独出文章讲)
浏览器通过dns服务器得到域名的ip地址,向这个ip地址请求得到html文本浏览器渲染进程解析html文本,构建dom树解析html的同时,如果遇到内联样式或者样式文件,则下载并构建样式规则,如果遇到javascript脚本,则会下载执行脚本dom树和cssom构建完成之后,渲染进程将两者合并成渲染树(render tree)渲染进程开始对渲染树进行布局,生成布局树(layout tree)渲染树对布局树进行绘制,生成绘制记录
4.浏览器是如何解析代码的?解析html
html是逐行解析的,浏览器的渲染引擎会将html文档解析并转换成dom节点。
将html解析成许多tokens将tokens解析成object将object组合成一个dom树解析css
浏览器会从右往左解析css选择器
我们知道dom树与cssom树合并成render树,实际上是将cssom附着到dom树上,因此需要根据选择器提供的信息对dom树进行遍历。
我们看一个例子:
<style>.nav .title span {color:blue}</style><div class='nav'> <div class='title'> <span>南玖</span> </div> <div class="sub_title">前端</header></div>
从右至左的匹配:
先找到所有的最右节点 span,对于每一个 span,向上寻找节点 div.title
由 h3再向上寻找 div.nav 的节点
最后找到根元素 html 则结束这个分支的遍历。
解析js
在浏览器中有一个js解析器的工具,专门用来解析我们的js代码。
当浏览器遇到js代码时,立马召唤“js解析器”出来工作。
解析器会找到js当中的所有变量、函数、参数等等,并且把变量赋值为未定义(undefined)。
把函数取出来成为一个函数块,然后存放到仓库当中。这件事情做完了之后才开始逐行解析代码(由上向下,由左向右),然后再去和仓库进行匹配。
5.domcontentloaded与load的区别?domcontentloaded:仅当dom解析完成后触发,不包括样式表,图片等资源。load:当页面上所有的dom,样式表,脚本,图片等资源加载完毕事触发。6.浏览器重绘域重排的区别?重排: 部分渲染树或整个渲染树需要重新分析且节点尺寸需要重新计算,表现为重新生成布局,重新排列元素重绘: 由于节点的几何属性发生改变或样式改变,例如元素背景元素,表现为某些元素的外观被改变重绘不一定导致重排,但重排一定绘导致重绘
如何触发重绘和重排?
任何改变用来构建渲染树的信息都会导致一次重排或重绘:
添加、删除、更新dom节点通过display: none隐藏一个dom节点-触发重排和重绘通过visibility: hidden隐藏一个dom节点-只触发重绘,因为没有几何变化移动或者给页面中的dom节点添加动画添加一个样式表,调整样式属性用户行为,例如调整窗口大小,改变字号,或者滚动。如何避免重绘或重排?
集中改变样式:比如使用class的方式来集中改变样式
使用document.createdocumentfragment():我们可以通过createdocumentfragment创建一个游离于dom树之外的节点,然后在此节点上批量操作,最后插入dom树中,因此只触发一次重排
提升为合成层
将元素提升为合成层有以下优点:
合成层的位图,会交由 gpu 合成,比 cpu 处理要快
当需要repaint 时,只需要 repaint 本身,不会影响到其他的层
对于 transform 和 opacity 效果,不会触发 layout 和 paint
提升合成层的最好方式是使用 css 的 will-change 属性
7.为什么js是单线程的?这主要与js的用途有关,js作为浏览器的脚本语言,最初主要是实现用户与浏览器的交互,以及操作dom。这就决定了它只能是单线程,否则会带来许多复杂的同步问题。
举个例子: 如果js是多线程的,其中一个线程要修改一个dom元素,另外一个线程想要删除这个dom元素,这时候浏览器就不知道该听谁的。所以为了避免复杂性,从一诞生,javascript就被设计成单线程。
为了利用多核cpu的计算能力,html5提出web worker标准,允许javascript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作dom。所以,这个新标准并没有改变javascript单线程的本质
8.css加载会阻塞dom吗?先上结论
css不会阻塞dom的解析,但会阻塞dom的渲染css会阻塞js执行,但不会阻塞js文件的下载cssom的作用
第一个是提供给javascript操作样式表的能力
第二个是为布局树的合成提供基础的样式信息
这个cssom体现在dom中就是document.stylesheets
由之前讲到的浏览器渲染流程我们可以看出:
dom和cssom通常是并行构建的,所以css加载不会阻塞dom的解析
render树是依赖dom树和cssom树的,所以它必须等到两者都加载完毕才能开始构建渲染,所以css加载会阻塞dom的渲染
由于javascript是可以操作dom与css的,如果在修改这些元素属性同时渲染界面(即javascript线程与ui线程同时进行),那么渲染线程前后获得的元素可能就不一致了。所以为了防止渲染出现不可预期的结果,浏览器设置gui渲染线程与javascript线程为互斥的关系
js需要等待css的下载,这是为什么呢?(css阻塞dom执行)
如果js脚本的内容是获取元素的样式,那它就必然依赖css。因为浏览器无法感知js内部到底想干什么,为避免样式获取,就只好等前面所有的样式下载完毕再执行js。但js文件与css文件下载是并行的,css文件会在后面的js文件执行前先加载执行完毕,所以css会阻塞后面js的执行
避免白屏,提高css的加载速度
使用cdn(cdn会根据你的网络状况,挑选最近的一个具有缓存内容的节点为你提供资源,因此可以减少加载时间)对css进行压缩合理使用缓存减少http请求数,合并css文件9.js会阻塞页面吗?先上结论
js会阻塞dom的解析,因此也就会阻塞页面的加载
这也是为什么我们常说要把js文件放在最下面的原因
由于 javascript 是可操纵 dom 的,如果在修改这些元素属性同时渲染界面(即 javascript 线程和 ui 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。
因此为了防止渲染出现不可预期的结果,浏览器设置 **「gui 渲染线程与 javascript 引擎为互斥」**的关系。
当 javascript 引擎执行时 gui 线程会被挂起,gui 更新会被保存在一个队列中等到引擎线程空闲时立即被执行。
当浏览器在执行 javascript 程序的时候,gui 渲染线程会被保存在一个队列中,直到 js 程序执行完成,才会接着执行。
因此如果 js 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
10.defer和async的区别?两者都是异步去加载外部js文件,不会阻塞dom解析async是在外部js加载完成后,浏览器空闲时,load事件触发前执行,标记为async的脚本并不保证按照指定他们的先后顺序执行,该属性对于内联脚本无作用 (即没有**「src」**属性的脚本)。defer是在js加载完成后,整个文档解析完成后,触发 domcontentloaded 事件前执行,如果缺少 src 属性(即内嵌脚本),该属性不应被使用,因为这种情况下它不起作用11.浏览器的垃圾回收机制垃圾回收是一种自动的内存管理机制。当计算机上的动态内存不再需要时,就应该予以释放。
需要注意的是,自动的意思是浏览器可以自动帮助我们回收内存垃圾,但并不代表我们不用关心内存管理,如果操作不当,javascript中仍然会出现内存溢出的情况,造成系统崩溃。
由于字符串,数组,对象等都没有固定大小,因此需要当它们大小已知时,才能对他们进行动态的存储分配。javascript程序每次创建字符串,数组或对象时,解释器都必须分配内存来存储那个实体。
javascript解释器可以检测到何时程序不在使用一个对象了,当它确定这个对象是无用的时候,他就知道不再需要这个对象了,就可以把它占用的内存释放掉了。
浏览器通常采用的垃圾回收有两种方法:标记清除,引用计数。
标记清除
这是javascript中最常用的垃圾回收方式
从2012年起,所有现代浏览器都使用了标记清除的垃圾回收方法,除了低版本ie还是采用的引用计数法。
那么什么叫标记清除呢?
javascript中有一个全局对象,定期的,垃圾回收器将从这个全局对象开始,找出所有从这个全局对象开始引用的对象,再找这些对象引用的对象...对这些活跃的对象标记,这是标记阶段。清楚阶段就是清楚那些没有被标记的对象。
标记清除有一个问题,就是在清除之后,内存空间是不连续的,即出现了内存碎片。如果后面需要一个比较大的连续的内存空间,那将不能满足要求。而标记整理 方法可以有效德地解决这个问题。
在标记的过程中,引入了概念:三色标记法,三色为:
白:未被标记的对象,即不可达对象(没有扫描到的对象),可回收灰:已被标记的对象(可达对象),但是对象还没有被扫描完,不可回收黑:已被扫描完(可达对象),不可回收标记整理:
标记阶段与标记清除法没什么区别,只是标记结束后,标记整理法会将存活的对象向内存的一边移动,最后清理掉边界内存。
引用计数
引用计数的含义是跟踪记录每个值被引用的次数。当一个变量a被赋值时,这个值的引用次数就是1,当变量a重新赋值后,则之前那个值的引用次数就减1。当引用次数变成0时,则说明没有办法再访问这个值了,所以就可以清除这个值占用的内存了。
大多数浏览器已经放弃了这种回收方式
内存泄漏
为避免内存泄漏,一旦数据不再使用,最好通过将其值设为null来释放其引用,这个方法叫做接触引用
哪些情况会造成内存泄漏?如何避免?
以 vue 为例,通常有这些情况:
监听在 window/body 等事件没有解绑绑在 eventbus 的事件没有解绑vuex 的 $store,watch 了之后没有 unwatch使用第三方库创建,没有调用正确的销毁函数解决办法:beforedestroy 中及时销毁
绑定了 dom/bom 对象中的事件 addeventlistener ,removeeventlistener。观察者模式 $on,$off处理。如果组件中使用了定时器,应销毁处理。如果在 mounted/created 钩子中使用了第三方库初始化,对应的销毁。使用弱引用 weakmap、weakset。浏览器中不同类型变量的内存都是何时释放的?
引用类型
在没有引用之后,通过 v8 自动回收。基本类型
如果处于闭包的情况下,要等闭包没有引用才会被 v8 回收。非闭包的情况下,等待 v8 的新生代切换的时候回收。12.说一说浏览器的缓存机制?认识浏览器缓存
当浏览器请求一个网站时,会加载各种资源,对于一些不经常变动的资源,浏览器会将他们保存在本地内存中,下次访问时直接加载这些资源,提高访问速度。
如何知道资源是请求的服务器还是读取的缓存呢?
看上面这张图,有些资源的size值是大小,有些是from disk cache,有些是from memory cache,显示大小的是请求的服务器资源,而显示后面两种的则是读取的缓存。
disk cache: 就是将资源存储在磁盘中,等待下次访问时不需重新下载,直接从磁盘中读取,它的直接操作对象为curlcachemanager。(效率比内存缓存慢,但存储容量大,存储时间长)memory cache: 就是将资源缓存到内存中,等待下次访问时不需重新下载,直接从内存中读取。(从效率上看它是最快的,从存活时间来看,它是最短的。)-memory cachedisk cache
相同点 只能存储一些派生类资源文件 只能存储一些派生类资源文件
不同点 退出进程时数据会被清除 退出进程时数据不会被清除
存储资源 一般脚本、字体、图片会存在内存当中 一般非脚本会存在内存当中,如css等
浏览器缓存分类
强缓存协商缓存浏览器在向服务器请求资源时,首先判断是否命中强缓存,没命中再判断是否命中协商缓存
强缓存
浏览器在加载资源时,会先根据本地缓存资源的header中判断是否命中强缓存,如果命中则直接使用缓存中的资源,不会再向服务器发送请求。 (这里的header中的信息指的是 expires 和 cache-control)
expires
该字段是 http1.0 时的规范,它的值为一个绝对时间的 gmt 格式的时间字符串,比如 expires:mon,18 oct 2066 23:59:59 gmt。这个时间代表着这个资源的失效时间,在此时间之前,即命中缓存。这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱。所以这种方式很快在后来的http1.1版本中被抛弃了。
cache-control
cache-control 是 http1.1 时出现的 header 信息,主要是利用该字段的 max-age 值来进行判断,它是一个相对时间,例如 cache-control:max-age=3600,代表着资源的有效期是 3600 秒。cache-control 除了该字段外,还有下面几个比较常用的设置值:
no-cache:需要进行协商缓存,发送请求到服务器确认是否使用缓存。
no-store:禁止使用缓存,每一次都要重新请求数据。
public:可以被所有的用户缓存,包括终端用户和 cdn 等中间代理服务器。
private:只能被终端用户的浏览器缓存,不允许 cdn 等中继缓存服务器对其缓存。
cache-control 与 expires 可以在服务端配置同时启用,同时启用的时候 cache-control 优先级高。
协商缓存
当强缓存没命中时,浏览器会发送一个请求到服务器,服务器根据 header 中的信息来判断是否命中协商缓存。如果命中,则返回304 ,告诉浏览器资源未更新,可以使用本地缓存。 (这里的header信息指的是last-modify/if-modify-since 和 etag/if-none-match)
last-modify/if-modify-since
浏览器第一次请求一个资源的时候,服务器返回的 header 中会加上 last-modify,last-modify 是一个时间标识该资源的最后修改时间。
当浏览器再次请求该资源时,request 的请求头中会包含 if-modify-since,该值为缓存之前返回的 last-modify。服务器收到 if-modify-since 后,根据资源的最后修改时间判断是否命中缓存。
如果命中缓存,则返回 304,并且不会返回资源内容,并且不会返回 last-modify。
缺点:
短时间内资源发生了改变,last-modified 并不会发生变化。
周期性变化。如果这个资源在一个周期内修改回原来的样子了,我们认为是可以使用缓存的,但是 last-modified 可不这样认为,因此便有了 etag。
etag/if-none-match
与 last-modify/if-modify-since 不同的是,etag/if-none-match 返回的是一个校验码。etag 可以保证每一个资源是唯一的,资源变化都会导致 etag 变化。服务器根据浏览器上送的 if-none-match 值来判断是否命中缓存。
与 last-modified 不一样的是,当服务器返回 304 not modified 的响应时,由于 etag 重新生成过,response header 中还会把这个 etag 返回,即使这个 etag 跟之前的没有变化。
last-modified 与 etag 是可以一起使用的,服务器会优先验证 etag,一致的情况下,才会继续比对 last-modified,最后才决定是否返回 304。
总结
当浏览器访问一个已经访问过的资源是,它的步骤是:
1.先看是否命中强缓存,命中?的话直接使用缓存
2.没命中强缓存,则会发送请求到服务器看是否命中?协商缓存
3.如果命中了协商缓存,服务器会返回304告诉浏览器可以使用本地缓存
4.没命中协商缓存,则服务器会返回新的资源给浏览器
13.什么是浏览器的同源策略,以及跨域?同源策略
同源策略是浏览器的一种自我保护行为。所谓的同源指的是:协议,域名,端口均要相同
浏览器中大部分内容都是受同源策略限制的,但是以下三个标签不受限制:
<img src="..." /><link href="..." /><script src="..."></script>
跨域
跨域指的是浏览器不能执行其它域名下的脚本。它是由浏览器的同源策略限制的。
你可能会想跨域请求到底有没有发送到服务器?
事实上,跨域请求时能够发送到服务器的,并且服务器也能过接受的请求并正常返回结果,只是结果被浏览器拦截了。
跨域解决方案(列出几个常用的)jsonp
它主要是利用script标签不受浏览器同源策略的限制,可以拿到从其他源传输过来的数据,需要服务端支持。
优缺点:
兼容性比较好,可用于解决主流浏览器的跨域数据访问的问题。缺点就是仅支持get请求,具有局限性,不安全,可能会受到xss攻击。
思路:
声明一个回调函数,其函数名(如show)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)。创建一个<script>标签,把那个跨域的api数据接口地址,赋值给script的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=show)。服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是show,它准备好的数据是show('南玖')。最后服务器把准备的数据通过http协议返回给客户端,客户端再调用执行之前声明的回调函数(show),对返回的数据进行操作。// frontfunction jsonp({ url, params, callback }) { return new promise((resolve, reject) => { let script = document.createelement('script') window[callback] = function(data) { resolve(data) document.body.removechild(script) } params = { ...params, callback } // wd=b&callback=show let arrs = [] for (let key in params) { arrs.push(`${key}=${params[key]}`) } script.src = `${url}?${arrs.join('&')}` document.body.appendchild(script) })}jsonp({ url: 'http://localhost:3000/say', params: { wd: 'wxgongzhonghao' }, callback: 'show'}).then(data => { console.log(data)})
// server 借助express框架let express = require('express')let app = express()app.get('/say', function(req, res) { let { wd, callback } = req.query console.log(wd) // iloveyou console.log(callback) // show res.end(`${callback}('关注前端南玖')`)})app.listen(3000)
上面这段代码相当于向http://localhost:3000/say?wd=wxgongzhonghao&callback=show这个地址请求数据,然后后台返回show('关注前端南玖'),最后会运行show()这个函数,打印出'关注前端南玖'
跨域资源共享(cors)
cors(cross-origin resource sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。cors背后的基本思想是使用自定义的http头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。
cors 需要浏览器和后端同时支持。ie 8 和 9 需要通过 xdomainrequest 来实现。
浏览器会自动进行 cors 通信,实现 cors 通信的关键是后端。只要后端实现了 cors,就实现了跨域。
服务端设置 access-control-allow-origin 就可以开启 cors。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
虽然设置 cors 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
简单请求: (满足以下两个条件,就是简单请求)
1.请求方法为以下三个之一:
getposthead2.content-type的为以下三个之一:
text-plainmultiparty/form-dataapplication/x-www-form-urlencoded复杂请求:
不是简单请求那它肯定就是复杂请求了。复杂请求的cors请求,会在正式发起请求前,增加一次http查询请求,称为预检 请求,该请求是option方法的,通过该请求来知道服务端是否允许该跨域请求。
nginx反向代理nginx 反向代理的原理很简单,即所有客户端的请求都必须经过nginx处理,nginx作为代理服务器再将请求转发给后端,这样就规避了浏览器的同源策略。
14.说说什么是xss攻击什么是xss?
xss 全称是 cross site scripting,为了与css区分开来,所以简称xss,中文叫作跨站脚本
xss是指黑客往页面中注入恶意脚本,从而在用户浏览页面时利用恶意脚本对用户实施攻击的一种手段。
xss能够做什么?
窃取cookie监听用户行为,比如输入账号密码后之间发给黑客服务器在网页中生成浮窗广告修改dom伪造登入表单xss实现方式
存储型xss攻击反射型xss攻击基于dom的xss攻击如何阻止xss攻击?
对输入脚本进行过滤或转码
对用户输入的信息过滤或者转码,保证用户输入的内容不能在html解析的时候执行。
利用csp
该安全策略的实现基于一个称作 content-security-policy的http首部。(浏览器内容安全策略)它的核心思想就是服务器决定浏览器加载那些资源。
限制加载其他域下的资源文件,这样即使黑客插入了一个 javascript 文件,这个 javascript 文件也是无法被加载的;禁止向第三方域提交数据,这样用户数据也不会外泄;提供上报机制,能帮助我们及时发现 xss 攻击。禁止执行内联脚本和未授权的脚本;利用 httponly
由于很多 xss 攻击都是来盗用 cookie 的,因此还可以通过使用 httponly 属性来保护我们 cookie 的安全。这样子的话,javascript 便无法读取 cookie 的值。这样也能很好的防范 xss 攻击。
通常服务器可以将某些 cookie 设置为 httponly 标志,httponly 是服务器通过 http 响应头来设置的,下面是打开 google 时,http 响应头中的一段:
set-cookie: nid=189=m8l6-z41asxtm2uewcoc5oh9djkffomhwqqrlnctoi; expires=sat, 18-apr-2020 06:52:22 gmt; path=/; domain=.google.com; httponly
对于不受信任的输入,可以限制输入长度
15.说说什么是csrf攻击?什么是csrf攻击?
csrf 全称 cross-site request forgery,中文为跨站请求伪造 ,攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。 csrf攻击就是黑客利用用户的登录状态,并通过第三方站点来干一些嘿嘿嘿的坏事。
几种常见的攻击类型
1.get类型的csrf
get类型的csrf非常简单,通常只需要一个http请求:
<img src="http://bank.example/withdraw?amount=10000&for=hacker" >
在受害者访问含有这个img的页面后,浏览器会自动向http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker发出一次http请求。bank.example就会收到包含受害者登录信息的一次跨域请求。
2.post类型的csrf
这种类型的csrf利用起来通常使用的是一个自动提交的表单,如:
<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>
访问该页面后,表单会自动提交,相当于模拟用户完成了一次post操作。
3.链接类型的csrf
链接类型的csrf并不常见,比起其他两种用户打开页面就中招的情况,这种需要用户点击链接才会触发。这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式诱导用户中招,攻击者通常会以比较夸张的词语诱骗用户点击,例如:
<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank"> 重磅消息!! <a/>
由于之前用户登录了信任的网站a,并且保存登录状态,只要用户主动访问上面的这个php页面,则表示攻击成功。
csrf的特点
攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生。攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据。整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”。跨站请求可以用各种方式:图片url、超链接、cors、form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪。csrf通常是跨域的,因为外域通常更容易被攻击者掌控。但是如果本域下有容易被利用的功能,比如可以发图和链接的论坛和评论区,攻击可以直接在本域下进行,而且这种攻击更加危险。
防护策略
黑客只能借助受害者的cookie 骗取服务器的信任,但是黑客并不能凭借拿到**「cookie」**,也看不到 **「cookie」的内容。另外,对于服务器返回的结果,由于浏览器「同源策略」**的限制,黑客也无法进行解析。
这就告诉我们,我们要保护的对象是那些可以直接产生数据改变的服务,而对于读取数据的服务,则不需要进行csrf的保护。而保护的关键,是 「在请求中放入黑客所不能伪造的信息」
同源检测
既然csrf大多来自第三方网站,那么我们就直接禁止外域(或者不受信任的域名)对我们发起请求。
那么问题来了,我们如何判断请求是否来自外域呢?
在http协议中,每一个异步请求都会携带两个header,用于标记来源域名:
origin headerreferer header这两个header在浏览器发起请求时,大多数情况会自动带上,并且不能由前端自定义内容。 服务器可以通过解析这两个header中的域名,确定请求的来源域。
使用origin header确定来源域名
在部分与csrf有关的请求中,请求的header中会携带origin字段。字段内包含请求的域名(不包含path及query)。
如果origin存在,那么直接使用origin中的字段确认来源域名就可以。
但是origin在以下两种情况下并不存在:
ie11同源策略: ie 11 不会在跨站cors请求上添加origin标头,referer头将仍然是唯一的标识。最根本原因是因为ie 11对同源的定义和其他浏览器有不同,有两个主要的区别,可以参考mdn same-origin_policy#ie_exceptions302重定向: 在302重定向之后origin不包含在重定向的请求中,因为origin可能会被认为是其他来源的敏感信息。对于302重定向的情况来说都是定向到新的服务器上的url,因此浏览器不想将origin泄漏到新的服务器上。使用referer header确定来源域名
根据http协议,在http头中有一个字段叫referer,记录了该http请求的来源地址。 对于ajax请求,图片和script等资源请求,referer为发起请求的页面地址。对于页面跳转,referer为打开页面历史记录的前一个页面地址。因此我们使用referer中链接的origin部分可以得知请求的来源域名。
这种方法并非万无一失,referer的值是由浏览器提供的,虽然http协议上有明确的要求,但是每个浏览器对于referer的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。使用验证 referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不是很安全。在部分情况下,攻击者可以隐藏,甚至修改自己请求的referer。
2014年,w3c的web应用安全工作组发布了referrer policy草案,对浏览器该如何发送referer做了详细的规定。截止现在新版浏览器大部分已经支持了这份草案,我们终于可以灵活地控制自己网站的referer策略了。新版的referrer policy规定了五种referer策略:no referrer、no referrer when downgrade、origin only、origin when cross-origin、和 unsafe url。之前就存在的三种策略:never、default和always,在新标准里换了个名称。他们的对应关系如下:
策略名称属性值(新)属性值(旧)
no referrer no-referrer never
no referrer when downgrade no-referrer-when-downgrade default
origin only (same or strict) origin origin
origin when cross origin (strict) origin-when-crossorigin -
unsafe url unsafe-url always
根据上面的表格因此需要把referrer policy的策略设置成same-origin,对于同源的链接和引用,会发送referer,referer值为host不带path;跨域访问则不携带referer。例如:aaa.com引用bbb.com的资源,不会发送referer。
设置referrer policy的方法有三种:
在csp设置
页面头部增加meta标签
a标签增加referrerpolicy属性
上面说的这些比较多,但我们可以知道一个问题:攻击者可以在自己的请求中隐藏referer。如果攻击者将自己的请求这样填写:
<img src="http://bank.example/withdraw?amount=10000&for=hacker" referrerpolicy="no-referrer">
那么这个请求发起的攻击将不携带referer。
另外在以下情况下referer没有或者不可信:
1.ie6、7下使用window.location.href=url进行界面的跳转,会丢失referer。
2.ie6、7下使用window.open,也会缺失referer。
3.https页面跳转到http页面,所有浏览器referer都丢失。
4.点击flash上到达另外一个网站的时候,referer的情况就比较杂乱,不太可信。
无法确认来源域名情况
当origin和referer头文件不存在时该怎么办?如果origin和referer都不存在,建议直接进行阻止,特别是如果您没有使用随机csrf token(参考下方)作为第二次检查。
如何阻止外域请求
通过header的验证,我们可以知道发起请求的来源域名,这些来源域名可能是网站本域,或者子域名,或者有授权的第三方域名,又或者来自不可信的未知域名。
我们已经知道了请求域名是否是来自不可信的域名,我们直接阻止掉这些的请求,就能防御csrf攻击了吗?
且慢!当一个请求是页面请求(比如网站的主页),而来源是搜索引擎的链接(例如百度的搜索结果),也会被当成疑似csrf攻击。所以在判断的时候需要过滤掉页面请求情况,通常header符合以下情况:
accept: text/htmlmethod: get
但相应的,页面请求就暴露在了csrf的攻击范围之中。如果你的网站中,在页面的get请求中对当前用户做了什么操作的话,防范就失效了。
例如,下面的页面请求:
get https://example.com/addcomment?comment=xxx&dest=orderid
注:这种严格来说并不一定存在csrf攻击的风险,但仍然有很多网站经常把主文档get请求挂上参数来实现产品功能,但是这样做对于自身来说是存在安全风险的。
另外,前面说过,csrf大多数情况下来自第三方域名,但并不能排除本域发起。如果攻击者有权限在本域发布评论(含链接、图片等,统称ugc),那么它可以直接在本域发起攻击,这种情况下同源策略无法达到防护的作用。
综上所述:同源验证是一个相对简单的防范方法,能够防范绝大多数的csrf攻击。但这并不是万无一失的,对于安全性要求较高,或者有较多用户输入内容的网站,我们就要对关键的接口做额外的防护措施。
csrf token
前面讲到csrf的另一个特征是,攻击者无法直接窃取到用户的信息(cookie,header,网站内容等),仅仅是冒用cookie中的信息。
而csrf攻击之所以能够成功,是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么我们可以要求所有的用户请求都携带一个csrf攻击者无法获取到的token。服务器通过校验请求是否携带正确的token,来把正常的请求和攻击的请求区分开,也可以防范csrf的攻击。
利用cookie的samesite属性
可以看看mdn对此的解释:
https://developer.mozilla.org/zh-cn/docs/web/http/headers/set-cookie/samesite
samesite可以设置为三个值,strict、lax和none。
在strict模式下,浏览器完全禁止第三方请求携带cookie。比如请求sanyuan.com网站只能在sanyuan.com域名当中请求才能携带 cookie,在其他网站请求都不能。
在lax模式,就宽松一点了,但是只能在 get 方法提交表单况或者a 标签发送 get 请求的情况下可以携带 cookie,其他情况均不能。
在none模式下,cookie将在所有上下文中发送,即允许跨域发送。
更多编程相关知识,请访问:编程入门!!
以上就是看看这些关于浏览器的面试题,你能答对几个?的详细内容。
