从使用更安全令牌创建的进程调用时,RunAs Verb 不显示 UAC 弹出窗口且不提升

Posted

技术标签:

【中文标题】从使用更安全令牌创建的进程调用时,RunAs Verb 不显示 UAC 弹出窗口且不提升【英文标题】:RunAs Verb not showing a UAC popup and not elevating, when invoked from a process created with a Safer token 【发布时间】:2020-02-28 15:38:38 【问题描述】:

我想从提升的进程开始一个非提升的进程。 我已经使用 SaferComputeTokenFromLevel 完成了这项工作。它似乎正在工作,因为:

C:\>whoami /groups | findstr BUILTIN\Administrators
BUILTIN\Administrators     Alias    S-1-5-32-544    Group used for deny only

仅拒绝。所以我不是管理员。

1) 为什么(疑似)非管理员进程控制台主机窗口在标题中显示为“管理员:”?

我什至使用SetTokenInformation 将其设置为中等完整性。它可以工作,但即使不是管理员,它仍然显示为“管理员”。

然后,如果该非管理员进程想要通过ShellExecuteVerb=RunAs(使用powershell -C Start-Process -Verb RunAs -FilePath CMD.EXE)再次启动提升的进程,则不会出现 UAC 弹出窗口,并且该进程已启动但未提升,而不是-管理员。

2) 为什么会发生这种情况? (可能是链接令牌?) 3) 最后,我怎样才能启动一个非提升进程,它可以使用RunAs 动词来触发 UAC 并成功提升?

完整的复制代码(以管理员身份运行!)

using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.InteropServices;

