检查非英语安装的Windows中管理员用户登录

8

我有一些小问题...我的程序将用户列表存储在数据库中,并在程序启动时比较用户是否在列表中或者是管理员,然后才允许他们使用它。目前,我用来检查用户是否为管理员的方法只是通过将用户名与一个名为“ADMINISTRATOR”的字符串常量进行比较。这种方法在非英语系统上能行吗?例如,Windows是否使用特定于语言的版本的“administrator”?或者,我可以使用枚举版本的Admin用户来检查,而不是使用我的“ADMINISTRATOR”字符串吗?(你知道,就像Windows文件夹被枚举一样)。顺便说一下,我正在使用Delphi 2009。谢谢!


管理员名称可能会更改,有些系统甚至可以禁用它,因为策略要求每个管理员使用自己的帐户登录。 - user160694
很好你问了这个问题! - ChristianWimmer
5个回答

7

新闻

2010年,@ChristianWimmer 批评了我的编码风格。现在,两年后,我不得不再次在我的程序中使用这个函数。因此,我决定改进这个函数的编码风格。

概述

我为您提供了我私人库中的一小部分内容以方便测试。要测试访问令牌的用户帐户是否是本地管理员组的成员,请将 JwaWinNTeWellKnownSidType 参数设置为 WinBuiltinAdministratorsSid。请注意,它需要JEDI API Libray,因为 Delphi 的 Windows.pas 单元没有定义 CreateWellKnownSid()

实施

//------------------------------------------------------------------------------
// Purpose: Tests whether user account of the access token is a member of the
//   specified well known group, and report its elevation type.
// Parameter:
//   hToken [in,opt]
//     A handle to an access token having TOKEN_QUERY and TOKEN_DUPLICATE
//     access. If hToken is 0: if it is an impersonation token, the access token
//     of the calling thread is used; otherwise, the access token associated
//     with the process is used.
//   eWellKnownSidType [in]
//     Member of the WELL_KNOWN_SID_TYPE enumeration that specifies what Sid the
//     function will identify.
//   pDomainSid [in,opt]
//     A pointer to a SID that identifies the domain to use when identifying the
//     Sid. Pass nil to use the local computer.
//   peElevType [out,opt]
//     A pointer to a variable that receives the following elevation type of the
//     access token:
//       - TokenElevationTypeDefault: The access token does not have a linked
//         token. This value is reported under Windows prior to Windows Vista.
//       - TokenElevationTypeFull: The access token is an elevated token.
//       - TokenElevationTypeLimited: The access token is a limited token.
// Return value:
//   - True if user account of the access token is a member of the well known
//     group specified in eWellKnownSidType parameter.
//   - False, otherwise. To get error information, call GetLastError().
// Remarks:
//   To test whether user account of the access token is a member of local
//   administrators group, pass JwaWinNT.WinBuiltinAdministratorsSid to
//   eWellKnownSidType parameter.
// References:
//   - How To Determine Whether a Thread Is Running in User Context of
//     Local Administrator Account [MSDN]
//------------------------------------------------------------------------------
function Inu_IsMemberOfWellKnownGroup(const hToken: Windows.THandle;
    const eWellKnownSidType: JwaWinNT.WELL_KNOWN_SID_TYPE;
    const pDomainSid: JwaWinNT.PSID=nil;
    peElevType: PTokenElevationType=nil): Boolean;
var
  hAccessToken: Windows.THandle;
  rOSVerInfo: Windows.OSVERSIONINFO;
  eTET: Windows.TTokenElevationType;
  iReturnLen: Windows.DWORD;
  hTokenToCheck: Windows.THandle;
  iSidLen: Windows.DWORD;
  pGroupSid: JwaWinNT.PSID;
  bMemberOfWellKnownGroup: Windows.BOOL;
