整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Google Chrome 77引入新的网站隔离安全特性

着 Chrome 77 的发布,谷歌也为 Android 和桌面版本引入了全新的站点隔离安全特性。此前,当熔毁(Meltdown)和幽灵(Spectre)漏洞被披露的时候,谷歌就快速推出了这项安全特性,以便 Chrome 67 用户启用。开启之后,Chrome 浏览器将把用户访问的每个网站,都加载到单独的沙盒进程中,以限制其对资源和功能的访问。

(题图 via Bleeping Computer)

通过这种方式,能够有效地防止恶意网站利用推测执行攻击漏洞,来访问加载在其它浏览器选项卡中的数据。

然而启用站点隔离的代价,就是耗费更多的进程和内存资源,导致某些网站的内存用量爆炸。但在安全性和资源利用率之间,总要作出一定的权衡。

随着 Android 版 CChrome 77 的发布,谷歌决定进一步增强对移动用户的防护,所以引入了与桌面版本略有不同的网站隔离措施。

据悉,Android 版“网站隔离”特性,仅会保护用户通过密码登陆的站点,以减少移动设备的资源占用率,毕竟其处理器和内存性能都低于台式机计算机。

谷歌表示,目前已有 99% 运行 Android 且内存超过 2GB 的用户启用了此功能,且有 1% 用户保留了监测功能以提升性能。在未来,谷歌还考虑向更多设备提供支持。

对于想要提供完整站点隔离保护功能的用户,可在地址栏输入 chrome:// flags/#enable-site-per-process 并跳转,然后启用这个标记。

至于台式机用户,目前 Chrome 77 的“站点隔离”功能亦可保护用户免受渲染器进程的影响。这些进程负责各个标签页中发生的事情,例如将 HTML、CSS 和 JavaScript 代码转义为网页并显示。

一些攻击者试图对其它标签页中的网页代码展开攻击,但新加入的隔离措施,可有效防止此类恶意活动的发生。

者:HelloGitHub-追梦人物

文中涉及的示例代码,已同步更新到HelloGitHub-Team 仓库[1]

上一篇中我们使用了 Markdown 来为文章提供排版支持。Markdown 在解析内容的同时还可以自动提取整个内容的目录结构,现在我们来使用 Markdown 为文章自动生成目录。

在文中插入目录

先来回顾一下博客的 Post(文章)模型,其中 body 是我们存储 Markdown 文本的字段:

blog/models.py


from django.db import models


class Post(models.Model):
	# Other fields ...
	body = models.TextField()

再来回顾一下文章详情页的视图,我们在 detail 视图函数中将 postbody 字段中的 Markdown 文本解析成了 HTML 文本,然后传递给模板显示。

blog/views.py

def detail(request, pk):
	post = get_object_or_404(Post, pk=pk)
	post.body = markdown.markdown(post.body,
 				extensions=[
 					'markdown.extensions.extra',
 		 			'markdown.extensions.codehilite',
 					'markdown.extensions.toc',
 				])
	return render(request, 'blog/detail.html', context={'post': post})

markdown.markdown() 方法把 post.body 中的 Markdown 文本解析成了 HTML 文本。同时我们还给该方法提供了一个 extensions 的额外参数。其中 markdown.extensions.toc 就是自动生成目录的拓展(这里可以看出我们有先见之明,如果你之前没有添加的话记得现在添加进去)。

在渲染 Markdown 文本时加入了 toc 拓展后,就可以在文中插入目录了。方法是在书写 Markdown 文本时,在你想生成目录的地方插入 [TOC] 标记即可。例如新写一篇 Markdown 博文,其 Markdown 文本内容如下:

[TOC]

## 我是标题一

这是标题一下的正文

## 我是标题二

这是标题二下的正文

### 我是标题二下的子标题
这是标题二下的子标题的正文

## 我是标题三
这是标题三下的正文

其最终解析后的效果就是:

原本 [TOC] 标记的地方被内容的目录替换了。

在页面的任何地方插入目录

上述方式的一个局限性就是只能通过 [TOC] 标记在文章内容中插入目录。如果我想在页面的其它地方,比如侧边栏插入一个目录该怎么做呢?方法其实也很简单,只需要稍微改动一下解析 Markdown 文本内容的方式即可,具体代码就像这样:

blog/views.py

def detail(request, pk):
	post = get_object_or_404(Post, pk=pk)
	md = markdown.Markdown(extensions=[
		 'markdown.extensions.extra',
		 'markdown.extensions.codehilite',
		 'markdown.extensions.toc',
	])
	post.body = md.convert(post.body)
	post.toc = md.toc

	return render(request, 'blog/detail.html', context={'post': post})

