Windows NTFS文件系统目录大小写敏感导致的安全问题 - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

Windows NTFS文件系统目录大小写敏感导致的安全问题

41yf1sh 系统安全 2019-03-13 09:55:02
524484
收藏

导语:早在2018年2月,微软就发表过一篇有趣的博客文章,该文章介绍了NTFS文件系统中每个目录的大小写敏感问题。微软一直致力于为WSL提供更加强大的支持,并且开始打通Linux与Windows之间的边界。

概述

早在2018年2月,微软就发表过一篇有趣的博客文章,该文章介绍了NTFS文件系统中每个目录的大小写敏感问题。微软一直致力于为WSL提供更加强大的支持,并且开始打通Linux与Windows之间的边界。特别值得注意的是,传统的类Unix文件系统和Windows NTFS之间具有不同的语义。

我一直密切关注着新的Windows功能,并寻找是否可能存在安全风险。显然,目录大小写敏感的问题引起了我的注意。现在,我觉得是时机写一篇关于目录大小写敏感的简短文章,并列举一下其中的安全隐患。并且,既然我要分析这一方面,那么我觉得可以从Windows NT开始,分析一下多年以来的大小写敏感性问题。

早期版本

从Windows NT的第一个版本开始,文件系统就具有大小写敏感的特性。这是因为在操作系统中存在众所周知但很少使用到的POSIX子系统。如果查看CreateFile的文档,我们可以看到其中存在一个标志:FILE_FLAG_POSIX_SEMANTICS,该标志用于:

根据POSIX规则进行访问。其中包括允许多个文件具有字母相同但大小写不同的名称,适用于支持该命名方式的文件系统。

因此,为了让文件系统能够具有大小写敏感的特性,我们需要做的就是使用此标志。但是,由于该标志是一个可选的标志,因此大多数Windows软件可能都不太会正确使用它。我们希望了解这一标志实际上完成的工作,因为CreateFile并不是系统调用。如果我们深入研究KERNEL32中的代码,可以发现如下部分:

BOOL CreateFileInternal(LPCWSTR lpFileName, ..., DWORD dwFlagsAndAttributes) {
    // ...
    OBJECT_ATTRIBUTES ObjectAttributes;
    if (dwFlagsAndAttributes & FILE_FLAG_POSIX_SEMANTICS){
      ObjectAttributes.Attributes = 0;
    } else {
      ObjectAttributes.Attributes = OBJ_CASE_INSENSITIVE;
    }
   
    NtCreateFile(..., &ObjectAttributes, ...);
}

这段代码说明,如果设置了FILE_FLAG_POSIX_SEMANTICS标志,那么传递给NtCreateFile的OBJECT_ATTRIBUTES结构的Attributes成员将初始化为0。否则,将会使用标志OBJ_CASE_INSENSITIVE来初始化。OBJ_CASE_INSENSITIVE指示对象管理器(Object Manager)命名的内核对象执行不区分大小写的查找过程。但是,对象管理器不会直接解析文件,因此IO管理器会将此标志转化为IO_STACK_LOCATION标志SL_CASE_SENSITIVE,然后再将其传递给IRP_MJ_CREATE IRP中的文件系统驱动程序。然后,文件系统驱动程序可以选择是否遵循该标志。在NTFS文件系统中,将会遵循这一标志,并执行区分大小写的文件搜索过程,而不再进行默认的不区分大小写的搜索。

此外我还发现,FILE_FLAG_POSIX_SEMANTICS支持CreateFile的另外一个附加功能。通过在dwCreationDisposition参数中指定FILE_FLAG_BACKUP_SEMANTICS、FILE_FLAG_POSIX_SEMANTICS和FILE_ATTRIBUTE_DIRECTORY,以及作为dwCreationDisposition参数的CREATE_NEW,API将创建一个新目录,并返回一个句柄。这通常需要调用CreateDirectory,然后第二次调用打开或使用本地NtCreateFile系统调用。

NTFS始终支持大小写敏感操作,因此如果我们创建一个名为ABC.txt的文件,实际的大小写将保持不变。但是,在其确认文件是否存在的初始检查过程中是不区分大小写的,如果我们请求的是abc.TXT,那么NTFS将会找到该文件。如果创建的是大小写敏感的文件,那么NTFS将无法找到文件,也就意味着我们可以创建第二个文件。这样一来,就允许NTFS完全支持大小写敏感。

以大小写敏感的方式创建文件非常简单,只需要使用FILE_FLAG_POSIX_SEMANTICS标志,或者不将OBJ_CASE_INSENSITIVE传递给NtCreateFile即可。接下来,让我们在Windows 10 1809的默认版本中尝试是否真的可以使用PowerShell实现这一点。

