使用LogonUserW创建的令牌在模拟另一个用户时被拒绝访问资源。

huangapple go评论75阅读模式
英文:

Token created via LogonUserW while impersonating another user is denied from accessing resources

问题

以下是翻译的内容:

我有一个程序,通过LogonUserW创建令牌,并在调用RegOpenKeyW时模拟它们。如果我只做一次RegOpenKeyW调用,它将成功。如果我模拟第一个令牌,然后使用LogonUserW创建第二个令牌并模拟它,对RegOpenKeyW的相同调用将失败。以下是演示此行为的示例代码(注意:宏USERPASSWORD未包含在其中)。

#include <Windows.h>
#include <iostream>

void Test() {
    HKEY key;
    auto status{ RegOpenKeyW(HKEY_CLASSES_ROOT, L"*", &key) };
    std::wcout << L"RegOpenKeyW: ";
    if (status == ERROR_SUCCESS) {
        std::wcout << L"Succeeded";
        RegCloseKey(key);
    }
    else {
        std::wcout << L"Failed (0x" << std::hex << status << L")";
    }
    std::wcout << std::endl;
}

int main() {
    // 登录用户,模拟他们,然后运行测试
    HANDLE token1;
    if (LogonUserW(USER, L".", PASSWORD, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token1)) {
        if (ImpersonateLoggedOnUser(token1)) {
            Test();
            // 在仍模拟用户的情况下,运行相同的操作
            HANDLE token2;
            if (LogonUserW(USER, L".", PASSWORD, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token2)) {
                if (ImpersonateLoggedOnUser(token2)) {
                    Test();
                }
                CloseHandle(token2);
            }
        }
        CloseHandle(token1);
    }
}

这会生成以下输出。

RegOpenKeyW: Succeeded
RegOpenKeyW: Failed (0x5)

有人知道为什么第二次对Test的调用失败并返回访问被拒绝(0x5)吗?我期望如果第一次调用成功,第二次调用也会成功,但实际情况并非如此。

如果我在两次调用LogonUserW时使用相同的帐户,我可以使用TokenViewer检查两个令牌,并且它们似乎是相同的。但是,如果在调用LogonUserW时使用不同的帐户,行为将相同。

如果我选择不同的登录类型或登录提供程序作为LogonUserW的参数,行为将相同。

如果我在第二次调用LogonUserW之前调用RevertToSelf,则两次调用RegOpenKeyW都将成功,但我不确定为什么这种更改会导致它成功,而原始示例代码则不会。

我还修改了Test以访问除注册表之外的资源(例如LsaOpenPolicy),并遇到了相同的行为。第一次尝试访问资源将成功,而第二次尝试将失败。

英文:

I have a program which creates tokens via LogonUserW and impersonates them while calling RegOpenKeyW. If I do this once the call to RegOpenKeyW will succeed. If I impersonate the 1st token, then make a 2nd token with LogonUserW and impersonate it, the same call to RegOpenKeyW will fail. Here is example code which demonstrates the behavior (note: the macros USER and PASSWORD are not included).

#include &lt;Windows.h&gt;
#include &lt;iostream&gt;

void Test() {
    HKEY key;
    auto status{ RegOpenKeyW(HKEY_CLASSES_ROOT, L&quot;*&quot;, &amp;key) };
    std::wcout &lt;&lt; L&quot;RegOpenKeyW: &quot;;
    if (status == ERROR_SUCCESS) {
        std::wcout &lt;&lt; L&quot;Succeeded&quot;;
        RegCloseKey(key);
    }
    else {
        std::wcout &lt;&lt; L&quot;Failed (0x&quot; &lt;&lt; std::hex &lt;&lt; status &lt;&lt; L&quot;)&quot;;
    }
    std::wcout &lt;&lt; std::endl;
}

int main() {
    // Logon a user, impersonate them, then run a test
    HANDLE token1;
    if (LogonUserW(USER, L&quot;.&quot;, PASSWORD, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &amp;token1)) {
        if (ImpersonateLoggedOnUser(token1)) {
            Test();
            // While still impersonating the user, run the same actions
            HANDLE token2;
            if (LogonUserW(USER, L&quot;.&quot;, PASSWORD, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &amp;token2)) {
                if (ImpersonateLoggedOnUser(token2)) {
                    Test();
                }
                CloseHandle(token2);
            }
        }
        CloseHandle(token1);
    }
}