namespace SaferRepro

    class Program
    
        static void Main(string[] args)
        
            string appToRun = "CMD.EXE";
            string arguments = String.Empty;
            bool newWindow = true, hidden = false;
            var startupFolder = Environment.CurrentDirectory;
            int mediumIntegrity = 8192;

            using (var newToken = GetTokenFromSaferApi())
            
                AdjustedTokenIntegrity(newToken, mediumIntegrity); // optional, launch as medium integrity process.
                var process = StartWithToken(newToken, appToRun, arguments, startupFolder, newWindow, hidden);
                GetProcessWaitHandle(process.DangerousGetHandle()).WaitOne();
            
        

        private static SafeTokenHandle GetTokenFromSaferApi()
        
            IntPtr hSaferLevel;
            SafeTokenHandle hToken;

            SaferLevels level = SaferLevels.NormalUser;

            if (!NativeMethods.SaferCreateLevel(SaferScopes.User, level, 1, out hSaferLevel, IntPtr.Zero))
                throw new Win32Exception();

            if (!NativeMethods.SaferComputeTokenFromLevel(hSaferLevel, IntPtr.Zero, out hToken, SaferComputeTokenFlags.None, IntPtr.Zero))
                throw new Win32Exception();

            if (!NativeMethods.SaferCloseLevel(hSaferLevel))
                throw new Win32Exception();

            return hToken;
        

        private static SafeProcessHandle StartWithToken(SafeTokenHandle newToken, string appToRun, string args, string startupFolder, bool newWindow, bool hidden)
        
            var si = new STARTUPINFO();

            if (newWindow)
            
                si.dwFlags = 0x00000001; // STARTF_USESHOWWINDOW
                si.wShowWindow = (short)(hidden ? 0 : 1);
            

            si.cb = Marshal.SizeOf(si);

            var pi = new PROCESS_INFORMATION();
            uint dwCreationFlags = newWindow ? (uint)0x00000010 /*CREATE_NEW_CONSOLE*/: 0;

            if (!NativeMethods.CreateProcessAsUser(newToken, null, $"appToRun args",
                IntPtr.Zero, IntPtr.Zero, false, dwCreationFlags, IntPtr.Zero, startupFolder, ref si,
                out pi))
            
                throw new Win32Exception();
            

            NativeMethods.CloseHandle(pi.hThread);
            return new SafeProcessHandle(pi.hProcess, true);
        

        private static bool AdjustedTokenIntegrity(SafeTokenHandle newToken, int integrityLevel)
        
            string integritySid = "S-1-16-" + (integrityLevel.ToString(CultureInfo.InvariantCulture));
            IntPtr pIntegritySid;
            if (!NativeMethods.ConvertStringSidToSid(integritySid, out pIntegritySid))
                return false;

            TOKEN_MANDATORY_LABEL TIL = new TOKEN_MANDATORY_LABEL();
            TIL.Label.Attributes = 0x00000020 /* SE_GROUP_INTEGRITY */;
            TIL.Label.Sid = pIntegritySid;

            var pTIL = Marshal.AllocHGlobal(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>());
            Marshal.StructureToPtr(TIL, pTIL, false);

            if (!NativeMethods.SetTokenInformation(newToken.DangerousGetHandle(),
               TOKEN_INFORMATION_CLASS.TokenIntegrityLevel,
               pTIL,
               (uint)(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>() + NativeMethods.GetLengthSid(pIntegritySid))))
                return false;

            return true;
        

        public static System.Threading.AutoResetEvent GetProcessWaitHandle(IntPtr processHandle) =>
            new System.Threading.AutoResetEvent(false)
            
                SafeWaitHandle = new SafeWaitHandle(processHandle, ownsHandle: false)
            ;
    

    internal class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    
        internal SafeTokenHandle(IntPtr handle) : base(true)
        
            base.SetHandle(handle);
        

        private SafeTokenHandle() : base(true)  

        protected override bool ReleaseHandle()
        
            return NativeMethods.CloseHandle(base.handle);
        
    

    static class NativeMethods
    
        [DllImport("kernel32.dll", SetLastError = true)]
        internal static extern bool CloseHandle(IntPtr hObject);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CreateProcessAsUser(
             SafeTokenHandle hToken,
             string applicationName,
             string commandLine,
             IntPtr pProcessAttributes,
             IntPtr pThreadAttributes,
             bool bInheritHandles,
             uint dwCreationFlags,
             IntPtr pEnvironment,
             string currentDirectory,
             ref STARTUPINFO startupInfo,
             out PROCESS_INFORMATION processInformation);

        #region Safer

        [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool SaferCreateLevel(
            SaferScopes dwScopeId,
            SaferLevels dwLevelId,
            int OpenFlags,
            out IntPtr pLevelHandle,
            IntPtr lpReserved);

        [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool SaferCloseLevel(
            IntPtr pLevelHandle);

        [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool SaferComputeTokenFromLevel(
          IntPtr levelHandle,
          IntPtr inAccessToken,
          out SafeTokenHandle outAccessToken,
          SaferComputeTokenFlags dwFlags,
          IntPtr lpReserved
        );

        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool ConvertStringSidToSid(
            string StringSid,
            out IntPtr ptrSid
            );

        [DllImport("advapi32.dll")]
        public static extern int GetLengthSid(IntPtr pSid);

        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern Boolean SetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass,
            IntPtr TokenInformation, UInt32 TokenInformationLength);
        #endregion
    

    [Flags]
    public enum SaferLevels : uint
    
        Disallowed = 0,
        Untrusted = 0x1000,
        Constrained = 0x10000,
        NormalUser = 0x20000,
        FullyTrusted = 0x40000
    
    [Flags]
    public enum SaferComputeTokenFlags : uint
    
        None = 0x0,
        NullIfEqual = 0x1,
        CompareOnly = 0x2,
        MakeIntert = 0x4,
        WantFlags = 0x8
    

    [Flags]
    public enum SaferScopes : uint
    
        Machine = 1,
        User = 2
    

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    internal struct STARTUPINFO
    
        public Int32 cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public Int32 dwX;
        public Int32 dwY;
        public Int32 dwXSize;
        public Int32 dwYSize;
        public Int32 dwXCountChars;
        public Int32 dwYCountChars;
        public Int32 dwFillAttribute;
        public Int32 dwFlags;
        public Int16 wShowWindow;
        public Int16 cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    

    [StructLayout(LayoutKind.Sequential)]
    internal struct PROCESS_INFORMATION
    
        public IntPtr hProcess;
        public IntPtr hThread;
        public int dwProcessId;
        public int dwThreadId;
    

    [StructLayout(LayoutKind.Sequential)]
    public struct TOKEN_MANDATORY_LABEL
    

        public SID_AND_ATTRIBUTES Label;

    

    [StructLayout(LayoutKind.Sequential)]
    public struct SID_AND_ATTRIBUTES
    
        public IntPtr Sid;
        public uint Attributes;
    

    // Integrity Levels
    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
    

【问题讨论】:

blog.nextxpert.com/2012/06/09/… 有趣的@HansPassant。 WhoAmI 不可信。使用 Process Explorer 时也只有拒绝。 相关comment 现在想知道如何在提升的进程上获取SeTcbPrivilege。想知道模拟系统是否可行。 见How can I launch an unelevated process from my elevated process and vice versa?和How can I launch an unelevated process from my elevated process, redux @GerardoGrignoli 这个问题解决了吗? 【参考方案1】:

当您的 a 令牌被提升时,似乎无法更改它以取消标记它的 elevated 标记状态。您可以从本地管理员中删除成员资格(实际上将其设置为仅拒绝),或将完整性设置为中,但它仍将被标记为提升(即使没有管理员权限)。设置此标志后,RunAs 动词将不会提升。

您的选择是:

获取explorer.exe 令牌并假设它没有被提升。 (如果 UAC 被禁用或用户 .

如果启用了 UAC,则提升的令牌具有指向未提升令牌的链接,您可以使用 GetTokenInformationTOKEN_INFORMATION_CLASS.TokenLinkedToken 获取该链接。

如果禁用 UAC,则没有链接令牌。我的问题并不真正适用,因为 UAC 弹出窗口被禁用,永远不会显示。仅供参考,您可以使用 SaferApi 创建设置了提升标志的非管理员令牌,但它的 RunAs 尝试将失败。

如果您想了解我是如何编码的,请查看我正在处理的 gsudo(Windows 的 sudo):

https://github.com/gerardog/gsudo/blob/dev/src/gsudo/Tokens/TokenManager.cs https://github.com/gerardog/gsudo/blob/dev/src/gsudo/Helpers/ProcessFactory.cs#L182

或查看其他great answer

【讨论】:

以上是关于从使用更安全令牌创建的进程调用时,RunAs Verb 不显示 UAC 弹出窗口且不提升的主要内容,如果未能解决你的问题,请参考以下文章

如何从 RESTful 服务发送安全令牌?

使用 Active Directory 令牌从 Angular 到 Web API 应用程序进行安全 API 调用

windows—令牌窃取

windows—令牌窃取

将 WCF 与负载平衡 (AWS) 一起使用时,安全上下文令牌无效

如何使用 JWT 令牌使忘记密码更安全?