和之前的代码不同,我们没有直接用 markdown.markdown() 方法来渲染 post.body 中的内容,而是先实例化了一个 markdown.Markdown 对象 md,和 markdown.markdown() 方法一样,也传入了 extensions 参数。接着我们便使用该实例的 convert 方法将 post.body 中的 Markdown 文本解析成 HTML 文本。而一旦调用该方法后,实例 md 就会多出一个 toc 属性,这个属性的值就是内容的目录,我们把 md.toc 的值赋给 post.toc 属性(要注意这个 post 实例本身是没有 toc 属性的,我们给它动态添加了 toc 属性,这就是 Python 动态语言的好处)。

接下来就在博客文章详情页的文章目录侧边栏渲染文章的目录吧!删掉占位用的目录内容,替换成如下代码:

{% block toc %}
	<div class="widget widget-content">
		<h3 class="widget-title">文章目录</h3>
 	{{ post.toc|safe }}
 	</div>
{% endblock toc %}

即使用模板变量标签 {{ post.toc }} 显示模板变量的值,注意 post.toc 实际是一段 HTML 代码,我们知道 django 会对模板中的 HTML 代码进行转义,所以要使用 safe 标签防止 django 对其转义。其最终渲染后的效果就是:

处理空目录

现在目录已经可以完美生成了,不过还有一个异常情况,当文章没有任何标题元素时,Markdown 就提取不出目录结构,post.toc 就是一个空的 div 标签,如下:

<div class="toc">
 	<ul></ul>
</div>

对于这种没有目录结构的文章,在侧边栏显示一个目录是没有意义的,所以我们希望只有在文章存在目录结构时,才显示侧边栏的目录。那么应该怎么做呢?

分析 toc 的内容,如果有目录结构,ul 标签中就有值,否则就没有值。我们可以使用正则表达式来测试 ul 标签中是否包裹有元素来确定是否存在目录。