1.PNG

首先,我们创建一个名为AbC.txt的文件,在NTFS中是保留大小写的,因此这就将作为文件系统中分配给它的名称。然后,我们打开文件,设置OBJ_CASE_INSENSITIVE属性标志,并以小写形式指定全部名称。正如预期的那样,我们打开文件,显示了保留大小写形式的文件名。接下来,我们在没有OBJ_CASE_INSENSITIVE标志的情况下执行相同的操作,但出乎意料的是,它仍然有效。看起来内核只是忽略了丢失的标志,并且仍然保持大小写不敏感。

事实证明,这是在设计中被定义的,因为大小写不敏感的操作被定义为可选项,没有人会正确设置此标志,并且这可能会造成Windows子系统的崩溃。因此,为会话管理器内核注册表ObCaseInsensitive提供了大小写敏感的支持,默认情况下设置为TRUE。这个变量只在一个地方使用,就是ObpLookupObjectName,如下所示:

NTSTATUS ObpLookupObjectName(POBJECT_ATTRIBUTES ObjectAttributes, ...) {
  // ...
  DWORD Attributes = ObjectAttributes->Attributes;
 
  if (ObpCaseInsensitive) {
    Attributes |= OBJ_CASE_INSENSITIVE;
  }
 
  // Continue lookup.
}

从这段代码中我们可以看到,无论ObpCaseInsensitive是否设置为TRUE,无论传递给查找操作的属性标志是什么,总会设置上OBJ_CASE_INSENSITIVE标志。这意味着,无论我们做什么,都无法在默认的Windows安装上执行区分大小写的查找操作。当然,如果我们安装了POSIX子系统,我们通常会发现内核变量设置为FALSE,这将会为每个人启动大小写敏感的操作,为了避免他们忘记设置标志的情况。

让我们再次使用PowerShell进行相同的测试,但这一次保证ObpCaseInsensitive为FALSE,现在看看是否获得了预期的操作。

2.PNG

在设置了OBJ_CASE_INSENSITIVE标志后,我们仍然可以使用小写名称打开AbC.txt。但是,如果没有指定标志,我们将得到STATUS_OBJECT_NAME_NOT_FOUND,表示查找操作失败。

适用于Linux的Windows子系统

接下来,让我们快速介绍一下Windows 10 1607中的WSLL。WSL以某种方式来表示典型的区分大小写的Linux文件系统。从理论上讲,开发人员可以在不区分大小写的文件系统上实现它,但这可能会引入太多的兼容性问题。然而,全局禁用ObCaseInsensitive可能会在Windows端引入自己的一组兼容性问题。需要进行妥协,以支持现有卷上的区分大小写的文件。