That generates the following output.

RegOpenKeyW: Succeeded
RegOpenKeyW: Failed (0x5)

Does anyone know why the 2nd call to Test fails with access denied (0x5)? I would expect that if the 1st call succeeded that the 2nd call would succeed as well but that is not the case.

If I use the same account for both calls to LogonUserW, I can inspected both tokens with TokenViewer and they appear to effectively be the same. However, I can use different accounts when calling LogonUserW and the same behavior will occur.

If I chose a different logon type or logon provider as the arguments to LogonUserW the same behavior will occur.

If I call RevertToSelf before the 2nd call to LogonUserW then both calls to RegOpenKeyW will succeed, but I am unsure why that change would allow it to succeed and the original example code will not.

I have also modified Test to access resources other than the registry (ex. LsaOpenPolicy) and have experienced the same behavior. The 1st attempt at accessing the resource will succeed and the 2nd attempt will fail.

答案1

得分: 3

在线索 SECURITY_IMPERSONATION_LEVEL 分配给线程令牌中的关键点。ImpersonateLoggedOnUser 在内部调用 SetThreadToken
-> [PsImpersonateClient][2] 时使用 SecurityImpersonation 级别,但在内部此 API 调用 SeTokenCanImpersonate 并检查某些条件是否不满足:

> 该例程通过检查各种条件来确保客户端模拟实际发生,包括以下条件:... 如果没有满足任何条件,该例程将复制传递给调用的现有令牌,并将新复制的令牌分配为模拟令牌,尽管具有有限的安全模拟级别;也就是说,服务器线程只能获取关于客户端的信息。

换句话说,SecurityImpersonation 被替换为 SecurityIdentification

条件列表可能会发生变化,但通常情况下会发生以下情况:

  • 允许模拟匿名令牌
  • 如果进程令牌具有 SeImpersonatePrivilege 则允许
  • 如果进程令牌的 AuthenticationId 等于客户端令牌的 OriginatingLogonSession 则允许
  • 如果令牌具有相同的安全标识符(SIDs)则允许

当您调用 LogonUserW 时不进行模拟 - OriginatingLogonSession 客户端令牌将等于进程令牌的 AuthenticationId。结果是,在第一次调用 ImpersonateLoggedOnUser 后,您将始终具有 SecurityImpersonation 级别。但是,如果您在进行模拟时再次调用 LogonUserW,则 token2 将具有另一个OriginatingLogonSession,如果您的进程没有SeImpersonatePrivilege,并且您模拟另一个用户(而不是当前用户,您的进程以其身份启动),则在第二次调用后,您将具有SecurityIdentification。这意味着您只能检查令牌属性,但任何安全检查都将失败。opnkey、文件、具有 SD 的对象 - 都会失败。

用于测试/检查的 POC 代码:

PCSTR ToString(TOKEN_TYPE TokenType)
{
	switch (TokenType)
	{
	case TokenPrimary:       return &quot;      Primary&quot;;
	case TokenImpersonation: return &quot;Impersonation&quot;;
	}

	__debugbreak();
	return 0;
}

PCSTR ToString(SECURITY_IMPERSONATION_LEVEL ImpersonationLevel)
{
	switch (ImpersonationLevel)
	{
	case SecurityAnonymous:      return &quot;Anonymous     &quot;;
	case SecurityIdentification: return &quot;Identification&quot;;
	case SecurityImpersonation:  return &quot;Impersonation &quot;;
	case SecurityDelegation:     return &quot;Delegation    &quot;;
	}

	__debugbreak();
	return 0;
}

void PrintTokenInfo(HANDLE hToken)
{
	TOKEN_ORIGIN to;
	TOKEN_STATISTICS ts;
	ULONG cb;
	
	if (GetTokenInformation(hToken, TokenStatistics, &amp;ts, sizeof(ts), &amp;cb) &amp;&amp;
		GetTokenInformation(hToken, TokenOrigin, &amp;to, sizeof(to), &amp;cb))
	{
		DbgPrint(&quot;%s:%s: %08x -&gt; %08x\n&quot;, 
			ToString(ts.TokenType), ToString(ts.ImpersonationLevel), 
			to.OriginatingLogonSession.LowPart, ts.AuthenticationId.LowPart);
	}
}

