确保库加载的安全以防 DLL 预回载攻击


概要


当应用程序在不指定完全限定路径的情况下动态加载动态链接库 (DLL) 时,Windows 会尝试通过搜索定义完好的目录集来查找 DLL。如果攻击者获得其中一个目录的控制权,他们可以强制应用程序加载恶意的 DLL 副本,而不是预期的 DLL。这些攻击称为“DLL 预加载攻击”,这对于支持动态加载共享 DLL 库的所有操作系统都很常见。此类攻击的结果是攻击者可以在运行该应用程序的用户上下文中执行代码。以管理员身份运行应用程序时,这可能会导致本地特权提升。我们会继续关注这些攻击。若要减小此问题对我们共同的客户造成的负面影响,我们向开发人员社区发布该文档,以确保他们了解此问题并使用所需工具解决其应用程序中的问题。

更多信息


DLL 预加载攻击说明

基于 LoadLibrary 的攻击

当应用程序在不指定完全限定路径的情况下动态加载 DLL 时,Windows 会尝试通过线性搜索整个定义完好的目录集(也称为 DLL 搜索顺序)来查找该 DLL。如果 Windows 在 DLL 搜索顺序内找到 DLL,将会加载该 DLL。但是,如果 Windows 按 DLL 搜索顺序没有在任何目录中找到 DLL,则会为 DLL 加载操作返回一个失败结果。 以下是 LoadLibraryLoadLibraryEx 函数的 DLL 搜索顺序,这两个函数用于动态加载 DLL。
  1. 从其中加载应用程序的目录
  2. 系统目录
  3. 16 位系统目录
  4. Windows 目录
  5. 当前工作目录 (CWD)
  6. PATH 环境变量中列出的目录


请考虑以下情况:


  • 应用程序加载 DLL,而不指定希望在应用程序的 CWD 中找到的完全限定路径。
  • 找不到 DLL 时,应用程序做好了处理这种情况的全部准备。
  • 攻击者知道关于应用程序的该信息,并控制 CWD。
  • 攻击者在 CWD 中复制他们自己特制的 DLL 版本。这假定攻击者具有执行此操作的权限。
  • Windows 按 DLL 搜索顺序对目录进行搜索,并在应用程序的 CWD 中找到 DLL。
在这种情况下,特制 DLL 在该应用程序中运行,并获取了当前用户的特权。

建议
若要阻止此攻击,应用程序可以从 DLL 搜索路径中删除当前工作目录 (CWD),方法是使用空字符串 (“”) 调用 SetDllDirectory API。如果应用程序依赖于从当前目录加载 DLL,请获取当前工作目录并将其用于在 LoadLibrary 的完全限定路径中传递。



我们还注意到一些开发人员使用 LoadLibrary 来验证特定 DLL 是否存在,以便确定用户在运行的 Windows 版本。请注意,这会给应用程序带来漏洞。如果应用程序在其中执行的 Windows 发行版上不存在受影响的库,攻击者可能会将具有相同名称的库引入 CWD。我们强烈建议您不要使用此技术。而使用 MSDN 文章“获取系统版本”中所述的推荐技术。

加载第三方插件但不能强制插件将完全限定路径用于其 LoadLibrary 调用的应用程序,应调用 SetDllDirectory(“”) 以删除 CWD,然后调用 SetDllDirectory(“插件安装位置”)向 DLL 搜索路径添加插件安装目录。

基于 SearchPath 的攻击

当应用程序使用 SearchPath API 来查找 DLL 并动态加载由 SearchPath 返回的路径时,存在一个类似的攻击。下面是 SearchPath API 的默认搜索顺序:
  • 从其中加载应用程序的目录
  • 当前工作目录 (CWD)
  • 系统目录
  • 16 位系统目录
  • Windows 目录
  • PATH 环境变量中列出的目录
我们不推荐此模式,因为它不安全。如果打算在 LoadLibrary 函数调用中使用输出,我们不建议您使用 SearchPath 函数作为查找 .dll 文件的方法。这可能导致找到错误的 .dll 文件,因为 SearchPath 函数的搜索顺序与 LoadLibrary 函数使用的搜索顺序不同。如果您要查找并加载 .dll 文件,请使用 LoadLibrary 函数。在开发人员调用类似的函数(如 ShellExecuteCreateProcess)以加载外部可执行文件时,可能还存在这些问题的

ShellExecute 和 CreateProcess

变体。我们建议开发人员在加载二进制文件时一定要小心,并建议指定完全限定路径。这样在加载二进制文件而非库文件时会降低复杂性。

对软件开发人员的建议步骤

