五、竞态条件

原文:Race Condition Vulnerability

译者:飞龙

1 竞态条件漏洞

  • 下面的代码段属于某个特权程序(即 Set-UID 程序),它使用 Root 权限运行。

    1: if (!access("/tmp/X", W_OK)) {
    2: /* the real user ID has access right */
    3: f = open("/tmp/X", O_WRITE); 4
    : write_to_file(f);
    5: }
    6: else {
    7: /* the real user ID does not have access right */
    8: fprintf(stderr, "Permission denied\n");
    9: }
    • access系统该调用检查了真实 UID 或者 GID 是否拥有访问文件的权限,有的话返回 0。在代表真实 UID (而不是有效 UID)访问文件之前,该系统调用通常由 Set-UID 程序使用。
    • open系统调用也执行访问控制,但是仅仅检查有效 UID 或 GID 是否拥有访问文件的权限。
    • 上面的程序想要写入文件/tmp/X。在这么做之前,它要确保,文件确实由真实 UID 写入。如果没有这种检查,程序可以写入这个文件,无论真实 UID 可不可以写入它,因为程序使用 Root 权限运行(即open所检查的有效 UID 是 Root)。
  • 假设上面的程序执行的非常慢。执行程序中的每行语句需要一分钟。请思考下列问题:
    • 你可以使用这个程序来覆盖其它文件,例如/etc/passwd嘛?
    • 你不能修改该程序,但是你可以利用每两条语句之前的的一分钟。
    • /tmp目录的权限为rwxrwxrwx,这允许任何用户在里面创建文件或链接。
    • 提示:/tmp/X不需要是真实文件,他可以是符号链接。
  • 攻击策略:
    • 如果我们让/tmp/X在第一行之前打印/etc/passwdaccess调用就会发现,真实 UID 没有权限来修改/etc/passwd。因此,执行流会来到else分支。在第一行之前,/tmp/X必须是一个能被真实 UID 写入的文件。
    • 显然,如果我们在第一行之后不做任何事情,/tmp/X会打开,攻击者不能获得任何东西。
    • 让我们专注于第一行和第三行之前的时间间隔。由于我们假设,程序执行得很慢。我们在第一行之后,第三行之前有一分钟的间隔。使用这个时间间隔,我们可以删掉/tmp/X并且使用相同名称创建符号链接。并使其指向/etc/passwd
    • 如果我们这么做,会发生什么?
      • 通过遵循符号链接,程序使用open来打开/etc/passwd
      • open系统调用只检查有效 UID 或 GID 是否可以访问文件。由于这是个 Set-UID Root 程序,有效 UID 是 Root,它可以读写/etc/passwd
      • 因此,第四行实际上会写入文件/etc/passwd。如果写入文件的内容也可以由用户控制,攻击者就可以修改密码文件,并且最终获得 Root 权限。如果内容不能由用户控制,攻击者可以破坏密码文件,组织其他用户登入系统。
  • 回到现实:这个程序执行得很快,并且我们没有一分钟时间间隔。我们可以做什么?
  • 竞态条件攻击
    • 使/tmp/X在访问和打开调用中,表现为两个文件。
    • access(/tmp/X, W_OK)之前,/tmp/X就是/tmp/X
    • access(/tmp/X, W_OK)之后,将/tmp/X修改为/etc/passwd
    • 如何实现?
      • 在两个调用之间只有很短的时间间隔。
      • 检查和使用之间的间隔:TOCTOU
      • CPU 可能在access后进行上下文切换,之后执行其它进程。
      • 如果攻击进程在上下文切换之间,得到了机会来执行这种攻击,攻击就会成功。
      • 因为我们不能保证,第一行和第三行之间存在上下文切换,即使攻击程序在上下文切换期间,得到执行机会,攻击也可能失败。但是,如果执行一次不成功,我们可以多次执行攻击和目标程序。
  • 提高成功率:竞态条件攻击的最关键步骤,出现在 TOCTOU 间隔中。由于我们不能修改漏洞程序,我们可以做的只有让我们的攻击程序和目标程序一起运行。并希望链接的时机正好就在间隔之内。不幸的是,我们不能完成完美的时间规划。因此,攻击是否成功是个概率。攻击成功的概率可能很低,如果间隔很小。我们如何提升概率呢?
    • 通过执行多个 CPU 密集的程序来拖慢计算机。
    • 创建多个攻击进程。
  • 另一个例子(Set-UID 程序):

    file = "/tmp/X";
    fileExist = check_file_existence(file);
    if (fileExist == FALSE){ // The file does not exist, create it. f = open(file, O_CREAT);
    }
    • 在 Unix 中,我们使用open系统调用来创建文件。
    • open(file, O_CREAT)在文件不存在时创建文件,如果文件存在,它只会打开文件。
  • 为什么存在漏洞?
    • 竞态条件:使文件在检查期间不存在,并使其在检查之后指向/etc/passwd