def detail(request, pk):
 	post = get_object_or_404(Post, pk=pk)
 	md = markdown.Markdown(extensions=[
 	'markdown.extensions.extra',
 	 	'markdown.extensions.codehilite',
 	'markdown.extensions.toc',
 	])
 	post.body = md.convert(post.body)
 
 	m = re.search(r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>', md.toc, re.S)
 	post.toc = m.group(1) if m is not None else ''
 
 	return render(request, 'blog/detail.html', context={'post': post})

这里我们正则表达式去匹配生成的目录中包裹在 ul 标签中的内容,如果不为空,说明目录,就把 ul 标签中的值提取出来(目的是只要包含目录内容的最核心部分,多余的 HTML 标签结构丢掉)赋值给 post.toc;否则,将 post 的 toc 置为空字符串,然后我们就可以在模板中通过判断 post.toc 是否为空,来决定是否显示侧栏目录:

{% block toc %}
 	{% if post.toc %}
 	 <div class="widget widget-content">
 		<h3 class="widget-title">文章目录</h3>
 		<div class="toc">
 		<ul>
 			{{ post.toc|safe }}
 		</ul>
 		</div>
 	 </div>
 	{% endif %}
{% endblock toc %}

这里我们看到了一个新的模板标签 {% if %},这个标签用来做条件判断,和 Python 中的 if 条件判断是类似的。

美化标题的锚点 URL

文章内容的标题被设置了锚点,点击目录中的某个标题,页面就会跳到该文章内容中标题所在的位置,这时候浏览器的 URL 显示的值可能不太美观,比如像下面的样子:

http://127.0.0.1:8000/posts/8/#_1

http://127.0.0.1:8000/posts/8/#_3

#_1 就是锚点,Markdown 在设置锚点时利用的是标题的值,由于通常我们的标题都是中文,Markdown 没法处理,所以它就忽略的标题的值,而是简单地在后面加了个 \_1 这样的锚点值。为了解决这一个问题,需要修改一下传给 extentions 的参数,其具体做法如下:

blog/views.py

from django.utils.text import slugify
from markdown.extensions.toc import TocExtension

def detail(request, pk):
 	post = get_object_or_404(Post, pk=pk)
 	md = markdown.Markdown(extensions=[
 	 'markdown.extensions.extra',
 	 'markdown.extensions.codehilite',
 	 # 记得在顶部引入 TocExtension 和 slugify
 	 TocExtension(slugify=slugify),
 	])
 	post.body = md.convert(post.body)
 
 	m = re.search(r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>', md.toc, re.S)
 	post.toc = m.group(1) if m is not None else ''
 
 	return render(request, 'blog/detail.html', context={'post': post})

和之前不同的是,extensions 中的 toc 拓展不再是字符串 markdown.extensions.toc ,而是 TocExtension 的实例。TocExtension 在实例化时其 slugify 参数可以接受一个函数,这个函数将被用于处理标题的锚点值。Markdown 内置的处理方法不能处理中文标题,所以我们使用了 django.utils.text 中的 slugify 方法,该方法可以很好地处理中文。

这时候标题的锚点 URL 变得好看多了。

http://127.0.0.1:8000/posts/8/#我是标题一

http://127.0.0.1:8000/posts/8/#我是标题二下的子标题

References

[1] HelloGitHub-Team 仓库: https://github.com/HelloGitHub-Team/HelloDjango-blog-tutorial

欢迎关注 HelloGitHub 公众号,获取更多开源项目的资料和内容

『讲解开源项目系列』启动——让对开源项目感兴趣的人不再畏惧、让开源项目的发起者不再孤单。跟着我们的文章,你会发现编程的乐趣、使用和发现参与开源项目如此简单。欢迎联系我们给我们投稿,让更多人爱上开源、贡献开源~

. 安全

1.1. 关乎人类心理学

1.1.1. 接受开发者有着人类的弱点,主要的弱点就是对概率的错误估计

1.2. 安全从来不只跟软件和信息有关,也跟人和环境有关

1.2.1. 有不计其数的公司让它们的数据库在互联网上没有密码就可以被访问

1.3. 安全漏洞本身总是被叫作事故(incident),绝不是不负责任的

1.4. 安全,就像测试一样,是你的服务、数据和业务的可靠性的一个子集

1.5. 应当将与安全有关的决定看作可靠性技术债,它能帮你优化整个人生

1.6. 安全问题的不可避免也强调了事无绝对,没有绝对安全的系统

1.7. 完美的安全是不可能实现的,你总会遇到用户体验和安全之间的权衡

2. 复盘报告

2.1. postmortem blog post

2.2. 通常是在一次非常让人尴尬的安全事件发生之后写的,目的是清晰地、极尽细节地向管理层描述事件始末

2.2.1. 实际上是为了掩盖他们搞砸了的事实

3. 负责任的披露

3.1. responsible disclosure

3.2. 指的是那些没有在快速识别问题方面投入较多资源的公司,在利用充足的时间来修复问题后,再向公众公布修复安全漏洞的做法

3.3. 负责任的披露从一开始就应该被称为限时披露

4. 黑客之外

4.1. 安全可能还与那些你认为不相关的因素有关

4.2. “不要在星期五进行部署”

4.2.1. 这句话的逻辑很简单,即如果你把事情搞砸了,周末不会有人来处理,所以你应该在一周的第一天来进行一些高危操作

4.2.2. 周末存在的本身不是一个安全漏洞,但它仍然可能导致灾难性的结果

4.3. Facebook给开发者提供了一个API,让他们可以浏览用户的好友列表

4.3.1. 2016年,一个公司通过这些信息生成用户的政治倾向图,然后通过精准投放广告影响大选

4.3.2. 这个功能是完全按照需求来设计的,没有错误,没有安全漏洞,没有后门,也没有黑客介入

4.3.3. 某些人创造了这个功能,另一些人使用它,但是获得的数据却能违背他人意愿而操控他们,并因此导致伤害

5. 威胁模型

5.1. 对脑海里或者纸面上的威胁模型确定需要设置的安全措施的优先次序,并找出其中的漏洞

5.2. 最常见的威胁模型之一可能是用“反正我也没什么值得看的东西!”的想法来应对黑客攻击、平台管控,或者心怀恶意的前合作伙伴

5.3. 我们并不真正关心数据是否被泄露和滥用,这种情况产生的原因主要是我们缺乏想象力来思考数据的用途

5.4. 隐私就像安全带:你在大多数时间里不需要它,但当你需要它的时候,它可以救你的命

5.5. 实际的威胁建模涉及分析行为者(analyzing actor)、数据流(data flow)和信任边界(trust boundary)

5.6. 袖珍威胁模型

5.6.1. 你的应用程序的资产

5.6.1.1. 任何你不想丢失或泄露的东西都是资产,包括你的源代码、设计文档、数据库、私钥、API令牌、服务器配置,还有Netflix观看清单

5.6.2. 资产所处的服务器

5.6.2.1. 每台服务器都会被一些人访问,而每台服务器都会访问其他一些服务器

5.6.3. 信息敏感性

5.6.4. 访问资源的路径

5.7. 你服务器上的所有第三方组件都经过了数百万次的测试、错误修复和安全审计

6. 编写安全的网络应用程序

6.1. 在设计时考虑到安全问题

6.1.1. 安全是很难后续改造的,主要是因为所有的设计导致你一开始就写了不安全的代码

6.1.1.1. 设计时首先要考虑到安全问题,因为在既有基础上去改造安全措施是很难的

6.1.2. 审查你纸面的或脑海里的威胁模型

6.1.2.1. 了解风险,以及现在使之安全的成本和以后使之安全的成本

6.1.3. 决定你将把应用程序的秘密(数据库密码、API密钥)存储在哪里

6.1.3.1. 让它成为一个铁打不动的原则

6.1.4. 设计最少的权限

6.1.4.1. 代码不应该得到除它必须用到的之外的权限

6.1.4.2. 如果只有少数任务需要更高的权限,可以考虑将它们分解成单独的、隔离的实体

6.1.4.3. 尽可能在最低权限的账户下运行网页应用程序

6.1.5. 将安全原则应用于你的整个组织

6.1.5.1. 员工不应该访问他们执行日常任务时不需要的资源

6.1.5.2. CEO根本就不该有访问数据库或服务器的权限

6.1.5.3. 没有人可以被信任,而是因为他们的访问权可能会被外部人员恶意取得

6.2. 隐蔽性安全的用处

6.2.1. 软件安全是一场与时间的竞赛

6.2.1.1. 所有安全措施的唯一目的是为你赢得时间,让攻击者做无用功

6.2.2. 信息安全专家厌恶隐蔽性安全

6.2.2.1. 它不能为你赢得时间,或者也许它可以,但只是杯水车薪

6.2.3. 隐蔽性并不会给你带来真正的安全,但它偶尔会给你争取补救时间,直到你把问题解决掉

6.2.4. 边际安全并不是真正的安全

6.2.5. 当你在安全这件事上努力的程度是大是小都没什么区别而且风险很大时,更推荐你采用真正的安全而不是隐蔽性安全

6.2.6. 隐蔽性安全并不是真正的安全,但它可能是真正的损害。你得权衡利弊

6.3. 不要光靠你自己去实现安全

6.3.1. 你不应该编写一种仅属于自己的安全机制,无论它是哈希、加密还是节流

6.3.2. 一个普通的开发者在实现自己软件的安全时可能会错过关键的细节,基本上造成的结果就是毫不安全(zero security)

6.4. SQL注入攻击

6.4.1. 解决SQL注入问题的最安全方法是使用参数化查询(parameterized query)

6.4.1.1. 参数化查询并不是灵丹妙药

6.4.1.2. 有些数据库的抽象似乎不支持常见的参数化查询,这些数据库的抽象有其他的方法来进行参数化查询

6.4.1.3. 不要力求全部查询都实现参数化

6.4.2. 使用参数化查询的另一个好处是可以减少查询计划缓存(query plan cache)污染

6.4.3. 因为查询计划缓存的容量是有限的,如果你用大量的不同用户名运行这个查询,其他有用的查询计划条目将从缓存中被挤出,而缓存将被这些可能无用的条目填满,这就叫作查询计划缓存污染

6.5. 备份

6.5.1. 回退是最糟糕的错误类型,会浪费我们的时间

6.5.2. "3-2-1备份规则”(3-2-1 backup rule)

6.5.2.1. 有三个独立的备份,两个在独立的媒介上,一个在独立的地点

6.6. 跨站脚本攻击

6.6.1. 跨站脚本(cross-site scripting)攻击应该被叫作“JavaScript注入攻击”

6.6.2. 第一阶段是将JavaScript的代码插入网页当中

6.6.3. 第二阶段就是通过网络传输更多的JavaScript代码,并在网页上执行

6.6.4. 通过从其他会话中窃取会话cookie来捕获这些会话,这个操作叫作会话劫持(session hijacking)

6.6.5. 导致XSS攻击出现的原因往往是存在问题的HTML代码

6.6.6. 抵御XSS攻击的最简单方法是对文本进行重编码,使特殊的HTML字符被转义

6.6.7. CSP是应对XSS攻击的另一个手段

6.6.7.1. CSP(content security policy,内容安全策略)

6.6.7.2. 它是一个HTTP头,限制了可以从第三方服务器请求的资源

6.6.7.3. 维护一个可信域列表并且使它保持最新状态是一件很费力、费时的事情

6.6.7.4. 无论你是否打算使用CSP,都应该注意正确编码HTML输出

6.6.8. 只要不忽略注入HTML代码和完全不进行编码等问题,那么避免XSS攻击还是很容易的

6.7. 跨站请求伪造

6.7.1. 在HTTP中,修改网络内容的操作是用POST,而不是用GET来实现的,这是有原因的

6.7.1.1. 你没办法生成指向POST地址的可单击链接

6.7.1.2. 它只能被POST一次,如果操作失败,浏览器会警告你是否需要再次提交

6.7.1.3. POST的这种性质使我们对它过于信任

6.7.1.4. POST的隐患是,原始表单不一定要和POST请求所在的域相同

6.7.1.4.1. 要避免这种问题的产生,可以对每个生成的form使用一个随机生成的数字,这个数字会被复制在form本身和网站响应标题上

6.7.1.4.2. 你需要确保它在服务器端也得到了验证

6.8. 永远不要相信用户的输入