我们建议软件开发人员执行以下操作:

  • 验证其应用程序中是否存在不安全的库加载实例(后文提供各种示例)。这些方法包括:
    • 使用 SearchPath 来标识库或组件的位置。
    • 使用 LoadLibrary 来标识操作系统的版本。
  • 对所有 LoadLibraryCreateProcess、和 ShellExecute 的调用使用完全限定路径(如果需要)。
  • 使用空字符串实现对 SetDllDirectory 的调用,这可按默认 DLL 搜索顺序删除当前工作目录(如果需要)。请注意,SetDllDirectory 会影响整个进程。因此,您应该在初始化过程的初期(而不是在调用 LoadLibrary 之前或之后)执行一次该操作。由于 SetDllDirectory 影响整个进程,因此使用不同值调用 SetDllDirectory 的多个线程可能会导致不确定的行为。此外,如果此进程用于加载第三方 DLL,还需要进行测试以确定进程范围的设置是否会导致出现不兼容问题。一个已知问题是当应用程序依赖 Visual Basic for Applications 时,进程范围的设置可能会导致不兼容问题。
  • 使用 SetSearchPathMode函数为进程启用安全进程搜索模式。这可在进程的生命周期内将当前工作目录移到 SearchPath 搜索列表的最末尾。
  • 即使安全搜索模式已启用,也请避免在没有指定完全限定路径的情况下使用 SearchPath 来检查 DLL 的存在性,因为这仍会导致 DLL 预加载攻击。

有关标识不安全库加载的指南

在源代码中,下面是一些不安全库加载的示例:

  • 在下面的代码示例中,应用程序通过使用安全性最低的搜索路径搜索“schannel.dll”。如果攻击者可以在 CWD 中放置 schannel.dll,甚至会在应用程序在 Windows 目录中搜索相应的库之前加载该 DLL。
    DWORD retval = SearchPath(NULL, "schannel", ".dll", err, result, NULL); 
    HMODULE handle = LoadLibrary(result);
  • 在下面的代码示例中,应用程序尝试从各种应用程序和操作系统位置为 LoadLibrary() 调用加载库(如本文档开头所述)。如果有任何文件不存在的风险,应用程序都会尝试从当前工作目录加载该文件。这种情况的危险性略低于上一示例。但是,如果环境不是完全可预测,仍会将应用程序用户暴露在风险中。
    HMODULE handle = LoadLibrary("schannel.dll");



下面是一些更好更安全的库加载的示例:

  • 在下面的代码示例中,通过使用完全限定路径直接加载库。除非攻击者已具有对应用程序目标目录的写入权限,否则不存在攻击者引入恶意代码的风险。

    HMODULE handle = LoadLibrary("c:\\windows\\system32\\schannel.dll");


    注意有关如何确定系统目录的信息,请参阅以下资源:

    GetSystemDirectorySHGetKnownFolderPath
  • 在下面的代码示例中,在调用 LoadLibrary 之前从搜索路径中删除当前工作目录。这大大降低了风险,因为攻击者必须控制应用程序目录、Windows 目录或用户路径中指定的任何目录才能使用 DLL 预加载攻击。
    SetDllDirectory ("");
    HMODULE handle = LoadLibrary("schannel.dll");
  • 在已安装安全更新 963027 的所有系统上(MS09-014 中所述),以下代码会将 CWD 记录地移动到搜索顺序最后一个位置。在尝试更改搜索模式的进程中,以后对 SetSearchPathMode 函数的所有调用都会失败。
    SetDllDirectory ("");
    HMODULE handle = LoadLibrary("schannel.dll");
  • 在下面的代码示例中,在调用 LoadLibrary 之前从搜索路径中删除当前工作目录。这大大降低了风险,因为攻击者必须控制应用程序目录、Windows 目录或用户路径中指定的任何目录才能使用 DLL 预加载攻击。
    SetSearchPathMode (BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE | BASE_SEARCH_PATH_PERMANENT );
    HMODULE handle = LoadLibrary("schannel.dll");

使用进程监视器动态检测不安全加载

Microsoft 发布了一个名为进程监视器的工具。使用此工具,开发人员和管理员可以密切跟踪运行中进程的行为。进程监视器可用于动态检测您其中一个应用程序是否容易遭受此类问题的攻击。
  • 若要下载进程监视器,请访问下面的 Microsoft 网页:
  • 尝试通过将 CWD 设置为特定目录来启动应用程序。例如,双击具有扩展名的文件,其文件句柄已分配给应用程序。
  • 使用以下筛选器设置进程监视器:



    进程监视器筛选图像
  • 如果存在漏洞的路径正遭受攻击,则会显示类似以下内容的信息:进程监视器筛选图像 2

    对用于加载 DLL 的远程文件共享的调用指示这是一个存在漏洞的程序。

附加资源


有关更多信息,请访问下面的 Microsoft 网页:

动态链接库搜索顺序关于 SearchPath 函数的 MSDN 文档关于 LoadLibrary 函数的 MSDN 文档关于 SetDllDirectory 函数的 MSDN 文档关于 SetSearchPathMode 函数的 MSDN 文档Microsoft Office 首席安全工程师 David Leblanc 的博客文章研究 DLL 预加载攻击的 MSRC 工程小组成员 Andrew Roths 的博客文章