以管理员身份安全地浏览 Web 和读取电子邮件

Michael Howard
Microsoft Security Engineering

**摘要:**Michael Howard 讨论了在使用任何工具访问 Internet 时,如何通过去掉不必要的管理特权来以管理员身份运行并安全地访问 Internet 数据。

下载 DropMyRights.msi 文件

“用管理帐户运行会危及您的计算机和数据”,对此我已经说过多次,但是我将再重申一遍。因此,当某个人说他们必须以管理员身份操作计算机时,我总是试图说服他们,从安全的角度来看,这不是正确的操作。说归说,我偶尔也会碰到理由充分的人。例如,我使用办公室内的某台计算机安装最新的 Windows 日更新版本,并且我需要以管理员身份安装 OS。但是,当我以管理员身份在该计算机上运行时,不会以任何方式读取电子邮件、浏览 Web 或访问 Internet,这一点很重要。我之所以不这样做,是因为 Web 是当今大多数令人讨厌的攻击的来源。

如果某个人确实希望浏览 Web,该怎么办?亦或是读取电子邮件,或者进行即时消息处理等,而且由于某种原因必须在管理上下文中运行,那该怎么办?如果您查看对计算机的主要威胁,就会发现它们都来自用户通过浏览器和电子邮件客户程序等工具与 Web 进行的交互。虽然的确存在非用户交互的攻击,如 Blaster (http://www.cert.org/advisories/CA-2003-20.html) 和 Lion (http://www.sans.org/y2k/lion.htm),但是这在一定程度上解释了我们为什么在 Windows XP SP2 中打开了防火墙!

有关以非管理员身份运行的最佳做法,我建议您查阅 Aaron Margosis 的网络日记,以便收集有关在 Windows 中以非管理员身份运行的提示。

*

本页内容

举例说明为何以管理员身份运行不好
更多详细信息
DropMyRights 应用程序

举例说明为何以管理员身份运行不好

某个令人讨厌的恶意软件 之所以会发挥作用,就是因为浏览 Web 的用户是管理员。一个很好的例子就是名为 W32.Beagle.AV@mm 的 Bagle/Beagle 蠕虫的最新变种。我建议您在计算机系统招致了该蠕虫后,仔细研究它的行为。Symantec 在 http://securityresponse.symantec.com/avcenter/venc/data/w32.beagle.av@mm.html 上有一篇不错的详细描述文章。我之所以说“招致”,是因为该恶意软件不是利用编码或设计缺陷,而是利用简单的人为错误来执行的。

该恶意软件执行的许多操作都需要管理权限,这些操作包括:

  • 在 system32 目录中创建文件。

  • 终止各个进程。

  • 禁用 Windows 防火墙。

  • 下载文件并将其写入到 system32 目录中。

  • 删除 HKLM 中的注册表值。

如果运行电子邮件客户程序的用户不是管理员,那么所有这些操作都会失败。

因此,如果您能够以非管理员身份浏览 Web、阅读电子邮件等(即使您需要以管理员身份执行普通的日常任务),不是很有用(亦或:更安全)吗?幸运的是,Windows XP 和 Windows Server 2003 以及更高版本都可以使用受限制的令牌来支持此功能。

更多详细信息

Windows XP 和 Windows Server 2003 以及更高版本都支持名为软件限制策略(又称为 SAFER)的功能,该功能允许用户或软件开发人员以较低的特权运行代码,而无需让用户在应用程序启动时输入凭据信息。例如,管理员可以在应用程序启动时,从应用程序的令牌中去掉某些 SID 和特权,从而以普通用户身份运行应用程序。一些应用程序(最典型的是面向 Internet 的应用程序,如 Web 浏览器、即时消息或电子邮件客户程序)绝对不能在管理上下文中运行。

DropMyRights 应用程序

DropMyRights 是一个非常简单的应用程序,它可以帮助那些必须以管理员身份运行的用户在更安全的上下文(非管理员的上下文)中运行应用程序。这是通过以下方法来完成的:提取当前用户的令牌,从该令牌中删除各种特权和 SID,然后使用该令牌启动另一个进程(如 Internet Explorer 或 Outlook)。该工具完全适用于 Mozilla 的 Firefox、Eudora 或 Lotus Notes 电子邮件。

代码再简单不过了。其核心代码如下:

//////////////////////////////////////////////////////////////////////////////////
DWORD wmain(int argc, wchar_t **argv) {
   DWORD fStatus = ERROR_SUCCESS;
   if (2 != argc && 3 != argc) {
      Usage();
      return ERROR_INVALID_PARAMETER;
   }
   // get the SAFER level
   DWORD hSaferLevel = SAFER_LEVELID_NORMALUSER;
   if (3 == argc && argv[2]) {
      switch(argv[2][0]) {
         case 'C' : 
         case 'c' :  hSaferLevel = SAFER_LEVELID_CONSTRAINED; 
                  break;
         case 'U' :
         case 'u' :   hSaferLevel = SAFER_LEVELID_UNTRUSTED;
                  break;
         default  :   hSaferLevel = SAFER_LEVELID_NORMALUSER;
                  break;
      }
   }
   // get the command line, and make sure it's not bogus
   wchar_t *wszPath = argv[1];
   size_t cchLen = 0;
   if (FAILED(StringCchLength(wszPath,MAX_PATH,&cchLen)))
      return ERROR_INVALID_PARAMETER;
    SAFER_LEVEL_HANDLE hAuthzLevel = NULL;
    if (SaferCreateLevel(SAFER_SCOPEID_USER,
                         hSaferLevel,
                         0, 
             &hAuthzLevel, NULL)) {
        //  Generate the restricted token we will use.
        HANDLE hToken = NULL;
        if (SaferComputeTokenFromLevel(
            hAuthzLevel,    // SAFER Level handle
            NULL,           // NULL is current thread token.
            &hToken,        // Target token
            0,              // No flags
            NULL)) {        // Reserved
         STARTUPINFO si;
         ZeroMemory(&si, sizeof(STARTUPINFO));
         si.cb = sizeof(STARTUPINFO);
         si.lpDesktop = NULL;
         // Spin up the new process
         PROCESS_INFORMATION pi;
         if (CreateProcessAsUser( 
            hToken,
            wszPath, NULL,
            NULL, NULL,
            FALSE, CREATE_NEW_CONSOLE,
            NULL, NULL,  
            &si, &pi)) {
               CloseHandle(pi.hProcess);
               CloseHandle(pi.hThread);
         } else {
            fStatus = GetLastError();
            fwprintf(stderr,L"CreateProcessAsUser failed (%lu)\n",fStatus);
         } 
      } else {
         fStatus = GetLastError();
      }
      SaferCloseLevel(hAuthzLevel);
   } else {
      fStatus = GetLastError();
   }
   return fStatus;
}

在本文开头提供了源代码和可执行文件。现在,让我们看一下如何将应用程序配置为以较低的特权运行应用程序。

安装

只需将 DropMyRights.exe 复制到某个文件夹中,然后,对于要以较低特权运行的每个应用程序,按照以下三部分中的步骤操作。

创建快捷方式

创建一个快捷方式,输入 DropMyRights.exe 作为目标可执行文件,然后输入您要以较低特权执行的应用程序的路径。

例如:

C:\warez\dropmyrights.exe "c:\program files\internet explorer\iexplore.exe"

图 1 显示了这在屏幕上的外观。

ms972827.secure11152004-fig1(zh-cn,MSDN.10).gif

1. 要以较低特权运行的应用程序的路径

更新快捷方式名称

接下来,更新快捷方式的名称,使其表示目标可执行文件,而非 dropmyrights。我通常会在应用程序名称的后面加上单词“(Safer)”,以表示该应用程序将在更安全的安全上下文中运行。也可以添加“(Non-admin)”,如图 2 所示。

secure11152004-fig2

2. 更新快捷方式名称

设置图标和运行模式

最后,在创建快捷方式之后,将快捷方式的 Run 选项设置为 Minimized,如果需要的话,还可以选择一个新图标。

ms972827.secure11152004-fig3(zh-cn,MSDN.10).gif

3. “Run” 选项设置为 “Minimized” ,或者更改图标

高级选项

DropMyRights 的参数如下所示:

DropMyRights {path} [N|C|U]

这些变量的含义解释如下:

  • Path 是要启动的应用程序的完整路径。

  • N 表示以普通用户身份运行应用程序。如果您未提供参数,这将是默认值。

  • C 表示以受限制的用户身份运行应用程序。

  • U 表示以不受信任的用户身份运行应用程序。有时,这将导致某些应用程序失败。

用来标识其中每个设置所执行操作的最佳方法就是查看得到的进程令牌。以下各表显示了对进程令牌进行的更改。

表 1. 管理帐户

SID

限制 SID

特权

DOMAIN\Domain Users 
Everyone 
BUILTIN\Administrators 
BUILTIN\Users 
NT AUTHORITY
\INTERACTIVE 
NT AUTHORITY
\Authenticated Users
\LOCAL

SeChangeNotifyPrivilege 
SeSecurityPrivilege
SeBackupPrivilege 
SeRestorePrivilege 
SeSystemtimePrivilege
SeShutdownPrivilege
SeRemoteShutdownPrivilege 
SeTakeOwnershipPrivilege
SeDebugPrivilege
SeSystemEnvironmentPrivilege 
SeSystemProfilePrivilege
SeProfileSingleProcessPrivilege
SeIncreaseBasePriorityPrivilege
SeLoadDriverPrivilege
SeCreatePagefilePrivilege
SeIncreaseQuotaPrivilege
SeUndockPrivilege 
SeManageVolumePrivilege 
SeCreateGlobalPrivilege
SeImpersonatePrivilege

表 2. 普通用户(“N”)

SID

限制 SID

特权

DOMAIN\Domain Users Everyone BUILTIN
\Administrators BUILTIN\Users NT AUTHORITY
\INTERACTIVE NT AUTHORITY
\Authenticated Users LOCAL

SeChangeNotifyPrivilege

表 3. 受限制的用户(“C”)

SID

限制 SID

特权

DOMAIN\Domain Users 
Everyone
BUILTIN\Administrators 
BUILTIN\Users
NT AUTHORITY\INTERACTIVE
NT AUTHORITY\Authenticated Users
LOCAL
DOMAIN\Domain Users 
Everyone
BUILTIN\Users
NT AUTHORITY\INTERACTIVE
NT AUTHORITY\Authenticated Users
LOCAL
NT AUTHORITY\RESTRICTED

SeChangeNotifyPrivilege

表 4. 不受信任的用户(“U”)

SID

限制 SID

特权

DOMAIN\Domain Users 
Everyone
BUILTIN\Administrators 
BUILTIN\Users
NT AUTHORITY\INTERACTIVE
NT AUTHORITY\Authenticated UsersLOCAL
NT AUTHORITY\RESTRICTED 
Everyone
NT AUTHORITY\INTERACTIVE
NT AUTHORITY\Authenticated Users
BUILTIN\Users

SeChangeNotifyPrivilege

红叉标记表示该 SID 仍在令牌中,但它是一个拒绝 SID。具有此特性的 SID 是一个仅限拒绝的 SID。当系统执行访问检查时,会检查是否有适用于该 SID 的拒绝访问的 ACE,但是它会忽略适用于该 SID 的允许访问的 ACE。

最大的特权和 SID 增量介于管理帐户和普通用户帐户之间。正如您所看到的那样,除 Bypass Traverse Checking 特权(又称为 SeChangeNotifyPrivilege)以外的所有特权都从令牌中去掉了。受约束的用户和不受信任的用户是普通用户的较小增量,而且您可能会开始看到一些应用程序因安全限制错误而失败。我的观点是,对于大多数任务使用 Normal(默认值),如果您认为可能会浏览恶意或危险的网站,则可以使用 Constrained。

发现安全缺陷

许多人都解决了我在上一篇文章中的错误。当代码只从该文件读取时,CreateFile 函数会为所有访问打开该文件。FILE_ALL_ACCESS 应当替换为 GENERIC_READ 或类似内容。这是不好的,因为很可能只有管理员(而非普通用户)才可以使用该代码。我经常看到此错误。

您是否能发现这个代码缺陷?前几天,我碰到过这个错误,它是某个 Java DNS 程序中的一个错误。这是一个有趣的错误,我在 C# 中重新编写并总结了这个缺陷。

Int16 req;
...
while (true) {
    getRequest();
    req++;
    arr[req] = DateTime.Now;
}

Michael Howard 是 Microsoft 安全工程设计组的高级安全项目经理,他与别人合著了 Writing Secure Code 一书(现在是第二版),并且他是 Designing Secure Web-based Applications for Windows 2000 一书的主要作者。他还与别人合编了 IEEE Security & Privacy Magazine 中的 Basic Training。他致力于确保人们所设计、构建、测试和记录的系统符合安全要求。他最喜欢的一句话是“一人之工具,他人之凶器”。

转到原英文页面