检测是否以管理员身份运行并具有提升的权限?

92

我有一个应用程序,需要检测它是否以提升的权限运行。我目前已经设置了以下代码:

static bool IsAdministrator()
{
    WindowsIdentity identity = WindowsIdentity.GetCurrent();
    WindowsPrincipal principal = new WindowsPrincipal(identity);
    return principal.IsInRole (WindowsBuiltInRole.Administrator);
}

这段代码可以检测用户是否是管理员,但是在以管理员身份运行但没有提升权限的情况下将无法工作(例如在vshost.exe中)。

如何确定当前已经有管理员权限或者能够获取管理员权限?

10个回答

60

试试这个:

using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class UacHelper
{
    private const string uacRegistryKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
    private const string uacRegistryValue = "EnableLUA";

    private static uint STANDARD_RIGHTS_READ = 0x00020000;
    private static uint TOKEN_QUERY = 0x0008;
    private static uint TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY);

    [DllImport("advapi32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool GetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, IntPtr TokenInformation, uint TokenInformationLength, out uint ReturnLength);

    public enum TOKEN_INFORMATION_CLASS
    {
        TokenUser = 1,
        TokenGroups,
        TokenPrivileges,
        TokenOwner,
        TokenPrimaryGroup,
        TokenDefaultDacl,
        TokenSource,
        TokenType,
        TokenImpersonationLevel,
        TokenStatistics,
        TokenRestrictedSids,
        TokenSessionId,
        TokenGroupsAndPrivileges,
        TokenSessionReference,
        TokenSandBoxInert,
        TokenAuditPolicy,
        TokenOrigin,
        TokenElevationType,
        TokenLinkedToken,
        TokenElevation,
        TokenHasRestrictions,
        TokenAccessInformation,
        TokenVirtualizationAllowed,
        TokenVirtualizationEnabled,
        TokenIntegrityLevel,
        TokenUIAccess,
        TokenMandatoryPolicy,
        TokenLogonSid,
        MaxTokenInfoClass
    }

    public enum TOKEN_ELEVATION_TYPE
    {
        TokenElevationTypeDefault = 1,
        TokenElevationTypeFull,
        TokenElevationTypeLimited
    }

    public static bool IsUacEnabled
    {
        get
        {
            RegistryKey uacKey = Registry.LocalMachine.OpenSubKey(uacRegistryKey, false);
            bool result = uacKey.GetValue(uacRegistryValue).Equals(1);
            return result;
        }
    }

    public static bool IsProcessElevated
    {
        get
        {
            if (IsUacEnabled)
            {
                IntPtr tokenHandle;
                if (!OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_READ, out tokenHandle))
                {
                    throw new ApplicationException("Could not get process token.  Win32 Error Code: " + Marshal.GetLastWin32Error());
                }

                TOKEN_ELEVATION_TYPE elevationResult = TOKEN_ELEVATION_TYPE.TokenElevationTypeDefault;

                int elevationResultSize = Marshal.SizeOf((int)elevationResult);
                uint returnedSize = 0;
                IntPtr elevationTypePtr = Marshal.AllocHGlobal(elevationResultSize);

                bool success = GetTokenInformation(tokenHandle, TOKEN_INFORMATION_CLASS.TokenElevationType, elevationTypePtr, (uint)elevationResultSize, out returnedSize);
                if (success)
                {
                    elevationResult = (TOKEN_ELEVATION_TYPE)Marshal.ReadInt32(elevationTypePtr);
                    bool isProcessAdmin = elevationResult == TOKEN_ELEVATION_TYPE.TokenElevationTypeFull;
                    return isProcessAdmin;
                }
                else
                {
                    throw new ApplicationException("Unable to determine the current elevation.");
                }
            }
            else
            {
                WindowsIdentity identity = WindowsIdentity.GetCurrent();
                WindowsPrincipal principal = new WindowsPrincipal(identity);
                bool result = principal.IsInRole(WindowsBuiltInRole.Administrator);
                return result;
            }
        }
    }
}

