none
解决两个难懂的安全性问题 RRS feed

  • 常规讨论

  • 作者:Michael Howard

    编译:VCKBASE


    经常讨论一些您发现的少量错误并让人们知道它们是很有益的。在本月的专栏中,我想讨论两个主题:

    1. 交互式服务
    2. 调用 _alloca()

    安全性、服务和交互式桌面

      与 Unix 守护程序类似,服务是 Microsoft Windows NT 的中枢,可以向操作系统和用户提供重要功能而无需用户的参与。创建服务时,有一些问题需要注意。
      Microsoft Windows 中的服务通常是控制台应用程序,它们的运行无需用户参与,也没有用户界面。但在某些实例中,服务可能需要与用户进行交互。运行在较高安全环境中的服务(如 SYSTEM)不应作为交互式服务运行。在 Windows 用户界面中,桌面是安全边界,在交互式桌面上运行的任何应用程序可以与交互式桌面上的任何窗口交互,即使窗口并不可见。无论创建窗口的应用程序的安全环境和应用程序的安全环境如何,都是这样。
      由于这些设计特点,任何在交互式桌面上打开窗口的服务都会向登录用户所执行的应用程序公开。如果服务试图使用窗口消息控制其功能,则登录用户可以通过使用恶意消息来干扰该功能。
      将服务作为 SYSTEM 运行的做法(即,服务通过调用 OpenWindowStationGetThreadDesktop 来支持交互式桌面)十分不可取。请注意,Windows 将来的版本可能会完全取消对交互式服务的支持。
      我们建议服务编写人员使用客户端/服务器技术(例如 RPC、套接字、命名管道或 COM)实现与来自某个服务的登录用户的交互,使用带 MB_SERVICE_NOTIFICATION 的 MessageBox 显示简单的状态。如果您的服务代码具有以下任何属性,请提高警惕:

    • 作为 LocalSystem 运行,并且服务在安全配置管理器中进行了标记(“登录为”->“允许服务与桌面交互”),或注册表项 -> HKLM\CCS\Services\MyService\Type & 0x0100 == 0x0100)
    • CreateService,并且 dwServiceType & SERVICE_INTERACTIVE_PROCESS == SERVICE_INTERACTIVE_PROCESS
    • 调用 MessageBox(),其中 uType and (MB_DEFAULT_DESKTOP_ONLY | MB_SERVICE_NOTIFICATION | MB_SERVICE_NOTIFICATION_NT3X) != 0
    • 调用 OpenDesktop("winsta0",...) 并在该桌面上创建用户界面
    • OpenDesktop 上调用 LoadLibrary/GetProcAddress
    小心 _alloca

      _alloca 函数可以在堆栈中分配动态内存。分配的空间将在调用函数退出时自动释放,而不只是在分配超出范围时释放。下面是使用 _alloca 的示例代码:

    void function(char *szData) {
        PVOID p = _alloca(lstrlen(szData));
       // 使用 p
    }
    

      如果攻击者提供一个比堆栈大小还要长的 szData,_alloca 会引发一个异常并导致应用程序停止。如果该代码位于服务器中,则情况会更糟。处理这种错误情况的正确方法是将对 _alloca 的调用打包在异常处理程序中,并在出现错误时重置堆栈。

    void function(char *szData) {
        __try {
            PVOID p = _alloca(lstrlen(szData));
            // 使用 p
        } __except ((EXCEPTION_STACK_OVERFLOW == GetExceptionCode()) ?
                       EXCEPTION_EXECUTE_HANDLER :
                       EXCEPTION_CONTINUE_SEARCH) {
            _resetstkoflw();
        } 
    }
    

    相关问题:ATL 转换宏

      您还应当小心某些调用 _alloca 的 ATL 字符串转换宏。这些宏包括 A2WW2ACW2CT 等。如果您的代码是服务器代码,则调用其中任何转换函数时都必须考虑数据的长度。这是不要轻易相信输入的又一个示例。如果攻击者向您的代码提供一个 10 MB 的字符串,便会摧毁堆栈并引发异常;或者如果未引发异常,则导致失败。所以千万不要这样做!

    发现缺陷

    没有人看出上星期的代码中的错误,但很多人已接近目标。其中的问题是为明文和密文使用了相同的缓冲区。您永远都不能这样做。

      乍看起来,使用相同的缓冲区存储明文,然后加密明文产生的密文似乎很好。在大多数情况下也是这样。但在多线程环境中就不是这样了。设想一下,您的代码中出现了一个“竞争状态”,而您却并不知道。(竞争状态是由对软件中的事件的相对时间产生意外的严格依赖而引起的。它们通常与同步错误一起出现。)坦白地说,您永远不会知道存在着严重的竞争状态,等知道时已经太晚了。请再考虑一下,您的应用程序的正常流程如下所示:

    1. 使用明文加载缓冲区。
    2. 加密缓冲区。
    3. 将缓冲区内容发送给接收者。

    这看起来很正常。但是,设想您有一个多线程应用程序,由于某种原因,最后两个步骤由于竞争状态而被交换:

    1. 使用明文加载缓冲区。
    2. 将缓冲区环境发送给接收者。
    3. 加密缓冲区。

      接收者只接收到一些明文!这是在 Internet Information Server 4.0 中修复的一个错误。在非常特殊的负载和极少数情况下,当使用安全套接字层 (SSL) 保护从服务器至用户的数据通道时,服务器可能会遵循此模式,并将一个未加密的数据信息包发送给用户。这种潜在问题的破坏很小;只向用户(或者可能是一个攻击者)发送了一个信息包。并且当用户接收到信息包时,客户端软件将断开连接。据说该问题已被 Microsoft 修复了。有关该缺陷的详细信息,请访问 http://www.microsoft.com/technet/treeview/default.asp?url=/technet/security/bulletin/ms99-053.asp(英文)。

    修复的方法是使用两个缓冲区。一个缓冲区用于明文,另一个用于密文,并确保密文在执行不同调用时已被清空。

    您能指出此代码中的错误吗?

    void ShuffleAndUpdate(char *szName, char *szPwd, 
                    DWORD index,
                    DWORD d) {
       DWORD dwArray[32]; 
       ZeroMemory(dwArray,sizeof(dwArray));
       BOOL fAllowAccess = FALSE;
       if (IsValidUser(szName,szPwd)) {
          fAllowAccess = TRUE;
          ShuffleArray(dwArray,szName);
             }
       dwArray[index]= d;
       if (fAllowAccess) {
          // 执行某些敏感的操作
       }
    }

    Michael Howard 是 Microsoft 的 Secure Windows Initiative 组的安全程序经理,是 Writing Secure Code 的作者之一,也是 Designing Secure Web-based for Applications for Windows 2000 的主要作者。他的主要工作就是确保人们设计、构建、测试和记录无缺陷的安全系统。他最喜欢的话是“尺有所短,寸有所长”。   

    2009年6月1日 2:36