在没有管理员权限的情况下启动应用程序

问题描述:

使用管理员权限启动应用程序后,在该应用程序中使用ShellExecute执行的程序将继承管理员权限。但那不是我想要的:它只需定期开始,无需额外的权限。 ShellExecute接受参数OPEN(普通)和RUNAS(管理员)。但是,如果在以管理员身份启动应用程序后使用OPEN,它仍会像RUNAS那样操作。在没有管理员权限的情况下启动应用程序

以下示例演示了这一点:如果您使用常规权限启动它,它会显示'开始正常'。一旦你为'管理员'按1,它将以管理员身份启动。如果在新创建的提示中按2,则不会启动“常规”提示,但会再次出现“管理员”提示。

我发现了RUNAS中的一些参数(https://superuser.com/a/374866),但它们不能通过ShellExecute传递。有任何想法吗?

program WindowsPrivilegeTest; 
{$APPTYPE CONSOLE} 
{$R *.res} 

uses 
    System.SysUtils, 
    Winapi.Windows, 
    Winapi.ShellAPI; 

function CheckTokenMembership(TokenHandle: THANDLE; SidToCheck: Pointer; var 
    IsMember: BOOL): BOOL; stdcall; external advapi32 name 'CheckTokenMembership'; 

// Source: http://*.com/a/28572886/1870208 
function IsAdministrator: Boolean; 
var 
    psidAdmin: Pointer; 
    B: BOOL; 
const 
    SECURITY_NT_AUTHORITY: TSidIdentifierAuthority = (Value: (0, 0, 0, 0, 0, 5)); 
    SECURITY_BUILTIN_DOMAIN_RID = $00000020; 
    DOMAIN_ALIAS_RID_ADMINS  = $00000220; 
    SE_GROUP_USE_FOR_DENY_ONLY = $00000010; 
begin 
    psidAdmin := nil; 
    try 
    Win32Check(AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2, 
     SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, 
     psidAdmin)); 

    if CheckTokenMembership(0, psidAdmin, B) then 
     Result := B 
    else 
     Result := False; 
    finally 
    if psidAdmin <> nil then 
     FreeSid(psidAdmin); 
    end; 
end; 

var 
    lLine : String; 
    lOperation : PChar; 
begin 
    try 
    if IsAdministrator then 
    begin 
     Writeln('Started as administrator'); 
    end 
    else 
    begin 
     Writeln('Started regular'); 
    end; 

    while True do 
    begin 
     Writeln(''); 
     Writeln('How to start? 1 = admin, 2 = regular user. Type number and press enter'); 
     ReadLn(lLine); 
     lOperation := ''; 

     if lLine = '1' then 
     begin 
     lOperation := 'RUNAS'; 
     end 
     else 
     if lLine = '2' then 
     begin 
     lOperation := 'OPEN'; 
     end; 

     if lOperation <> '' then 
     begin 
     ShellExecute(0, lOperation, PChar(ParamStr(0)), nil, nil, SW_SHOWNORMAL); 
     Break; 
     end; 
    end; 
    except 
    on E: Exception do 
     Writeln(E.ClassName, ': ', E.Message); 
    end; 
end. 
+0

由于被引用的问题的答案不是正确的方式,因此正确的方法是获取进程标记,使用TokenLinkedToken参数调用GetTokenInformation来获取非夸张的标记。使用该标记启动新的过程。显然还要检查UAC是否实际启用(http://www.remkoweijnen.nl/blog/2011/08/11/gettokeninformation-with-tokenlinkedtoken-returns-error-1312/)。 – Remko

我在创建安装程序时用来解决这个难题,这里有一个可能的解决方案。

这个想法是创建一个以用户权限运行的后台进程,主应用程序与它通信并让它知道要启动的程序 - 启动的进程将拥有用户权限。

它应该像这样工作: 后台进程像往常一样启动,具有用户权限。启动后,它会以管理员身份启动主程序。后台进程保持隐藏状态并等待主进程的命令。

+0

这当然是最简单的方法,如果唯一的要求是启动进程*作为当前登录会话的用户*。否则,在不知道或存储密码的情况下,为登录用户获取令牌的唯一方法是[通过作为本地系统用户的API调用](https://msdn.microsoft.com/en-us/library /aa383840(v=vs.85).aspx)(即:你需要编写一个系统服务来调用这个服务,然后用'ShellExecuteEx'进行跟进,传入用户的令牌)。 –

+0

@J ...不完全,请参阅愚蠢 –

+0

@DavidHeffernan令人惊讶的是,考虑到使用'WTSQueryUserToken'时的注意事项(以及令牌泄露的风险),即使从系统上下文中限制使用它,这也是可能的。 +1,无论如何。 –