2 预防措施

  • 方式

    • 将检查和使用操作转为一条原子操作。如果我们可以使用一个系统调用来完成这种检查和使用的目的,我们就没有竞态条件。在多数操作系统中,系统条用不可以被另一个用户空间的进程大端,因此,在系统调用期间不可能有上下文切换。
    • 在检查和使用操作期间,确保相同文件名指向相同文件(也就是相同的 inode)。
    • 使赢得竞态条件的可能性非常低。
    • 如果不是必要,不要使用太多的权限。
  • 使用原子操作
    • 如果系统调用可以在一条调用中执行检查和使用操作,它就是安全的,因为系统调用中不会发生上下文切换。
    • open(file, O_CREAT | O_EXCL)可以在一条原子指令中检查和打开文件。如果文件已经存在,它就会返回错误,否则它会创建文件。mkstemp函数会按照模板生成一个唯一的临时文件名称。这个函数使用O_EXCL来使用open。来防止竞态条件问题。
    • 与之类似,我们可以为open创建另一个选项,来一起执行accessopen。虽然这种选项不存在于 POSIX 标准中,但是它很容易实现。也就是,我们可以定义一个选项叫做O_REAL_USER_ID。当我们使用open调用open(file, O WRITE | O REAL USER ID),我们让open检查有效和真实 UID,并仅当两个 UID 都有权限打开文件时,才打开文件。实际上,让 POSIX 标准委员会接收这个新的选项并不是很容易。
  • 检查-使用-再检查方式

    • lstat(file, &result)可以获取文件状态。如果文件是个符号链接,它返回链接的状态(不是链接指向的文件)。在 TOCTOW 之前,我们可以使用它来检查文件状态。接着在间隔之后,执行另一个检查。如果结果不同,我们就检测到了竞态条件。让我们看看下面的解决方案:

          struct stat statBefore, statAfter;1:  lstat("/tmp/X", &statBefore);2:  if (!access("/tmp/X", O_RDWR)) { /* the real UID has access right */
      3:      f = open("/tmp/X", O_RDWR);
      4:      lstat("/tmp/X", &statAfter);5:      if (statAfter.st_ino == statBefore.st_ino)
      6:      { /* the I-node is still the same */
      7:          Write_to_file(f)
      8:      }
      9:      else perror("Race Condition Attacks!");
      10: }
      11: else fprintf(stderr, "Permission denied\n");
    • 但是,上面的解决方案不能工作(open和第二个`lstat之间存在竞态条件漏洞)。为了利用这个漏洞,攻击者需要执行另个静态条件攻击,第一个在第二行和第三行之间,另一个在第三行和第四行之间。虽然赢得两次竞争的可能性低于前面的情况,但还是可能的。

    • 为了修复漏洞,我们打算在文件描述符f上使用lstat,而不是在文件名称上。虽然lstat不能这样做,但是fstat可以。

          #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h>int main() { struct stat statBefore, statAfter;1:       lstat("/tmp/X", &statBefore);
      2:      if (!access("/tmp/X", O_RDWR)) { /* the real UID has access right */
      3:          int f = open("/tmp/X", O_RDWR);
      4:          fstat(f, &statAfter);
      5:          if (statAfter.st_ino == statBefore.st_ino)
      6:          { /* the I-node is still the same */
      7:              write_to_file(f);
      8:          }
      9:          else perror("Race Condition Attacks!");
      10:     }
      11:     else fprintf(stderr, "Permission denied\n");
      12: }
    • 问题:lstatfstat之间有没有竞态条件?如果在第一行使用符号链接(例如到/etc/shadow)。之后在第二行之前,快速切换到/tmp/X,之后在第三行之前再次快速切换会符号链接呢?

      答案:这个攻击是不可行的。函数调用lstat("/tmp/X",...)返回链接的状态,如果/tmp/X是个符号链接,而不是链接所指向文件的状态。换句话说,当/tnp/X指向了/etc/shadow,由lstat(/tmp/X,...)返回的 inode 就是/tmp/X的 inode,但是由fstat(f, ...)返回的 unode 是文件的 inode(这里是/etc/shadow的 inode)。即使/tmp/X指向了/etc/shadow,这两个 inode 是不同的。

    • 要注意:所有这类调用都有两个版本,一个用于文件名,另一个用于文件描述符(思考:如果access也可以用于文件描述符,解法会简单很多)。

  • 检查-使用-重复方式:在几个迭代内重复访问和打开。在下面的示例中,攻击者需要赢得五个竞态条件(1~2,2~3,3~4,4~5,5~6):

    1:  if (access("tmp/X", O_RDWR)) goto error handling
    2:  else f1 = open("/tmp/X", O_RDWR);
    3:  if (access("tmp/X", O_RDWR)) goto error handling
    4:  else f2 = open("/tmp/X", O_RDWR);
    5:  if (access("tmp/X", O_RDWR)) goto error handling
    6:  else f3 = open("/tmp/X", O_RDWR);// Check whether f1, f2, and f3 has the same i-node (using fstat)
  • 基于最小权限原则:

    • 在使用accessopen的程序中,我们知道open比我们想要的更加强大(它只检查有效 UID),这就是我们需要使用access来确保我们没有滥用权限的原因。我们从竞态条件攻击中得到的启示,就是这种检查不是始终可靠。
    • 另一个防止程序滥用权限的方法,就是不要给予程序权限。这就是最小权限原则的本质:如果我们暂时不需要这个权限,我们应该禁用他。如果我们永远都不需要这个权限,我们应该移除它。没有了权限,即使程序犯了一些错误,损失也会降低。
    • 在 Unix 中,我们可以使用seteuid或者setuid系统调用,来开启、禁用或删除权限。

          /* disable the root privilege */ #include <unistd.h> #include <sys/types.h>uid_t real_uid = getuid(); // get real user iduid_t effective_uid = geteuid(); // get effective user id
      1:  seteuid (real_uid);2:  f = open("/tmp/X", O_WRITE);
      3:  if (f != -1)
      4:  write_to_file(f);
      5:  else
      6:  fprintf(stderr, "Permission denied\n");/* if needed, enable the root privilege */
      7:  seteuid (effective_uid);

雪城大学信息安全讲义 五、竞态条件相关推荐

  1. 雪城大学信息安全讲义 3.2 Set-UID 程序的漏洞

    2 Set-UID 程序的漏洞 2.1 隐藏的输入:环境变量 特权程序必须对所有输入进行安全检查.输入检查实际上是访问控制的一部分,特权程序必须这么做,来确保程序的安全.很多安全问题都是输入检查的错误 ...

  2. 雪城大学信息安全讲义 4.1~4.2

    四.缓冲区溢出漏洞和攻击 原文:Buffer-Overflow Vulnerabilities and Attacks 译者:飞龙 1 内存 这个讲义的"区域"(Area)和&quo ...

  3. 雪城大学信息安全讲义 七、格式化字符串漏洞

    七.格式化字符串漏洞 原文:Format String Vulnerability 译者:飞龙 printf ( user_input ); 上面的代码在 C 程序中十分常见.这一章中,我们会发现如果 ...

  4. 雪城大学信息安全讲义 3.1 Set-UID 机制如何工作

    三.Set-UID 特权程序 原文:Set-UID Programs and Vulnerabilities 译者:飞龙 这个讲义的主要目标就是来讨论特权程序,为什么需要他们,他们如何工作,以及它们有 ...

  5. 雪城大学信息安全讲义 3.3 提升 Set-UID 程序的安全性

    3 提升 Set-UID 程序的安全性 exec函数 exec函数系列通过将当前进程映像包装为新的,来运行紫禁城.有许多exec函数的版本,工作方式不同.它们可以归类为: 使用/不适用 Shell 来 ...

  6. 雪城大学信息安全讲义 六、输入校验

    六.输入校验 原文:Input Validation 译者:飞龙 1 环境变量(隐藏的输入) 环境变量是隐藏的输入.它们存在并影响程序行为.在编程中忽略它们的存在可能导致安全隐患. PATH 在 Sh ...

  7. 雪城大学信息安全讲义 4.5

    5 堆或 BSS 的缓冲区溢出 堆或 BSS 的内容 字符串常量 全局变量 静态变量 动态分配的内存 示例:覆盖文件指针 /* The following variables are stored i ...

  8. 雪城大学信息安全讲义 4.3~4.4

    3 对抗措施 3.1 应用安全工程原则 使用强类型语言,例如 Java.C#,以及其他.使用这些语言,可以避免缓冲区溢出. 使用安全的库函数 可能拥有缓冲区溢出问题的函数:gets.strcpy.st ...

  9. 雪城大学信息安全讲义 二、Unix 安全概览

    二.Unix 安全概览 原文:Unix Security Basics 译者:飞龙 1 用户和用户组 用户 root:超极用户(UID = 0) daemon:处理网络. nobody:不拥有文件,用 ...

最新文章

  1. python 日志 装饰器_【Python】装饰器实现日志记录
  2. 微服务注册发现集群搭建——Registrator + Consul + Consul-template + nginx
  3. 不吹不擂,一文揭秘鸿蒙操作系统
  4. redux 和 mobX对比
  5. 使用C#进行系统编程
  6. 单例模式示例_单例设计模式示例
  7. 超级强大的socket工具ss,替代netstat
  8. 【Python爬虫】微信公众号历史文章和文章评论API分析
  9. 第一次做项目经理总结_工程总承包项目:项目经理如果是第一次做,一定要把握这4个要点...
  10. html评分图标,Bootstrap图标实现移动端的星星评分功能
  11. Js页面打印组件实现
  12. 全网最全的AItium Designer 16下载资源与安装步骤
  13. Thinkpad SL400 功能键介绍
  14. 方差(Var),样本方差(SVar),标准差(SD),均方误差(MSE),均方根误差(RMSE),平均绝对误差(MAE)
  15. node 使用 cnpm
  16. linux查询网络连接状态,Linux 查看网络连接状态
  17. IT从业者创业公司生存指南:创业中期 ---- 先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。
  18. 见面会 | 创新工场执行董事王嘉平:高性能可伸缩性区块链系统揭秘!
  19. NUC970 SD卡驱动(SDIO)
  20. 机器人学笔记之——空间描述和变换:姿态的其他描述方法

热门文章

  1. (71)Verilog HDL时间度量系统函数:$time
  2. (8)Zynq AXI_ACP接口介绍
  3. 视觉检测无脊椎机器人或vipir_深入浅出人工智能前沿技术—机器视觉检测,看清人类智慧工业...
  4. java mockserver搭建_mockjs,json-server一起搭建前端通用的数据模拟框架教程
  5. C# CRC16 在textbox中显示实时时间
  6. 贝叶斯网络+推理+近似推理+变分法
  7. 【LeetCode】剑指 Offer 17. 打印从1到最大的n位数
  8. 力扣报错“AddressSanitizer: heap-buffer-overflow on address...”的解决办法
  9. 【力扣】NO.13.罗马数字转整数
  10. Java 类主动引用和被动引用