【问题标题】:How to impersonate a user from a service correctly?如何正确模拟服务中的用户?
【发布时间】:2013-11-16 18:21:34
【问题描述】:

我正在使用一项服务,它应该模拟登录用户。

到目前为止我的代码,基本的错误处理:

 // get the active console session ID of the logged on user
if ( !WTSQueryUserToken( WTSGetActiveConsoleSessionId(), &hToken ) )
{
    ShowErrorText( "WTSQueryUserToken failed.", GetLastError( ), true );
    return;
}

HANDLE hDuplicated;

// duplicate the token
if ( !DuplicateToken( hToken, SecurityImpersonation, &hDuplicated ) )
{
    ShowErrorText( "DuplicateToken failed.", GetLastError( ), true );
}
else 
{
    ShowErrorText( "DuplicateToken succeeded.", 0, true );
}

// impersonate the logged on user
if ( !ImpersonateLoggedOnUser( hToken ) )
{
    ShowErrorText( "ImpersonateLoggedOnUser failed.", GetLastError(), true );
    return;
}

// retrieve the DC name 
if ( !GetPrimaryDC( DC ) )
{
    ShowErrorText( "GetPrimaryDC failed.", 0, true );
}
PROFILEINFO lpProfileInfo;

ZeroMemory( &lpProfileInfo, sizeof( PROFILEINFO ) );
lpProfileInfo.dwSize = sizeof( PROFILEINFO );
lpProfileInfo.lpUserName = CurrentUser;

// get type of profile. roaming, mandatory or temporary
int ret = GetTypeOfProfile();
if ( ret == 2 )
{
    // if roaming profile get the path of it
    if ( !GetRoamingProfilePath( DC, CurrentUser, RoamingProfilePath ) )
    {
        ShowErrorText( "Failed to retrieve roaming profile path.", GetLastError(), true );
    }
}
if ( RevertToSelf( ) )
{
    ShowErrorText( "Impersonation ended successfully.", 0, true );
}

 if ( !LoadUserProfile( hDuplicated, &lpProfileInfo ) )
{
    ShowErrorText( "LoadUserProfile failed.", GetLastError(), true );
}
else
{
    ShowErrorText( "LoadUserProfile succeeded.", 0, true );
}

   //do some stuff


  if ( !UnloadUserProfile( hDuplicated, lpProfileInfo.hProfile ) )
{
    ShowErrorText( "UnloadUserProfile failed.", GetLastError( ), true );
}
else
{
    ShowErrorText( "UnloadUserProfile succeeded.", 0, true );
}

 if ( !ImpersonateLoggedOnUser( hToken ) )
{
    ShowErrorText( "ImpersonateLoggedOnUser failed.", GetLastError( ), true );
    return;
}

根据 MSDN:

当用户以交互方式登录时,系统会自动加载用户的个人资料。如果服务或应用程序模拟用户,系统不会加载用户的配置文件。因此,服务或应用程序应使用 LoadUserProfile 加载用户的配置文件。

调用 LoadUserProfile 的服务和应用程序应检查用户是否有漫游配置文件。如果用户有漫游配置文件,请将其路径指定为 PROFILEINFO 的 lpProfilePath 成员。要检索用户的漫游配置文件路径,您可以调用 NetUserGetInfo 函数,指定信息级别 3 或 4。

成功返回后,PROFILEINFO 的 hProfile 成员是一个向用户配置单元根目录打开的注册表项句柄。它已以完全访问权限 (KEY_ALL_ACCESS) 打开。如果模拟用户的服务需要读取或写入用户的注册表文件,请使用此句柄而不是 HKEY_CURRENT_USER。不要关闭 hProfile 句柄。而是将其传递给 UnloadUserProfile 函数。

如果我现在使用我的代码,那么它可以工作。然而这有点奇怪,因为首先我必须模拟登录用户,然后结束模拟,以加载用户配置文件。如果我不结束模拟,则 LoadUserProfile 将失败并出现错误 5(访问被拒绝)。 LoadUserProfile 成功后我应该再次模拟用户吗?

所以我的问题是,这意味着要这样做,还是我做错了什么? 另一个问题是,如果 LoadUserProfile 成功,我可以使用 hProfile 作为登录用户注册表的句柄。问题是如何?因为要使用 RegOpenKeyEy 和 RegSetValueEx,我需要传递 HKEY,而不是 HANDLE。那么我该如何使用这个Handle呢?

谢谢!

