EternalWindows
セキュリティコンテキスト / トークングループ

トークンユーザーがメンバとなっているグループはトークングループといわれ、 オブジェクトへのアクセスの成否に大きく関わります。 たとえば、Administratorsに書き込みを許可するファイルがあったとして、 プロセスがそのファイルに書き込むことができたならば、 プロセスに割り当てられているトークンのトークングループには、 Administratorsが含まれていることになります。 トークングループによってプロセスが行える作業の程度が変化するため、 どのグループに属するかを調べ、個々のグループの特徴を知ることは重要です。

トークングループはしばしば、アカウントの種類を表す指標として参照されることがあります。 たとえば、プロセスが管理者としてコードを実行しているかを確認したい場合、 トークングループにAdministratorsが含まれているかを確認することができます。 ただし、ここで注意しなければならないのは、 トークングループがときとして無効になるという点です。 たとえば、UACが有効である状態で管理者としてログオンした場合は、 制限ユーザーとしてプロセスが実行されることになるため、 トークンのAdministratorsは無効にされています。 無効にされたグループはオブジェクトへのアクセスなどで参照されないため、 このような場合はプロセスが管理者として動作していると判断してはいけません。

上記の事から分るように、アカウントの確認にグループを基にするのであれば、 そのグループがトークングループに含まれるのはもちろんのこと、 グループが有効になっているかも確認する必要があります。 次に示すCheckTokenMembershipは、そのような処理を行います。

BOOL WINAPI CheckTokenMembership(
  HANDLE TokenHandle,
  PSID SidToCheck,
  PBOOL IsMember
);

TokenHandleは、偽装トークンのハンドルを指定します。 現在のスレッドのトークンを対象にする場合は、NULLを指定することができます。 SidToCheckは、確認したいグループのSIDを指定します。 IsMemberは、確認の結果を受け取る変数のアドレスを指定します。 この値がTRUEである場合、SidToCheckで指定したグループはトークングループに含まれており、 さらにそのグループは有効ということになります。

次に、CheckTokenMembershipを呼び出す例を示します。 確認の対象としているのは、Administratorsです。