begin
  Result := False;
  hAccessToken := 0;
  hTokenToCheck := 0;
  pGroupSid := nil;
  try
    if hToken = 0 then begin // If the caller doesn't supply a token handle,
      // Get the calling thread's access token
      if not Windows.OpenThreadToken(Windows.GetCurrentThread(),
          Windows.TOKEN_QUERY or Windows.TOKEN_DUPLICATE,
          True, hAccessToken) then begin
        if Windows.GetLastError() <> Windows.ERROR_NO_TOKEN then
          Exit();
        // If no thread token exists, retry against process token
        if not Windows.OpenProcessToken(Windows.GetCurrentProcess(),
            Windows.TOKEN_QUERY or Windows.TOKEN_DUPLICATE, hAccessToken) then
          Exit();
      end;
    end
    else // If the caller supplies a token handle,
      hAccessToken := hToken;
    // Determine whether the system is running Windows Vista or later because
    // because they support linked tokens, previous versions don't.
    rOSVerInfo.dwOSVersionInfoSize := SizeOf(Windows.OSVERSIONINFO);
    if not Windows.GetVersionEx(rOSVerInfo) then
      Exit();
    if rOSVerInfo.dwMajorVersion >= 6 then begin
      // Retrieve information about the elevation level of the access token
      if not Windows.GetTokenInformation(hAccessToken,
          Windows.TokenElevationType, @eTET,
          SizeOf(Windows.TTokenElevationType), iReturnLen) then
        Exit();
      // If the access token is a limited token, retrieve the linked token
      // information from the access token.
      if eTET = Windows.TokenElevationTypeLimited then begin
        if not Windows.GetTokenInformation(hAccessToken,
            Windows.TokenLinkedToken, @hTokenToCheck,
            SizeOf(Windows.TTokenLinkedToken), iReturnLen) then
          Exit();
      end;
      // Report the elevation type if it is wanted
      if Assigned(peElevType) then
        peElevType^ := eTET;
    end
    else begin // if rOSVerInfo.dwMajorVersion < 6
      // There is no concept of elevation prior to Windows Vista
      if Assigned(peElevType) then
        peElevType^ := Windows.TokenElevationTypeDefault;
    end;
    // CheckTokenMembership() requires an impersonation token. If we just got a
    // linked token, it is already an impersonation token. Otherwise, duplicate
    // the original as an impersonation token for CheckTokenMembership().
    if (hTokenToCheck = 0) and (not Windows.DuplicateToken(hAccessToken,
        Windows.SecurityIdentification, @hTokenToCheck)) then
      Exit();
    // Allocate enough memory for the longest possible Sid
    iSidLen := JwaWinNT.SECURITY_MAX_SID_SIZE;
    pGroupSid := JwaWinNT.PSid(Windows.LocalAlloc(Windows.LMEM_FIXED, iSidLen));
    if not Assigned(pGroupSid) then
      Exit();
    // Create a Sid for the predefined alias as specified in eWellKnownSidType
    if not JwaWinBase.CreateWellKnownSid(eWellKnownSidType, pDomainSid,
        pGroupSid, iSidLen) then
      Exit();
    // Now, check presence of the created Sid in the user and group Sids of the
    // access token. In other words, it determines whether the user is a member
    // of the well known group specified in eWellKnownSidType parameter.
    if not JwaWinBase.CheckTokenMembership(hTokenToCheck, pGroupSid,
        bMemberOfWellKnownGroup) then
      Exit();
    Result := bMemberOfWellKnownGroup;
  finally
    // Close the access token handle
    if hAccessToken <> 0 then
      Windows.CloseHandle(hAccessToken);
    // Close the new duplicate token handle if exists
    if (hTokenToCheck <> 0) then
      Windows.CloseHandle(hTokenToCheck);
    // Free the allocated memory for the Sid created by CreateWellKnownSid()
    if Assigned(pGroupSid) then
      Windows.LocalFree(Windows.HLOCAL(pGroupSid));
  end;
end; // endfunction Inu_IsMemberOfWellKnownGroup
//==============================================================================

3
+1:使用JEDI API -1:使用goto:这在这里真的不必要,看起来你是一对一地转换了C源代码?它使得代码非常难以阅读。(我是一个try/finally的追随者) +1:添加了大量注释。 你检查了LocalFree和CloseHandle的返回值! 在我看来这没有意义。如果你已经有了结果bMemberOfWellKnownGroup,那么你只需要调用free和close handle函数就可以了,然后你就可以高兴了。如果它们失败了,那么就会出现一个实际的问题,尽管被掩盖了。通常我们必须使用raiseLastOsError来检查每个Free和Close调用。 - ChristianWimmer
1
如果不是CloseHandle(hAccessToken)的话,那就退出(); Result := bMemberOfWellKnownGroup; 即使bMemberOf...为真,这个CloseHandle也会改变函数的结果值。CloseHandle失败了,你无法对此做任何事情,但函数仍然返回FALSE。 Try/finally不是用于WinAPI调用,而是用于EXIT()调用,如果它们失败了。这样你就可以直接退出,最终将被调用。这里不需要使用goto。 - ChristianWimmer
1
此外,如果任何正常的winapi调用失败,您只需退出并返回false。在某种程度上,您将所有错误映射到FALSE,这也是正确的返回值。那么如何区分它们呢?因此解决方案是使用异常。如果不是wincall,则引发LastOsError。此外,try/finally也非常方便。 - ChristianWimmer
1
然而,Try/Finally 永远不知道 API 函数是否调用成功。在纯 Winapi 代码中没有任何 if 和 try..finally,即使 API 函数失败,剩余的代码也将始终执行。你真的读了我的评论吗?我已经说过了。在我的看法中,应该使用 Delphi 中的 exit 结合 try/finally 替换 goto。旧的 C 语言不支持异常处理,因此许多代码不使用这种技术,这对他们来说是可以的。在我的职业生涯中,我阅读了很多这样的 C 代码,我总是很高兴能够阅读 Delphi 代码。 - ChristianWimmer
-1 表示糟糕的错误处理和忽略改进意见/建议。 - Remko
显示剩余21条评论