8
如果要运行的账户是本地管理员,则函数有效,但如果使用域管理员,则变量isProcessAdmin返回false。但是,当提升权限时(在Windows中创建文件夹,以管理员身份运行等),UAC接受域管理员为有效管理员...如何修改您的函数以考虑这种情况? - VSP
1
您可能还需要考虑一下,如果该账户是内置管理员,则UAC默认会提升,因此在这种情况下IsProcessElevated将返回false(因为IsUacEnabled为true且elevationResult为TokenElevationTypeDefault),即使进程在未提示用户的情况下以提升模式运行。换句话说,该账户已被提升,进程以默认提升类型运行。 - Mister Cook
2
这段代码需要以下的 using 语句:using System.Diagnostics; using System.Runtime.InteropServices; using System.Security.Principal;同时,它似乎也在这里镜像:http://www.sadrobot.co.nz/2011/06/20/how-to-check-if-the-current-user-is-an-administrator-even-if-uac-is-on/ - Scott Solmer
这在Windows 8上引发了一个异常,在Marshal.SizeOf((int)elevationResult)处。我还不确定原因。异常消息是:找不到方法。在:Int32 System.Runtime.InteropServices.Marshal.SizeOf(!!0). - CularBytes
TokenElevationTypeLimited怎么办?是否应该将isProcessAdmin设置为true? - anon
这段代码在使用Visual Studio Setup项目自定义操作时表现得非常好。我试图转移到InstallShield安装程序基本MSI项目,并调用相同的上述代码,但是在同一台机器上以相同的用户运行时,给出了不同的值。当在VS和IS安装程序上运行时,elevationResult值不同,而且IsAdministrator也不同。VS Installer:elevationResult值为TokenElevationTypeDefault,IsAdministrator为true。 IS Installer:elevationResult值为TokenElevationTypeFull,IsAdministrator为false。 有人遇到过同样的问题吗?有什么想法可以解释这种不同的行为吗? - Santhosh

39

(问题提出后六年)新答案

免责声明:这只是在我特定的操作系统、设置和用户下运行的一些东西。

using System.Security.Principal;

// ...

    static bool IsElevated
    {
      get
      {
        return WindowsIdentity.GetCurrent().Owner
          .IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid);
      }
    }
当我以“管理员身份运行”时,属性get访问器返回true。当以普通方式运行(即使我的用户“是”管理员,只是没有以“管理员身份”运行此特定应用程序),它会返回false。这似乎比其他许多答案简单得多。我不知道是否存在可能失败的情况。
PS!这也看起来没问题:
    static bool IsElevated
    {
      get
      {
        var id = WindowsIdentity.GetCurrent();
        return id.Owner != id.User;
      }
    }