#include <windows.h>

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	BOOL                     bMember;
	PSID                     pSidAdministrators;
	SID_IDENTIFIER_AUTHORITY sidIdentifier = SECURITY_NT_AUTHORITY;

	AllocateAndInitializeSid(&sidIdentifier, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS,
		0, 0, 0, 0, 0, 0, &pSidAdministrators);

	if (!CheckTokenMembership(NULL, pSidAdministrators, &bMember)) {
		FreeSid(pSidAdministrators);
		return 0;
	}

	if (bMember)
		MessageBox(NULL, TEXT("指定されたグループは有効です。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("指定されたグループが無効、またはメンバではありません。"), NULL, MB_ICONWARNING);

	FreeSid(pSidAdministrators);

	return 0;
}

AllocateAndInitializeSidはSIDを作成する関数であり、 上記コードではAdministratorsのSIDを作成しています。 SIDを作成する関数にはこの他、LookupAccountNameやCreateWellKnownSidがあります。 CheckTokenMembershipの第1引数にNULLを指定しているため、 現在のスレッドのトークンが参照されることになります。 このトークンのトークングループに第2引数のSIDが含まれ有効である場合、 第3引数にTRUEが格納されます。

トークングループについての知識を深めるために、 GetTokenInformationを利用した方法を取り上げます。 次のプログラムは、トークングループに含まれる各グループをリストボックスに列挙し、 グループが有効になっているかも表示します。

#include <windows.h>

BOOL ConvertSidToName(PSID pSid, LPTSTR lpszName, DWORD dwSizeName);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndListBox = NULL;
	
	switch (uMsg) {

	case WM_CREATE: {
		TCHAR         szBuf[256];
		TCHAR         szGroupName[256];
		DWORD         i;
		DWORD         dwLength;
		BOOL          bEnabled;
		HANDLE        hToken;
		PTOKEN_GROUPS pTokenGroups;
		
		hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
			MessageBox(NULL, TEXT("トークンのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
			return -1;
		}
		
		GetTokenInformation(hToken, TokenGroups, NULL, 0, &dwLength);
		pTokenGroups = (PTOKEN_GROUPS)LocalAlloc(LPTR, dwLength);
		GetTokenInformation(hToken, TokenGroups, pTokenGroups, dwLength, &dwLength);
		
		for (i = 0; i < pTokenGroups->GroupCount; i++) {
			bEnabled = pTokenGroups->Groups[i].Attributes & SE_GROUP_ENABLED;

			if (pTokenGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID)
				lstrcpy(szGroupName, TEXT("logon SID"));
			else
				ConvertSidToName(pTokenGroups->Groups[i].Sid, szGroupName, sizeof(szGroupName) / sizeof(TCHAR));
			
			wsprintf(szBuf, TEXT("%s %s"), bEnabled ? TEXT("○") : TEXT("×"), szGroupName);
			SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		}

		LocalFree(pTokenGroups);
		CloseHandle(hToken);

		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

BOOL ConvertSidToName(PSID pSid, LPTSTR lpszName, DWORD dwSizeName)
{
	TCHAR        szDomainName[256];
	DWORD        dwSizeDomain = sizeof(szDomainName) / sizeof(TCHAR);
	SID_NAME_USE sidName;

	return LookupAccountSid(NULL, pSid, lpszName, &dwSizeName, szDomainName, &dwSizeDomain, &sidName);
}

列挙されたグループ名の横には、○または×の文字が表示されます。 ×の場合は、そのグループが無効ということであり、 たとえば、UACが有効の状態で管理者としてログオンしている場合は、 Administratorsが×として表示されることになります。 トークングループを取得するには、GetTokenInformationにTokenGroupsを指定します。 取得したpTokenGroups->GroupCountからグループの数を、 pTokenGroups->Groupsからグループを表すSID_AND_ATTRIBUTES構造体を参照することができます。

for (i = 0; i < pTokenGroups->GroupCount; i++) {
	bEnabled = pTokenGroups->Groups[i].Attributes & SE_GROUP_ENABLED;

	if (pTokenGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID)
		lstrcpy(szGroupName, TEXT("logon SID"));
	else
		ConvertSidToName(pTokenGroups->Groups[i].Sid, szGroupName, sizeof(szGroupName));
	
	wsprintf(szBuf, TEXT("%s %s"), bEnabled ? TEXT("○") : TEXT("×"), szGroupName);
	SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
}

グループが有効であるかどうかは、SID_AND_ATTRIBUTES.AttributesにSE_GROUP_ENABLEDが 含まれているかどうかで判断できます。 SIDは、自作関数のConvertSidToNameで名前に変換していますが、 ログオンSIDの場合はこれに成功しません。 ログオンSIDの場合は、SE_GROUP_LOGON_IDが含まれるため、 これを確認して個別に処理しています。

シェル関数による判定

shell32.dllからエクスポートされているいくつかの関数は、 目的のグループがトークングループに含まれているかの確認を容易にします。 これらの関数はCheckTokenMembershipを内部で呼び出しているので、 グループが有効になっているかどうかも確認します。 次に示すIsUserAnAdminは、現在のスレッドのトークンにAdministratorsが含まれているかを確認します。

#include <windows.h>
#include <shlobj.h>

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	if (IsUserAnAdmin())
		MessageBox(NULL, TEXT("Administratorsグループは有効です。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("Administratorsグループが無効、またはメンバではありません。"), NULL, MB_ICONWARNING);

	return 0;
}

IsUserAnAdminはAdministratorsが有効になっているかを確認するため、 管理者としてログオンしてもUACが有効な場合は、 戻り値はFALSEとなります。 ただし、明示的に管理者として実行させた場合は、TRUEを返すことになります。 IsUserAnAdminは、Windows 2000からVistaまで使用できますが、 次期Windowsでは使用できなくなる可能性があるとされています。

IsUserAnAdminはAdministratorsの確認に特化した関数ですが、 他のグループの確認も行いたい場合はSHTestTokenMembershipの方が優れています。 この関数もIsUserAnAdminと同じく、事前にSIDを作成しておく必要がないので、 次のように簡単に呼び出すことができます。

#include <windows.h>

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	if (SHTestTokenMembership(NULL, DOMAIN_ALIAS_RID_ADMINS))
		MessageBox(NULL, TEXT("Administratorsグループは有効です。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("Administratorsグループが無効、またはメンバではありません。"), NULL, MB_ICONWARNING);

	return 0;
}

SHTestTokenMembershipの第1引数は、CheckTokenMembershipの第1引数と同じ意味を持ちます。 第2引数は確認するグループSIDのRIDであり、 DOMAIN_ALIAS_RID_ADMINSはAdministratorsのRIDです。 AdministratorsのSIDはS-1-5-32-544であり、GuestアカウントがメンバであるGuestsはS-1-5-32-546ですが、 このようなSIDの最後の部分がRIDになります。 SHTestTokenMembershipは、S-1-5-32-X形式のグループSIDのみを確認の対象とし、 Windows XPから使用可能です。



戻る