可以说,类Unix操作系统(包括Linux)根本没有区分大小写的文件系统,只是一个忽略大小写的文件系统。大多数类Unix文件系统只会将磁盘上的文件名视为不透明字节(Opaque Bytes)的字符串,判断文件名与字节序列是否匹配。文件系统并不关心任何特定字节是小写字母还是大写字母。这会导致一些有趣的问题,例如,用户看起来相同的两个文件名可能会有不同的字节表示,从而导致打开文件时出现意外的故障。某些文件系统(例如:macOS的HFS+,https://en.wikipedia.org/wiki/HFS_Plus)使用Unicode规范化形式(http://unicode.org/reports/tr15/)来使文件名具有规范化的字节表示,但这样又会产生额外的复杂性,并在后续APFS(https://en.wikipedia.org/wiki/Apple_File_System)中被迅速移除。

更新:有人指出,Apple实际上在iOS 11和macOS 10.13中改变了APFS。

我们可以在ObpLookupObjectName中找到,如下所示:

NTSTATUS ObpLookupObjectName(POBJECT_ATTRIBUTES ObjectAttributes, ...) {
  // ...
  DWORD Attributes = ObjectAttributes->Attributes;
 
  if (ObpCaseInsensitive &&
      KeGetCurrentThread()->CrossThreadFlags.ExplicitCaseSensitivity == FALSE) {
      Attributes |= OBJ_CASE_INSENSITIVE;
  }
 
  // 继续查找
}

在代码中,我们发现现有对ObpCaseInsensitive的检查是通过对ExplicitCaseSensitivity位标志当前线程的CrossThreadFlags额外检查进行了补充。仅当未设置标志时,才会强制进行不区分大小写的查找。这看起来存在一些问题,无需更改全局行为即可获取区分大小写的文件。我们可以在NtSetInformationThread中找到设置此标志的代码。

NTSTATUS NtSetInformationThread(HANDLE ThreadHandle,
                                THREADINFOCLASS ThreadInformationClass,
                                PVOID ThreadInformation,
                                ULONG ThreadInformationLength) {
  switch(ThreadInformationClass) {
 
    case ThreadExplicitCaseSensitivity:
      if (ThreadInformationLength != sizeof(DWORD))
        return STATUS_INFO_LENGTH_MISMATCH;
      DWORD value = *((DWORD*)ThreadInformation);
      if (value) {
        if (!SeSinglePrivilegeCheck(SeDebugPrivilege, PreviousMode))
          return STATUS_PRIVILEGE_NOT_HELD;
        if (!RtlTestProtectedAccess(Process, 0x51) )
          return STATUS_ACCESS_DENIED;
      }
   
      if (value)
        Thread->CrossThreadFlags.ExplicitCaseSensitivity = TRUE;
      else
        Thread->CrossThreadFlags.ExplicitCaseSensitivity = FALSE;
      break;
  }
  // ...
}

请注意,在设置ExplicitCaseSensitivity标志的代码时,我们需要同时拥有SeDebugPrivilege以及级别为0x51的受保护进程,也就是Windows签名PPL级别。这段代码来源于Windows 10 1809,我不清楚此前是否存在这样的限制。但是,我们的目标是WSL,因此这并不重要,因为所有的进程都由系统服务和内核驱动程序进行控制,所以实际上可以轻松绕过这些检查。由于WSL进程的任何新线程都必须通过Pico进程驱动程序,因此可以自动设置此标志,一切都会正常进行。

每个目录下的大小写敏感

逐线程地从大小写不敏感中退出解决了当前存在的问题,允许WSL在现有卷上创建大小写敏感的文件,但这种方式无法帮助Windows应用程序与WSL创建的文件实现交互操作。我猜测,当存在多个具有相同字母、不同大小写形式名称的文件时,如果执行大小写不敏感的查找,NTFS将不能保证会打开哪一个文件。由于试图打开一个文件的过程总会出现问题,所以Windows应用程序很可能经常出错。显然,我们需要进行进一步的工作,因此就会到了本文开头所提到的主题,每个目录下的大小写敏感性。

NTFS驱动程序已经处理了大小写敏感的查找操作,因此为什么不将这一重任交给NTFS,来启用大小写敏感的操作呢?一个简单的位标志就足够用了。我在本文开篇时引用的博客文章中,建议使用fsutil命令来设置区分大小写,但是我希望知道它是如何实现的。因此,我使用IDA在Windows Insider中查看fsutil,从而找出其原理。幸运的是,我们找到了关于大小写敏感的文档,我们将带有FILE_CS_FLAG_CASE_SENSITIVE_DIR设置的FILE_CASE_SENSITIVE_INFORMATION结构通过NtSetInformationFile传递到目录。使用FileCaseSensitiveInformation information类。我们可以在NTFS驱动程序中看到其实现。

NTSTATUS NtfsSetCaseSensitiveInfo(PIRP Irp, PNTFS_FILE_OBJECT FileObject) {
 
  if (FileObject->Type != FILE_DIRECTORY) {
    return STATUS_INVALID_PARAMETER;
  }
 
  NSTATUS status = NtfsCaseSensitiveInfoAccessCheck(Irp, FileObject);
  if (NT_ERROR(status))
    return status;
 
  PFILE_CASE_SENSITIVE_INFORMATION info =
        (PFILE_CASE_SENSITIVE_INFORMATION)Irp->AssociatedIrp.SystemBuffer; 
  if (info->Flags & FILE_CS_FLAG_CASE_SENSITIVE_DIR) {
    if ((g_NtfsEnableDirCaseSensitivity & 1) == 0)
      return STATUS_NOT_SUPPORTED;
    if ((g_NtfsEnableDirCaseSensitivity & 2)
        && !NtfsIsFileDeleteable(FileObject))
    {
      return STATUS_DIRECTORY_NOT_EMPTY;
    }
    FileObject->Flags |= 0x400;
  } else {
    if (NtfsDoesDirHaveCaseDifferingNames(FileObject)) {
      return STATUS_CASE_DIFFERING_NAMES_IN_DIR;
    }
    FileObject->Flags &= ~0x400;
  }
 
  return STATUS_SUCCESS;
}

在这里,还有一点需要进行说明。首先,我们只能将其应用于目录。此外,还需要通过调用NtfsCaseSensitiveInfoAccessCheck来传递访问权限检查。我们会暂时忽略这一点。

接下来,我们进入到实际设置/取消设置标志的阶段。除非在全局g_NtfsEnableDirCaseSensitivity变量中设置了位0,否则不会启用对目录大小写敏感的支持。此值从HKLM\SYSTEM\CurrentControlSet\Control\FileSystem中NtfsEnableDirCaseSensitivity的值加载,默认情况下该值设置为0。这意味着,在全新安装的Windows 10中无法使用此功能。几乎可以肯定,在安装WSL时会设置此值,但我也在Microsoft应用程序开发虚拟机上发现设置了此值,而我觉得虚拟机上没有安装WSL。因此,我们可能会发现,该功能会在一些意想不到的情况下启用。g_NtfsEnableDirCaseSensitivity变量也可以设置为位1,这表示在更改大小写敏感标志之前该目录必须为空(使用NtfsIsFileDeleteable检查),但我没有看到启用。如果这些检查通过,那么会在NTFS文件对象中设置标志0x400。

如果未设置该标志,那么唯一会进行的就是检查该目录是否包含任何冲突的文件名。这似乎是最近添加的功能,因为我最初在Insider Preview中测试此功能时,发现可疑禁用具有冲突的文件名的标志,这不一定是明智的行为。

回到访问检查上来,NtfsCaseSensitiveInfoAccessCheck的代码如下所示:

NTSTATUS NtfsCaseSensitiveInfoAccessCheck(PIRP Irp, PNTFS_FILE_OBJECT FileObject) {
 
  if (NtfsEffectiveMode(Irp) || FileObject->Access & FILE_WRITE_ATTRIBUTES) {
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    SECURITY_SUBJECT_CONTEXT SubjectContext;
    SeCaptureSubjectContext(&SubjectContext);
    NtfsLoadSecurityDescriptor(FileObject, &SecurityDescriptor);
    if (SeAccessCheck(SecurityDescriptor,
                      &SubjectContext
                      FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD)) {
      return STATUS_SUCCESS;
    }
  } 
 
  return STATUS_ACCESS_DENIED;
}

第一次检查确保使用FILE_WRITE_ATTRIBUTES访问打开文件句柄,但这还没有启动此标志。该检查还会确保如果对目录的安全描述符执行访问检查,那么调用者会被授予FILE_ADD_FILE、FILE_ADD_SUBDIRECTORY和FILE_DELETE_CHILD访问权限。根据推测,这种二次检查是为了防止文件句柄被共享到具有较少权限但具有FILE_WRITE_ATTRIBUTES权限的另一个进程的情况。

如果通过了安全检查,并且启用了此功能,那么现在就可以更改大小写敏感的行为,甚至可以通过任意Windows应用程序(例如:PowerShell或记事本)进行更改。另外,需要注意的是,大小写敏感会在原始目录下创建的任何新目录中继承。

3.PNG

每个目录下大小写敏感的安全问题

对于这一功能特性,我们最感兴趣的问题就是,这一功能是否存在一些安全问题?我们可能不会立即看到这一行为导致的问题。在这里,所进行的是颠覆了正常Windows应用程序的期望,当涉及到文件名查找的行为发生时,无法检测是否使用了这一设置,也无法进行缓解。使用FILE_FLAG_POSIX_SEMANTICS标志,我们会引入预期之外的大小写敏感功能,但这一功能也意味着NTFS驱动程序在进行查找决策时不会关注OBJ_CASE_INSENSITIVE的状态。从互相操作的角度来看,这有利的,但从安全性来看,就不是那么好了。

我们可以发现这一特性的潜在安全问题如下:

1、TOCTOU用于打开文件的文件名在进行安全检查和最终操作之间,进行了大小写的修改,可能会导致检查的与实际打开的是不同的文件。

2、如果创建请求的大小写与磁盘上文件的实际名称不匹配,那么会在共享位置覆盖文件查找。如果默认情况下启用了“禁止在空目录上设置大小写敏感”标志,可以缓解这一问题。

3、我们可以根据大小写敏感标志的状态,替换路径中早期目录的查找。这一点可以通过检查目录中冲突的文件名来实现部分缓解,但我无法预估这一问题的潜在风险有多严重。

有趣的是,此功能不会使用RtlIsSandboxToken来检查调用方是否位于沙箱中。只要满足访问检查的要求,就可以在AppContainer执行此操作。从好的方面来说,至少在默认情况下不会启用此功能。但是,我仍然可以想象,一些企业使用的镜像中意外启用了这一功能,或者某些应用程序(例如:Visual Studio)会将其启用。从安全角度来看,在全局范围内不开启大小写敏感较为安全。尽管这只是我初步的分析,还没有真正找到严重的漏洞利用方法,但这只是一个时间问题。

  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2024 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务