1
谢谢!- 我在PowerShell中使用了这个[Security.Principal.WindowsIdentity]::GetCurrent().Owner.IsWellKnown([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid) - Lewis
4
这将无法区分“半提升”进程和正确提升的进程:IsElevated 可能会返回假,但进程仍可能在高完整性级别下运行。真正的非提升进程具有中等完整性级别。对于99%的应用程序来说,这可能是不相关的,但值得一提的是,像 Process Hacker 这样的工具仍可能声明这种进程被提升了。通常情况下,您看不到“半提升”进程;它可能发生在某人未能正确启动非提升子进程时。 - Roman Starkov
1
@StingyJack 这个问题太大了,无法在评论中回答,但可以参考这里和这里。 - Roman Starkov
感谢@RomanStarkov和Jeppe,我在这个行业已经有20多年了,但我之前从未听说过这个术语。 - StingyJack
1
这并不完全正确,安全令牌的所有者通常是管理员,如果它是一个提升的令牌,但实际上并不一定是这样。此外,您可以拥有由管理员拥有的非提升安全令牌。真正测试令牌是否提升的方法是检查ACL中授予访问SID列表中是否有管理员SID,因为这才是使其通过ACL测试的实际原因。 - Benj
显示剩余3条评论

22

这是一个修改过的版本,包括正确处理资源释放和域管理员处理等内容。原答案可以在此处查看。

public static class UacHelper
{
    private const string uacRegistryKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
    private const string uacRegistryValue = "EnableLUA";

    private static uint STANDARD_RIGHTS_READ = 0x00020000;
    private static uint TOKEN_QUERY = 0x0008;
    private static uint TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY);

    [DllImport("advapi32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool CloseHandle(IntPtr hObject);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool GetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, IntPtr TokenInformation, uint TokenInformationLength, out uint ReturnLength);

    public enum TOKEN_INFORMATION_CLASS
    {
        TokenUser = 1,
        TokenGroups,
        TokenPrivileges,
        TokenOwner,
        TokenPrimaryGroup,
        TokenDefaultDacl,
        TokenSource,
        TokenType,
        TokenImpersonationLevel,
        TokenStatistics,
        TokenRestrictedSids,
        TokenSessionId,
        TokenGroupsAndPrivileges,
        TokenSessionReference,
        TokenSandBoxInert,
        TokenAuditPolicy,
        TokenOrigin,
        TokenElevationType,
        TokenLinkedToken,
        TokenElevation,
        TokenHasRestrictions,
        TokenAccessInformation,
        TokenVirtualizationAllowed,
        TokenVirtualizationEnabled,
        TokenIntegrityLevel,
        TokenUIAccess,
        TokenMandatoryPolicy,
        TokenLogonSid,
        MaxTokenInfoClass
    }

    public enum TOKEN_ELEVATION_TYPE
    {
        TokenElevationTypeDefault = 1,
        TokenElevationTypeFull,
        TokenElevationTypeLimited
    }

    public static bool IsUacEnabled
    {
        get
        {
            using (RegistryKey uacKey = Registry.LocalMachine.OpenSubKey(uacRegistryKey, false))
            {
                bool result = uacKey.GetValue(uacRegistryValue).Equals(1);
                return result;
            }
        }
    }

    public static bool IsProcessElevated
    {
        get
        {
            if (IsUacEnabled)
            {
                IntPtr tokenHandle = IntPtr.Zero;
                if (!OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_READ, out tokenHandle))
                {
                    throw new ApplicationException("Could not get process token.  Win32 Error Code: " +
                                                   Marshal.GetLastWin32Error());
                }

                try
                {
                    TOKEN_ELEVATION_TYPE elevationResult = TOKEN_ELEVATION_TYPE.TokenElevationTypeDefault;

                    int elevationResultSize = Marshal.SizeOf(typeof(TOKEN_ELEVATION_TYPE));
                    uint returnedSize = 0;

                    IntPtr elevationTypePtr = Marshal.AllocHGlobal(elevationResultSize);
                    try
                    {
                        bool success = GetTokenInformation(tokenHandle, TOKEN_INFORMATION_CLASS.TokenElevationType,
                                                           elevationTypePtr, (uint) elevationResultSize,
                                                           out returnedSize);
                        if (success)
                        {
                            elevationResult = (TOKEN_ELEVATION_TYPE) Marshal.ReadInt32(elevationTypePtr);
                            bool isProcessAdmin = elevationResult == TOKEN_ELEVATION_TYPE.TokenElevationTypeFull;
                            return isProcessAdmin;
                        }
                        else
                        {
                            throw new ApplicationException("Unable to determine the current elevation.");
                        }
                    }
                    finally
                    {
                        if (elevationTypePtr != IntPtr.Zero)
                            Marshal.FreeHGlobal(elevationTypePtr);
                    }
                }
                finally
                {
                    if (tokenHandle != IntPtr.Zero)
                        CloseHandle(tokenHandle);
                }
            }
            else
            {
                WindowsIdentity identity = WindowsIdentity.GetCurrent();
                WindowsPrincipal principal = new WindowsPrincipal(identity);
                bool result = principal.IsInRole(WindowsBuiltInRole.Administrator) 
                           || principal.IsInRole(0x200); //Domain Administrator
                return result;
            }
        }
    }
}

这完全取决于您正在运行服务的用户。您是想检测服务是否作为本地系统、本地服务、网络服务或Windows用户运行吗?检测“管理状态”无法区分本地系统和本地服务之间的差异,您需要通过直接检查运行进程的用户来进行测试。 - Scott Chamberlain
我从客户那里收到了错误信息,自动记录了下来。目前我所知道的是它在他/她的电脑上无法运行。截至目前,我所知道的只有它是Windows 8,64位,2个处理器。我怀疑这是由于旧版本的.NET引起的,因为我在其他地方看到Marshal.SizeOf仅支持4.5.1及以上版本。 - CularBytes
1
哦,你正在使用4.5.1版本进行编译,因此它试图使用此重载(https://msdn.microsoft.com/en-us/library/dn261467(v=vs.110).aspx),但用户没有安装 4.5.1版本。尝试用Marshal.SizeOf(typeof(TOKEN_ELEVATION_TYPE))来替换它。 - Scott Chamberlain
感谢@ScottChamberlain。用户已更新到我所针对的4.5.2版本,并解决了问题。这个解决方案可能对向后兼容性很有帮助。 - CularBytes
2
@ScottChamberlain 在32位应用程序.NET 4.0中,int elevationResultSize = Marshal.SizeOf(typeof(TOKEN_ELEVATION_TYPE))会抛出一个ArgumentException异常。但是,int elevationResultSize = Marshal.SizeOf((int)elevationResult)可以正常工作。 - Martin Braun
显示剩余5条评论

16

NB:这是一个旧答案,当问题最初编写时更为相关,它是如何在托管C++(即C++的.NET版本)中解决此问题的,因为在那时用C#解决这个问题要困难得多。您可以在单独的程序集中构建此项,然后像平常一样在C#项目中引用它。

CodePlex项目UAChelper有代码检查UserAccountControl.cpp中的提升UserAccountControl::IsUserAdmin,它检查UAC是否启用,然后检查进程是否已提升。

bool UserAccountControl::IsCurrentProcessElevated::get()
{
    return GetProcessTokenElevationType() == TokenElevationTypeFull;    //elevated
}

从函数中:

int UserAccountControl::GetProcessTokenElevationType()
{
    HANDLE hToken;
    try
    {
        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
            throw gcnew Win32Exception(GetLastError());

        TOKEN_ELEVATION_TYPE elevationType;
        DWORD dwSize;
        if (!GetTokenInformation(hToken, TokenElevationType, &elevationType, sizeof(elevationType), &dwSize))
            throw gcnew Win32Exception(GetLastError());

        return elevationType;
    }
    finally
    {
        CloseHandle(hToken);
    }
}

这似乎是C++代码,而不是所请求的C#代码。 - John Lord
@JohnLord 这是托管的C++ (.net),而不是纯粹的C++。我相信在我14年前写下这个答案时,用C#来实现并不像现在这么容易/可能。我无法确定了,因为我已经没有那个时期的工具了。但你的观点是合理的。 - Preet Sangha
好的,我并没有投反对票或其他的行为。然而,如果您的解决方案使用了管理型C语言,您应该解释如何将其包含到C#项目中。我已经用C#编程5年了,但我不知道该如何做。我认为您必须将其作为单独的项目包含进来,但我不确定。 - John Lord

11

在 .net Framework 4.5 中,我发现另一种适合我的方法。关于下面的脚本,可以在这里找到这里(德语)。

 rem --- Admintest.bat ---
 whoami /groups | find "S-1-5-32-544" > nul
 if errorlevel 1 goto ende
 echo Benutzer %username% ist lokaler Administrator.
 :ende

在C#中,它看起来像这样:

    private bool IsAdmin
    {
        get
        {
            WindowsIdentity identity = WindowsIdentity.GetCurrent();
            if (identity != null)
            {
               WindowsPrincipal principal = new WindowsPrincipal(identity);
               List<Claim> list = new List<Claim>(principal.UserClaims);
               Claim c = list.Find(p => p.Value.Contains("S-1-5-32-544"));
               if (c != null)
                  return true;
            }
            return false;
        }
    }

但是在 .net < 4.5 版本中,WindowsPrincipal 类并不包含 UserClaims 属性,我没有找到获取这些信息的方法。


3
仅确定账户是否为管理员,而不是应用程序是否被提升。 - CularBytes
要在 .Net < 4.5 中检查用户是否是 S-1-5-32-544(管理员组)的成员,您可以直接使用原始问题中的代码。只有当进程正在以提升的方式运行且用户在该组中时,主体才会成为管理员组的成员。如果进程未被提升,则主体将不在该组中。 - Adam
1
不错的答案,简短高效,我给了你一个+1。顺便说明一下,我把它做成了我的代码属性 (private bool IsAdmin{ get { ... } }),这样如果你调用 IsAdmin 就不需要括号了。 - Matt

5
使用TokenElevationType是可行的,但如果您在管理员组SID上PInvoke CheckTokenMembership(),则您的代码也可以在UAC关闭时和2000 / XP / 2003上工作,并且还将处理拒绝SID。
还有一个IsUserAnAdmin()函数可以为您执行CheckTokenMembership检查,但MSDN表示它可能不会永远存在。

当受到UAC的限制时,我发现CheckTokenMembership不够用 - https://github.com/chocolatey/choco/blob/25cd37f3259383e2bcd8903b1272c17befac2232/src/chocolatey/infrastructure/information/ProcessInformation.cs#L51 返回false。请检查代码(我正在替换它),并查看来自Win2012R2的输出 - http://i.imgur.com/gX3JP0W.png - ferventcoder
@ferventcoder 这取决于您真正想知道什么; 用户现在是否为提升的管理员或者他们可以在需要时提升。例如,您可以检查TOKEN_ELEVATION_TYPE并最终得到以下代码:bool is_or_can_elevate() { return process_is_elevated() || TokenElevationTypeLimited == get_current_token_elevation_type(); }。另一个问题是“提升”一词的定义并不相同,您可以拥有带有“管理员:”前缀的控制台窗口,同时处于高完整性级别以下!TokenElevation并不总是匹配TokenIntegrityLevel。 - Anders
有趣的时光。我想知道是否有一个高级进程,与用户是否为管理员无关。这是我的结论。如果不正确,请告诉我应该去哪里 - https://github.com/chocolatey/choco/issues/77#issuecomment-73523774 和 https://github.com/chocolatey/choco/commit/ba2350662154de12e23221551b6338c6d2ae9760 - ferventcoder
@ferventcoder is_processes_elevated() { return CheckTokenMembership/IsInRole || TokenElevation/TokenIntegrityLevel>=0x3000; } 在Vista以下或关闭UAC时,使用CheckTokenMembership或IsInRole。根据您想要检测的提升方式,使用TokenElevation或TokenIntegrityLevel>=0x3000。我认为conhost.exe使用TokenElevation,但在我看来它是有问题的,您应该检查实际级别...(您需要特殊工具生成一个欺骗TokenElevation的令牌)另请参阅:https://windowssucks.wordpress.com/2011/02/07/uac-are-you-high/# - Anders
...甚至这种说法也有点不准确,理论上可能存在一个提升的令牌而不在管理员组中。因此,如果您只想让管理员组中的人员并确保他们被提升,则应执行CheckTokenMembership/IsInRole检查,然后Token*检查应失败(无UAC)_或_其值应指示提升...当然,这取决于您实际想要访问什么。您可能需要成为系统/管理员并提升,或者只需提升,这取决于ACL。 - Anders
显示剩余2条评论

5

这个答案 存在一些问题。首先,它不能获取任何以管理员身份运行的系统进程(例如NT-Authority/SYSTEM下的进程)。

下面的代码示例解决了所有问题(检测本地管理员、域管理员和本地系统管理员)。

如果你只想获取当前进程,请使用 Process.GetCurrentProcess().Handle 替换 pHandle

注意: 你必须具有某些特权才能运行它。(每个 AdminProcess 都拥有这些特权,但需要先激活它们;服务默认已激活这些特权)

internal static bool IsProcessElevatedEx(this IntPtr pHandle) {

        var token = IntPtr.Zero;
        if (!OpenProcessToken(pHandle, MAXIMUM_ALLOWED, ref token))
                throw new Win32Exception(Marshal.GetLastWin32Error(), "OpenProcessToken failed");

        WindowsIdentity identity = new WindowsIdentity(token);
        WindowsPrincipal principal = new WindowsPrincipal(identity);
        bool result = principal.IsInRole(WindowsBuiltInRole.Administrator)
                   || principal.IsInRole(0x200); //Domain Administrator
        CloseHandle(token);
        return result;
}

3

我认为还有一个问题。我查看了您提供的解决方案,不得不说在安装Windows 7并以管理员身份登录时,检查不起作用。Windows从未返回进程以提升模式运行的信息。因此,顺序如下:

if (IsUacEnabled)
    return IsProcessInElevatedMode();
return IsUserAdmin();

当以管理员身份登录但进程具有执行系统操作的所有权限(例如停止系统服务)时,不返回true。

工作序列如下:

if (IsUserAdmin())
    return true;

if (IsUacEnabled)
    return IsProcessInElevatedMode();

return false;

您应该首先检查进程是否在管理员上下文中运行。

附加信息:
IsUacEnabled() - checks if the UAC has been enabled in the system (Windows)
IsProcessInElevatedMode() - checks if the process is run in an elevated mode
IsUserAdmin() - checks if the current user has an Administrtor role

所有这些方法都已经在之前的帖子中进行了描述。

2
这不是一个答案,而是对另一篇帖子的评论。 - user585968

1

使用UACHelper NuGet包: https://www.nuget.org/packages/UACHelper/

if (UACHelper.IsElevated)
    // something
else
    // something else

还有许多其他属性可以用来检测用户是否是管理员,或进程是否在UAC虚拟化下运行,或桌面所有者是否为进程所有者。(从受限帐户运行)

查看读我文件获取更多信息。


0

我正在使用这段代码,而且效果很好:


bool runningAsAdmin = WindowsIdentity.GetCurrent().Owner.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid);

*管理员是内置管理员组的一部分。

“系统管理员的用户帐户。此帐户是在操作系统安装期间创建的第一个帐户。该帐户不能被删除或锁定。它是管理员组的成员,不能从该组中删除。” -- https://ss64.com/nt/syntax-security_groups.html


从技术上讲,这依赖于实现细节。事实上,“令牌”的“所有者”字段指向内置的 Administrators 组。然而,没有人能说这个实现细节会一直存在,适当的方法是检查当前安全上下文是否提升了权限。 - 0xC0000022L

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