7
不,不要那样做。这肯定会出问题。你可以获取用户所属的所有组的列表,并检查其中是否有一个SID是S-1-5-32-544,它是管理员组的SID。有一份已知的SID清单。还有一个原始管理员帐户的SID。
这是清单链接:http://support.microsoft.com/kb/243330

1
-1:在>= Vista中,对成员身份的简单搜索将返回错误的结果,因为Administrators中的成员通常在令牌中受到限制。该成员资格是可用的,但Windows将检查令牌中成员资格的DENY_ONLY_SID标志,并仍然拒绝访问。受限令牌自Windows 2000以来就已经可用,但很少被使用。 - ChristianWimmer
您的建议在这里得到了实现: http://www.techtricks.com/delphi/isadmin.php 在>= VISTA上是不正确的(并且在< Vista上仅仅是偶然起作用)。 CreateRestrictedToken函数:msdn.microsoft.com/en-us/library/aa446583%28VS.85%29.aspx 最低支持客户端:Windows 2000 Professional - ChristianWimmer

2
这是来自JEDI API&WSCL的JwsclToken.pas的摘录。 这两个函数都执行相同的检查,但方式不同。你看到代码使用很少吗?同样的代码在普通的WinAPI中至少会大5倍。 当然,你可以直接从该单元调用这些函数。不需要在此处复制
function JwCheckAdministratorAccess: boolean;
var
  SD: TJwSecurityDescriptor;
begin
  if not Assigned(JwAdministratorsSID) then
    JwInitWellKnownSIDs;

  SD := TJwSecurityDescriptor.Create;
  try
    SD.PrimaryGroup := JwNullSID;
    SD.Owner   := JwAdministratorsSID;
    SD.OwnDACL := True;

    SD.DACL.Add(TJwDiscretionaryAccessControlEntryAllow.Create(nil,
      [], STANDARD_RIGHTS_ALL, JwAdministratorsSID, False));

    Result := TJwSecureGeneralObject.AccessCheck(SD, nil,
      STANDARD_RIGHTS_ALL, TJwSecurityGenericMapping);
  finally
    FreeAndNil(SD);
  end;
end;

function JwIsMemberOfAdministratorsGroup: boolean;
var
  Token: TJwSecurityToken;
begin
  Token := TJwSecurityToken.CreateTokenEffective(TOKEN_READ or
    TOKEN_DUPLICATE);
  try
    Token.ConvertToImpersonatedToken(SecurityImpersonation, MAXIMUM_ALLOWED);
    Result := Token.CheckTokenMembership(JwAdministratorsSID)
  finally
    FreeAndNil(Token);
  end;
end;

1
在第二个函数中,你不应该进行Assigned(JwAdministratorsSID)检查吗?或者它在之前的语句中初始化了吗? - The_Fox
1
好的观点。 虽然是由用户完成的,但仍应进行检查。我认为我在另一个版本中添加了一个检查。我只是发现我捕获了一个较旧的版本。 现在,你明白为什么不应该不加思考地复制粘贴或直接使用库了吧。我将在Subversion分支存储库中修复它,但在这里保留它。 - ChristianWimmer
Jedi API使用了大量的内联汇编,而这在Delphi的win64编译器中是不被支持的。EMB仅提供了Windows API的子集(我只有XE7)。有人有什么建议吗? - Sender

1

这个问题因Windows版本而异...在Vista之前...管理员用户名是使用主要的Windows语言...例如,西班牙语中是Administrador

在Vista之后,没有管理员用户。您应该存储并检查用户权限。

我发现了这个IsAdmin函数,您可能也会觉得它有用...


1
-1:在后续版本中有一个管理员,但它被停用了。所有属于管理员组的成员都是事实上的管理员。IsAdmin函数是不正确的,它没有考虑到受限令牌,并且只检查令牌中管理员组的可用性,但忽略了DENY_ONLY标志。这些源代码在互联网上广泛流传,因为它们只是从一个旧的、错误的和修订过的MSDN文章中复制而来。 - ChristianWimmer

1

CreateWellKnownSid函数可用。

但是明确检查管理员帐户可能不是一个好主意。只需执行操作并请求提升,如果出现“访问被拒绝”的错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接