【问题讨论】:

    标签: c++ windows service impersonation


    【解决方案1】:

    您无需调用ImpersonateLoggedOnUser(),因为您将用户的令牌传递给LoadUserProfile()。仅当您需要调用不允许您将用户令牌传递给它们的 API 时才调用 ImpersonateLoggedOnUser()

    如果您阅读了LoadUserProfile() 文档的其余部分,它会说:

    调用进程必须具有 SE_RESTORE_NAME 和 SE_BACKUP_NAME 权限。

    通过冒充您尝试为其加载配置文件的用户,您可能会失去这些权限。所以不要冒充用户。

    更新:试试这样的:

    // get the active console session ID of the logged on user
    DWORD dwSessionID = WTSGetActiveConsoleSessionId();
    if ( dwSessionID == 0xFFFFFFFF )
    {
        ShowErrorText( "WTSGetActiveConsoleSessionId failed.", GetLastError( ), true );
        return;
    }
    
    if ( !WTSQueryUserToken( dwSessionID, &hToken ) )
    {
        ShowErrorText( "WTSQueryUserToken failed.", GetLastError( ), true );
        return;
    }
    
    // duplicate the token
    HANDLE hDuplicated = NULL;
    if ( !DuplicateToken( hToken, SecurityImpersonation, &hDuplicated ) )
    {
        ShowErrorText( "DuplicateToken failed.", GetLastError( ), true );
        CloseHandle( hToken );
        return;
    }
    
    // retrieve the DC name 
    if ( !GetPrimaryDC( DC ) )
    {
        ShowErrorText( "GetPrimaryDC failed.", 0, true );
        CloseHandle( hDuplicated );
        CloseHandle( hToken );
        return;
    }
    
    PROFILEINFO lpProfileInfo;
    ZeroMemory( &lpProfileInfo, sizeof( PROFILEINFO ) );
    lpProfileInfo.dwSize = sizeof( PROFILEINFO );
    lpProfileInfo.lpUserName = CurrentUser;
    
    // get type of profile. roaming, mandatory or temporary
    USER_INFO_4 *UserInfo = NULL;
    int ret = GetTypeOfProfile();
    if ( ret == 2 )
    {
        // if roaming profile get the path of it
        if ( NetUserGetInfo( DC, CurrentUser, 4, (LPBYTE*)&UserInfo) != NERR_Success )
        {
            ShowErrorText( "NetUserGetInfo failed.", 0, true );
            CloseHandle( hDuplicated );
            CloseHandle( hToken );
            return;
        }
    
        lpProfileInfo.lpProfilePath = UserInfo->usri3_profile;
    }
    
    if ( !LoadUserProfile( hDuplicated, &lpProfileInfo ) )
    {
        ShowErrorText( "LoadUserProfile failed.", GetLastError(), true );
        if ( UserInfo )
            NetApiBufferFree(UserInfo);
        CloseHandle( hDuplicated );
        CloseHandle( hToken );
        return;
    }
    
    if ( UserInfo )
        NetApiBufferFree(UserInfo);
    
    ShowErrorText( "LoadUserProfile succeeded.", 0, true );
    
    //do some stuff
    
    if ( !UnloadUserProfile( hDuplicated, lpProfileInfo.hProfile ) )
    {
        ShowErrorText( "UnloadUserProfile failed.", GetLastError( ), true );
    }
    else
    {
        ShowErrorText( "UnloadUserProfile succeeded.", 0, true );
    }
    
    CloseHandle( hDuplicated );
    CloseHandle( hToken );
    

    对于注册表,hProfile 句柄是为用户的HKEY_CURRENT_USER 树打开的HKEY。将其传递给注册表 API 函数时,Simpy 将其从 HANDLE 类型转换为 HKEY。它已经打开,因此您无需调用RegOpenKeyEx() 再次打开同一个键,但您可以在创建/打开子键时将其用作根键,或在根键中读取/写入值。

    【讨论】:

    • 我有点困惑。我第一次打电话给 ImpersonateLoggedonUser,因为如果用户有漫游配置文件,我需要获取。如果是,那么我需要它的路径,当然还有用户名。我需要这些东西来调用 LoadUserProfile。如果我不使用 ImpersonateLoggedOnUser 那么我怎么能得到它们?
    • 阅读LoadUserProfile() documentation:“要检索用户的漫游配置文件路径,您可以调用NetUserGetInfo()函数,指定信息级别3或4。”你不需要冒充。另请阅读 PROFILEINFO documentation 底部的有关漫游配置文件的 cmets。
    • 非常感谢您的帮助!它确实有效,虽然我有最后一个问题。 Untul 现在我使用 GetUsername 来检索登录的用户名。因为我使用的是 ImpersonateLoggedOnUser,所以它给了我正确的用户名,我必须将其传递给 PROFILINFO。但是我现在如何得到它?
    • GetUserName() 返回与调用线程关联的用户名。这就是为什么在从服务调用GetUserName() 时必须使用模拟的原因。由于您已经拥有用户令牌来自的会话 ID,您可以使用 WTSQuerySessionInformation() 来查询会话的登录用户名。将WTSInfoClass 参数设置为WTSUserName。您也可以使用WTSDomainName 查询会话的登录域。
    • @c00000fd:这应该作为一个新问题发布。但是,如果您更仔细地重新阅读我之前的评论评论,我说:“我会忽略GetProfileType(),而只是无条件地拨打NetGetUserInfo()......用户信息将具有漫游配置文件或不会”。阅读NetGetUserInfo documentation
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-27
    相关资源
    最近更新 更多