void PrintTokenInfo()
{
	HANDLE hToken;
	if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &amp;hToken) ||
		OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &amp;hToken))
	{
		PrintTokenInfo(hToken);
		CloseHandle(hToken);
	}
	else
	{
		DbgPrint(&quot;OpenThreadToken=%u\n&quot;, GetLastError());
	}
}

void m() {
	
	HANDLE token1, token2;
	if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &amp;token1))
	{
		const TOKEN_PRIVILEGES tp = { 1, { { { SE_IMPERSONATE_PRIVILEGE }, SE_PRIVILEGE_ENABLED } } };
		AdjustTokenPrivileges(token1, FALSE, const_cast&lt;TOKEN_PRIVILEGES*&gt;(&amp;tp), sizeof(tp), 0, 0);
		DbgPrint(&quot;SE_IMPERSONATE_PRIVILEGE=%u\n&quot;, GetLastError());
		PrintTokenInfo(token1);
		CloseHandle(token1);
	}

	if (LogonUserW(USER, L&quot;.&quot;, PASSWORD, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &amp;token1)) {
		PrintTokenInfo(token1);
		if (ImpersonateLoggedOnUser(token1)) {
			PrintTokenInfo();
			if (LogonUserW(USER, L&quot;.&quot;, PASSWORD, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &amp;token2)) {
				PrintTokenInfo(token2);
				if (ImpersonateLoggedOnUser(token2)) {
					PrintTokenInfo();
				}
				CloseHandle(token2);
			}
		}
		CloseHandle(token1);
	}

	RevertToSelf();
}

如果在提升的管理员进程下运行此代码:

SE_IMPERSONATE_PRIVILEGE=0
      Primary:Anonymous     : 000003e7 -&gt; 0003dafa
      Primary:Impersonation : 0003dafa -&gt; 0107cb0a
Impersonation:Impersonation : 0003dafa -&gt; 0107cb0a
      Primary:Impersonation : 0107cb0a -&gt; 0107cbba
Impersonation:Impersonation : 0107cb0a -&gt; 0107cbba

如果在受限制的(非提升的)用户下运行:

SE_IMPERSONATE_PRIVILEGE=1300 // ERROR_NOT_ALL_ASSIGNED
      Primary:Anonymous     : 000003e7 -&gt; 0003dbc5
      Primary:

<details>
<summary>英文:</summary>

here key point in [`SECURITY_IMPERSONATION_LEVEL`][1] assigned to thread token. `ImpersonateLoggedOnUser` internally call `SetThreadToken ` 
 -&gt; [`PsImpersonateClient`][2] with `SecurityImpersonation` level, but internally this api call `SeTokenCanImpersonate` and if some conditions not meet :

&gt; *The routine ensures whether client impersonation can actually occur by checking various conditions, including the following: ... If none
&gt; of the conditions are met, the routine makes a copy of the existing
&gt; token passed to the call and assigns the newly copied token as
&gt; impersonation token albeit with **limited** security impersonation
&gt; level; that is, the server thread can **only** obtain information
&gt; about the client.*

in other words, the `SecurityImpersonation` replaced to `SecurityIdentification `

the list of conditions can be subject to change,but in general it next

 - allow impersonating anonymous tokens
 - allow if process token have ***SeImpersonatePrivilege***
 - allow if ***AuthenticationId*** of process token equal to
   ***OriginatingLogonSession*** client token
 - allow if tokens have equal security identifiers (SIDs)

when you call `LogonUserW` while no impersonate - the ***OriginatingLogonSession*** client token will be equal to ***AuthenticationId*** of process token. as result you will be always have `SecurityImpersonation` level after first `ImpersonateLoggedOnUser`. but if you make second call to `LogonUserW` while you impersonating - the `token2` will be have **another *OriginatingLogonSession*** and if your process have no *SeImpersonatePrivilege* and you impersonate another user (not current, under which your process started) - after second call you will be have **SecurityIdentification**. with this - you can only check token properties but any security check will fail. opnkey, file, object with SD - all fail.

