HTTP、会话管理、同源策略
引言
本节主要讲述了被动攻击,以及浏览器用来防御被动攻击的同源策略。
# 1. HTTP 与会话管理
# 1.1 HTTP
略过一些基础知识。
# 🖊 百分号编码
百分号编码(Percent-Encoding)也被称为 URL 编码。中文和特殊符号等不能直接用于 URL,如果要将它们用在 URL 上的话就需要经过百分号编码。百分号编码是将字符以字节为单位转换成 %xx
的形式,其中 xx 是该字节的十六进制表示。
# 🖊 Referer
HTTP 请求消息中有时含有 Referer 头信息,它能告诉我们当前请求是从哪个页面链接过来的,值就是那个页面的 URL。除了通过 form 元素发送的请求,a 元素生成的链接或 img 元素的图像等也会产生 Referer 头信息。
Referer 头信息有时是提升安全性的帮手,有时却能成为问题之源。
- Referer 有益的一面体现在,当我们为了确保安全性而主动检验 Referer 头信息时,通过查看 Referer,能够确认应用程序的跳转是否跟预期一样。但是,同其他头信息一样,Referer 也能由访问者本人通过 Fiddler 之类的工具修改,或者被浏览器插件和其他安全方面的软件修改或删除,所以未必会正确显示链接的来源。
- 当 URL 中包含敏感信息时,Referer 就可能会引发安全问题。比如:URL 中包含的会话 ID 通过 Referer 泄漏给外界,从而使自己的身份被他人恶意胃名顶替就是一个典型的案例。
URL 中包含重要信息时,就有被 Referer 头信息泄密的风险。
# 🖊 GET 与 POST 的使用区别
RFC 2616 记载了区别两者的注意点:
- GET 方法仅用于查询(获取资源)
- GET 方法被认为没有副作用
- GET 发送敏感数据时应使用 POST 方法(因为 URL 中的参数可能经由 Referer 泄露)
# 🖊 篡改 HTTP 信息
在 Fiddler 的 Rules 菜单中,选择 “Automatic Breakpoints” - “Before Request”,此时点击“注册”按钮,Fiddler 就能截取到浏览器的请求消息,此时还未发送给服务器,我们可以在 Fiddler 中编辑修改此请求。
所以说,浏览器发送的值都能够被变更。
# 1.2 无状态的 HTTP 认证
HTTP 支持认证功能,可以根据实现方式细分为 Basic 认证、NTLM 认证和 Digest 认证等。正如 HTTP 无状态,HTTP 认证也是无状态的。
# 1.2.1 Basic 认证
Basic 认证下,当浏览器请求一个需要认证的网页时,服务器会先向浏览器返回“401 Unauthorized(未认证)”状态码。浏览器收到此状态码后,会显示要求输入 ID 和密码的画面,然后再将输入的 ID 和密码添加到请求信息中,再次向服务器发送。
在认证失败时,Basic 认证的规定会输出以下头信息:
HTTP/1.1 401 Authorization Required
WWW-Authenticate: Basic realm="xxxxx"
2
在用户提交 ID 和密码时,HTTP 请求消息会附带如下 Authorization 信息:
Authorization: Basic dXNlcjE6cGFzczE=
Basic 后面的字符串内容是 ID 和密码以冒号相隔组成的字符串,再经过 Base64 编码后的结果。
实际上,之后的每次请求都会发送 ID 和密码,认证状态并没有被保存。因此 Basic 认证也是无状态的。
认证与授权
- 认证:通过一些方法手段来确认操作者确实是其本人;
- 授权:授予已经通过认证的用户一些权限。
# 1.3 Cookie 与会话管理
为了实现会话管理,HTTP 引入了名为 Cookie 的机制。Cookie 相当于服务器下达给浏览器的命令,让其记住发送给它的“名称=变量”这种格式的值。
Cookie 可以设置有效期限,没有设置有效期限的 Cookie 会在浏览器被关闭之前一直有效。
由于 Cookie 只能记忆少量数据,所以可以采用 Session 技术的会话管理,这时只在 Cookie 中保存类似于“受理编号”的会话 ID,实际对应的值则保存在服务器端。
这个会话 ID 很有讲究,如果采用连续的号,则会产生下面的情况:
😈 恶人:Hi
😊 柜员:您的受理编号为 006,请提供银行卡号和密码。
恶人将受理编号减去 1 变成 005,假设 005 的顾客已经通过了身份核实…
😈 恶人:受理编号 005,请向卡号 99999 转账 5 万元。
😊 柜员:转账完毕!
仅仅改变了受理编号,就能成功使用他人的账户进行转账。
由此可见,会话 ID 不能使用连续的数字,而应当使用位数足够长的随机数。会话 ID 需要满足如下需求:
- 会话 ID 不能被第三方推测
- 会话 ID 不能被第三方劫持
- 会话 ID 不能向第三方泄漏
面对需求 1,在实际开发中,会话 ID 不要自己去生成,而是使用开发工具提供生成的会话 ID。
面对需求 2 说的会话 ID 不能被劫持,我们来看看刚刚可能产生的安全问题:
😈 恶人:你好。
😊 柜员:您的受理编号为 9466ir8fgmmk1gn6raeo7ne71。请提供您的银行卡号和密码。
😈 恶人暂时离开柜台并等待来客。有顾客进入银行时,恶人冒充银行柜员向顾客搭话。
😈 恶人:您的受理编号为 9466ir8fgmmk1gn6raeo7ne71。
👦 顾客:知道了。
顾客走向柜台。
👦 顾客:我的受理编号为 9466ir8fgmmk1gn6raeo7ne71。
😊 柜员:请提供您的银行卡号和密码。
👦 顾客:受理编号为 9466ir8fgmmk1gn6raeo7ne71。卡号为 12345,密码为 9876,请确认。
😊 柜员:身份核实完毕。
顾客执行完身份确认后恶人也走向了柜台。
😈 恶人:受理编号为 9466ir8fgmmk1gn6raeo7ne71。请向卡号 99999 的账户转账 3 万元。
😊 柜员:转账完毕。
像这样,恶人(攻击者)劫持正规用户的会话 ID 来进行攻击的手法被称为会话固定攻击(Session Fixation Attack)。
我们可以尝试修复此安全隐患:
有顾客进入银行时,恶人冒充银行柜员向顾客搭话。
😈 恶人:您的受理编号为 9466ir8fgmmk1gn6raeo7ne71。
👦 顾客:知道了。
顾客走向柜台。
👦 顾客:我的受理编号为 9466ir8fgmmk1gn6raeo7ne71。
😊 柜员:请提供您的银行卡号和密码。
👦 顾客:受理编号为 9466ir8fgmmk1gn6raeo7ne71。卡号为 12345,密码为 9876,请确认。
😊 柜员:身份核实完毕。您新的受理编号为eut1j15a058pm8gapa871937h6。
顾客执行完本人身份确认后恶人也走向了柜台。
😈 恶人:受理编号为9466ir8fgmmk1gn6raeo7ne71。请向卡号99999的账户转账3万元。
😊 柜员:您还没有进行身份核实。请提供您的银行卡号和密码。
这里修复的要点是:认证后改变会话 ID。
继续看需求 3 的防止会话 ID 泄露。其泄露的原因有如下几种:
- 发行 Cookie 时的属性指定有问题(稍后讲解)
- 会话 ID 在网络上被监听
- 通过跨站脚本漏洞等应用中的安全隐患被泄漏
- 由于 PHP 或浏览器等平台存在安全隐患而被泄漏
- 会话 ID 保存在 URL 中的情况下,会通过 Referer 消息头泄漏
# 1.4 Cookie 属性
生成 Cookie 时可以设置很多属性。主要属性如下:
属性 | 含义 |
---|---|
Domain | Cookie 发送对象服务器的域名 |
Path | Cookie 发送对象 URL 的路径 |
Expires | Cookie 的有效期限,未指定则表示至浏览器关闭。 |
Secure | 仅在 SSL 加密的情况下可以发送 Cookie |
HttpOnly | 指定了此属性的 Cookie 不能被 JavaScript 访问 |
# 1.4.1 Domain 属性
Cookie 在默认情况下只能被发送到与其绑定的服务器,虽然这是安全的,但有时也需要能向多个服务器发送的 Cookie,这时就需要用到 Domain 属性。
如下图,当浏览器中的 Cookie 属性为 Domain=example.jp
时,上面两个服务器可以访问成功,但 a.example.com 会因为域名不用而没有发送。
假如 a.example.jp 的服务器在 Set-Cookie 中指定了 Domain=example.com
,此 Cookie 也会被浏览器忽略,因为 Cookie 中指定不同的域名可能遭受会话固定攻击,所以 Cookie 是不能指定不同域名的。
未指定 Domain 时,Cookie 只能被发送至生成它的服务器。在原则上,尽量不要设置 Cookie 的 Domain 属性。
# 1.4.2 Cookie 的 Secure 属性
未设置时 Cookie 则无关是否为 SSL 传输,都会被发送;而设置后仅在 SSL 传输的情况下才会被发送。
# 1.4.3 Cookie 的 HttpOnly 属性
设置了 HttpOnly 属性后,JavaScript 就不能访问该 Cookie 了。恶意使用 JawaScript 进行跨站脚本攻击从而取得 Cookie 信息,是窃取 Cookie 中会话 ID 的典型案例。而 Cookie 中设置了 HttpOnly 属性后,就能防止 JavaScript 窃取 Cookie 信息。
后面专门讲述跨站脚本攻击也会提到,其实设置了 HttpOnly 属性也无法彻底抵御跨站脚本攻击,但是能加大攻击的难度,而设置 HttpOnly 属性通常不会带来环处,所以应当时常给 Cookie 加上 HttpOnly 属性。
# 2. 被动攻击
针对 Web 应用程序的攻击可分为主动攻击(Active Attack)和被动攻击(Passive Attack)。
# 2.1 主动攻击和被动攻击的区别
- Active Attack 是指攻击者直接攻击 Web 服务器。SQL 注入攻击就是 Active Attack 的例子。
- Passive Attack 是指攻击者不直接攻击服务器,而是针对网站的用户设下陷阱,利用掉入陷阱的用户来攻击应用程序。
# 2.2 被动攻击的 3 种模式
我们由易到难依次解说被动攻击的 3 种模式。
# 2.2.1 单纯的被动攻击
将用户诱导至设有圈套的网站,就是一种单纯的被动攻击模式。
此类攻击的典型案例为:用户在浏览过所谓的“可疑网站”之后会感染上恶意软件(病毒等)。理论上如果浏览器(包括Adobe Flash Player 等插件)不存在漏洞,此类单纯的被动攻击是行不通的。但现实中,针对浏览器以及 Adobe Reader、Adobe Flash Player、JRE 等插件的漏洞进行的攻击却层出不穷。
# 2.2.2 恶意利用正规网站进行的被动攻击
下面介绍通过在正规网站设置陷阱来实施攻击,这也是屡见不鲜的一种模式。
参见上图,攻击者事先入侵正规网站,往其内容中嵌入恶意代码(①)。网站用户在浏览了含有恶意代码的内容后(②~③),就会感染病毒(④)。在这一流程中,单看步骤 ① 的话似乎应归类为主动攻击,但步骤 ②~④ 均为被动攻击,因此,可将 ① 视作被动攻击的前期准备。
在正规网站中设置陷阱的手法通常有下列 4 种:
- 非法获取 FTP 等服务器的密码后篡改网站内容
- 通过攻击 Web 服务器的安全隐患来篡改网站内容
- 通过 SQL 注入攻击来集改网站内容
- 在社交网络这类用户能够自己发布内容的网站上,利用跨站脚本漏洞实施攻击
# 2.2.3 跨站被动攻击
看一下一种同时使用恶意网站和正规网站的被动攻击模式。
根据上图来看一下跨站被动攻击的具体流程:
- 用户浏览恶意网站
- 从恶意网站下载含有恶意代码的 HTML
- HTML 中的恶意代码被触发,从而向正规网站发送攻击请求
- 正规网站返回含有 JavaScript 等的响应内容
有些情况下,步骤 4 会被省略。
此类攻击的特征为恶意利用已经在正规网站登录的用户账号来实施攻击。由于步骤 3 的请求中要向正规网站发送会话Cookie,因此,如果用户已经在正规网站登录,就会利用其已经登录的状态实施攻击。
此类攻击模式的典型案例包括,在步骤 3 的请求中对Web应用发动攻击的跨站请求伪造(CSRF),以及在步骤 4 的响应中利用浏览器来执行攻击的跨站脚本攻击(XSS)和 HTTP 消息头注入攻击。
# 3. 同源策略(Same Origin Policy)
针对以上被动攻击,浏览器和网站都需要采取相应的防御措施。本节先关注浏览器的安全功能。
# 3.1 sandbox
浏览器能够在用户浏览网站的同时运行一些程序,比如 JavaScript、Java Applet、Adobe Flash Player、ActiveX 等。而为了防止恶意程序在用户的浏览器上运行,JavaScript 等语言提供了一些增强安全性的机能。基本思想有如下两种:
- 只有在用户确认了程序的发行方并且允许运行的情况下,程序才能被运行
- 提供限制程序权限的 sandbox 环境
第一种方式经常被用于 ActiveX 或带有签名的 Applet,但如果一般的应用程序都采用这种方式的话,对用户来说就显得负担过大,因此现在这种方式主要用于为浏览器提供插件功能。
Sandbox 是 JavaScript、Java Applet、Adobe Flash Player 等经常使用的一种思路。在 sandbox 里面,程序的权限受到制约,即使编写了恶意程序也无法对用户造成伤害。就像孩子们能在沙地中尽情地喧闹而不会给外界带来困扰一样,由此便使用了英语“sandbox'”一词。
通常情况下,sandbox 限制了以下功能:
- 禁止访问本地文件
- 禁止使用打印机等资源(可以显示页面)
- 限制网络访问(同源策略)
虽然网络访问无法被完全禁止,但却受到了严格的限制,此限制就被称为同源策略。下面看一下 JS 中的同源策略。
# 3.2 同源策略
同源策略(Same Origin Policy)是禁止 JavaScript 进行跨站访问的安全策略。它也是浏览器的 sandbox 环境所提供的一项制约。
浏览器可以同时处理多个网站的内容,其典型方法为使用标签页或 frame 等。下面、我们以 iframe 为例来说明同源策略的必要性。
# 3.2.1 JavaScript 访问 iframe 的试验
让我们通过观察 JavaScript 对 iframe 的访问限制来体验 Same Origin Policy。首先有一点,如果主机相同,在 iframe 外部就能够通过 JavaScript 取得 iframe 内部的 HTML 内容。
下面是包含 iframe 要素的“外层 HTML”:
<html>
<head><title>跨 frame 的读取试验</title></head>
<body>
<iframe name="iframe1" width="300" height="80" src="http://example.jp/32/32-002.html">
</iframe><br>
<input type="button" onclick="go()" value="密码→">
<script>
function go() {
try {
var x = iframe1.document.form1.passwd.value;
document.getElementById('out').innerHTML = x;
} catch (e) {
alert(e.message);
}
}
</script>
<span id="out"></span>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 变量
x
取得了 iframe 的内容 - line 11 在 DOM 中显示读取出的字符串。
下面是显示在 iframe 中的“内层” HTML:
<body>
<form name="form1">
iframe 的内层<br>
密码<input type="text" name="passwd" value="password1">
</form>
</body>
2
3
4
5
6
运行后我们点击“密码→”按钮后,iframe 内部的文本框中的文字出现在按钮右侧。由此证实了 JavaScript 能够取得 iframe 内部的支持。
# 3.2.2 iframe 被恶用的可能性
iframe 内部的信息能被 JavaScript 读取,这样会不会有安全性问题呢?现在假设你是被动攻击的受害者。在 example.jp
登录以后,浏览了恶意网站 tap.example.com
。恶意网站使用 iframe 来显示 example.jp
的内容,由于你已经登录了 example.jp
,所以 iframe 内会显示你的个人信息,但这些信息只有你自己看到,所以显示在浏览器上本身不存在问题。
但是,假如恶意网站能用 JavaScript 访问 iframe 内部信息的话就存在问题了。因为你的个人信息会被恶意网站上的脚本发送给它的服务器。
如果我们真的按上面所说的那么做了,页面会提示“拒绝访问”。这是因为虽然 iframe 内可以显示 example.jp
的内容,但是 iframe 外层的其他主机(trap.example.com
)上的 JavaScript 却无法访问其内容。这是因为 JavaScript 若能访问其他主机的话就会导致安全性问题,所以根据同源策略,访问遭到了拒绝。
# 3.2.3 同源的条件
- URL 的主机(FQDN,全称域名)一致;
- Scheme 一致;
- Port 一致;
同源策略的保护对象不仅仅是 iframe 内的文档。比如实现 Ajax 时所用的 XMLHttpRequest 对象能够访问的 URL 也受到了同源策略的保护。
# 3.2.4 应用程序安全隐患与被动攻击
应用程序安全隐患与被动攻击虽然浏览器的同源策略为抵御被动政击设下了一道屏障,但如果应用程序中存在安全隐患,还是有可能会遭受到被动攻击。跨站脚本攻击(XSS)就是典型的例子。
XSS 在下一章中会详细讲述,这里我们先利用刚才试验的例子来解释一下其攻击方式。在使用 iframe 外层的 JavaScripti 访问内层(其他主机)数据时由于违反同源策略,访问会被拒绝。但是,这种情况下却可以使用一些特殊手段将 Javascript 放到 iframe 的内层去执行。由于在 iframe 内层不会受到同源策略的限制,因此就能够成功访问文档信息。这种攻击就叫作跨站脚本攻击(XSS)。XSS 会在之后继续讲述。
# 3.3 JavaScript 以外的跨域访问
前面说了 JavaScript 的跨域访问会受到 Same Origin Policy 的限制。下面我们看一下能够进行跨域访问的其他浏览器功能。
# 3.3.1 frame 和 iframe 元素
由前面的试验可知,frame 和 iframe 元素能够进行跨域访问,但通过 JS 却不能跨域访问 iframe 中的文档。
X-FRAME-OPTIONS
X-FRAME-OPTIONS 是 Microsoft 提出的一种限制 frame 和 iframe 访问权限的方案,现已被主流浏览器采用。X-FRAME-OPTIONS 被定义在响应头信息中,值为 DENY 或 SAMEORIGIN。
- 指定了 DENY 的 response 将不能显示在 frame 等的内层中
- 指定了 SAMEORIGIN 则仅当与地址栏上显示的域名为同源是才能够被显示
X-FRAME-OPTIONS 还可以用来防范点击劫持(Clickjacking)。通过将不使用 frame 或 iframe 的网站指定为 DENY,使用 frame 并且使用单一主机的网站指定为 SAMEORIGIN,就能够更好地防御利用 frame 执行的各种攻击。
# 3.3.2 img 元素
img 元素的 src 属性能够指定其他域名。这时,请求图像时会附带图像所在主机的 Cookie,所以就能够让恶意网站上的图像显示为“此图像需要认证”。
JS 无法访问图像文件内部,所以跨域图像访问通常不会造成什么问题。如果不想让自己的图像被贴到某些特定网站,则可以针对图像检验 Referer 消息头。不过这样会使关闭了 Referer 的用户无法看到图像。
# 3.3.3 script 元素
通过指定 script 元素的 src 属性就能够从其他网站来读取 JavaScript。
# 3.3.4 CSS
CSS 能够被跨域读取。具体来说,除了 HTML 的 link 元素之外,也能在 CSS 中使用 @import
,或者使用 JavaScript 的 addImport
方法。
一般来说,即使读取不良网站的 CSS 也不会造成问题。但以前在 IE 浏览器中出现过叫作 CSSXSS 的安全隐患,它能使 HTML 或 JavaScript 被当成 CSS 读取,而如果其中部分代码能被执行的话就会有危险。
# 3.3.5 form 元素的 action 属性
form 元素的 action 属性也能够跨域指定。而且无论 action 的目标是否跨域,form 的提交都能通过 JavaScript 来操作。
恶意利用 form 元素的特性来实施攻击的方式被称为跨站请求伪造(CSRF)。CSRF 是让用户在不知情的情况下提交 form,从而肆意使用应用中的功能。