the POC code for test/check:

    PCSTR ToString(TOKEN_TYPE TokenType)
    {
    	switch (TokenType)
    	{
    	case TokenPrimary:       return &quot;      Primary&quot;;
    	case TokenImpersonation: return &quot;Impersonation&quot;;
    	}
    
    	__debugbreak();
    	return 0;
    }
    
    PCSTR ToString(SECURITY_IMPERSONATION_LEVEL ImpersonationLevel)
    {
    	switch (ImpersonationLevel)
    	{
    	case SecurityAnonymous:      return &quot;Anonymous     &quot;;
    	case SecurityIdentification: return &quot;Identification&quot;;
    	case SecurityImpersonation:  return &quot;Impersonation &quot;;
    	case SecurityDelegation:     return &quot;Delegation    &quot;;
    	}
    
    	__debugbreak();
    	return 0;
    }
    
    void PrintTokenInfo(HANDLE hToken)
    {
    	TOKEN_ORIGIN to;
    	TOKEN_STATISTICS ts;
    	ULONG cb;
    	
    	if (GetTokenInformation(hToken, TokenStatistics, &amp;ts, sizeof(ts), &amp;cb) &amp;&amp;
    		GetTokenInformation(hToken, TokenOrigin, &amp;to, sizeof(to), &amp;cb))
    	{
    		DbgPrint(&quot;%s:%s: %08x -&gt; %08x\n&quot;, 
    			ToString(ts.TokenType), ToString(ts.ImpersonationLevel), 
    			to.OriginatingLogonSession.LowPart, ts.AuthenticationId.LowPart);
    	}
    }
    
    void PrintTokenInfo()
    {
    	HANDLE hToken;
    	if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &amp;hToken) ||
    		OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &amp;hToken))
    	{
    		PrintTokenInfo(hToken);
    		CloseHandle(hToken);
    	}
    	else
    	{
    		DbgPrint(&quot;OpenThreadToken=%u\n&quot;, GetLastError());
    	}
    }
    
    void m() {
    	
    	HANDLE token1, token2;
    	if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &amp;token1))
    	{
    		const TOKEN_PRIVILEGES tp = { 1, { { { SE_IMPERSONATE_PRIVILEGE }, SE_PRIVILEGE_ENABLED } } };
    		AdjustTokenPrivileges(token1, FALSE, const_cast&lt;TOKEN_PRIVILEGES*&gt;(&amp;tp), sizeof(tp), 0, 0);
    		DbgPrint(&quot;SE_IMPERSONATE_PRIVILEGE=%u\n&quot;, GetLastError());
    		PrintTokenInfo(token1);
    		CloseHandle(token1);
    	}
    
    	if (LogonUserW(USER, L&quot;.&quot;, PASSWORD, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &amp;token1)) {
    		PrintTokenInfo(token1);
    		if (ImpersonateLoggedOnUser(token1)) {
    			PrintTokenInfo();
    			if (LogonUserW(USER, L&quot;.&quot;, PASSWORD, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &amp;token2)) {
    				PrintTokenInfo(token2);
    				if (ImpersonateLoggedOnUser(token2)) {
    					PrintTokenInfo();
    				}
    				CloseHandle(token2);
    			}
    		}
    		CloseHandle(token1);
    	}
    
    	RevertToSelf();
    }

if run this code under elevated admin process:

    SE_IMPERSONATE_PRIVILEGE=0
          Primary:Anonymous     : 000003e7 -&gt; 0003dafa
          Primary:Impersonation : 0003dafa -&gt; 0107cb0a
    Impersonation:Impersonation : 0003dafa -&gt; 0107cb0a
          Primary:Impersonation : 0107cb0a -&gt; 0107cbba
    Impersonation:Impersonation : 0107cb0a -&gt; 0107cbba

and if run under restricted(not elevated) user

    SE_IMPERSONATE_PRIVILEGE=1300 // ERROR_NOT_ALL_ASSIGNED
          Primary:Anonymous     : 000003e7 -&gt; 0003dbc5
          Primary:Impersonation : 0003dbc5 -&gt; 01064877
    Impersonation:Impersonation : 0003dbc5 -&gt; 01064877
          Primary:Impersonation : 01064877 -&gt; 010648ee
    OpenThreadToken=1346 // ERROR_BAD_IMPERSONATION_LEVEL

last error ( ERROR_BAD_IMPERSONATION_LEVEL ) say that real impersonation level of our thread is `SecurityIdentification`. this also can be checked by another, elevated tool

[1]: https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ne-wdm-_security_impersonation_level
  [2]: https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-psimpersonateclient



</details>



huangapple
  • 本文由 发表于 2023年7月11日 02:44:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76656497.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定