EternalWindows
LSA / 特権とアカウント

関数の呼び出しがセキュリティ的な理由で失敗した場合、返されるエラー値は主に2通り考えられます。 1つは、ERROR_ACCESS_DENIEDであり、 これはファイルなどのセキュリティ設定可能なオブジェクトが、 呼び出し側のアカウントにアクセスを許可していない場合に返されます。 そしてもう1つは、ERROR_PRIVILEGE_NOT_HELDであり、 これは呼び出し側のアカウントに適切な特権が割り当てられていない場合に返されます。 特権とは、何らかの重要な処理を行うために必要な権利のことであり、 一部の関数はアカウントに特権が割り当てられているかどうかを調べ、 その結果によって処理を実行するかを決定するようにしています。 特権の割り当ては、ローカルセキュリティポリシーなどから行うことができます。

「ポリシー」という列に定義されている特権及びユーザー権利が表示され、 「セキュリティの設定」という列にその特権またユーザー権利が割り当てられたアカウント名が表示されます。 特権が多く割り当てられているということは、それだけ多くの関数を呼び出せるということになりますから、 Administratorsには多くの特権が既定で割り当てられています。

特権とユーザー権利はよく似ている部分がありますが、実際には別物です。 確かにどちらもアカウントに割り当てることができるのですが、 ユーザー権利はユーザーがどのようにログオンできるかどうかを表すだけです。 LSAはユーザーがログオンする段階になると、そのユーザーに割り当てられているユーザー権利を調べ、 指定されたログオンが許可されている場合のみ、ログオンが成功するようになっています。 次に、定義されているユーザー権利を示します。

ユーザー権利 意味
SE_INTERACTIVE_LOGON_NAME ローカルログオン
SE_DENY_INTERACTIVE_LOGON_NAME ローカルログオンの拒否
SE_NETWORK_LOGON_NAME ネットワーク経由でコンピュータにアクセス
SE_DENY_NETWORK_LOGON_NAME ネットワーク経由でコンピュータにアクセスを拒否
SE_BATCH_LOGON_NAME バッチジョブとしてログオン
SE_DENY_BATCH_LOGON_NAME バッチジョブとしてログオンの拒否
SE_SERVICE_LOGON_NAME サービスとしてログオン
SE_DENY_SERVICE_LOGON_NAME サービスとしてログオンの拒否

たとえば、ユーザーをログオンさせるLogonUserにはLOGON32_LOGON_INTERACTIVEを指定することができますが、 これはローカルコンピュータへの対話ログオンを意味します。 つまり、そのログオンしたユーザーとして動作するアプリケーションは、 UIを表示してユーザーと対話することが可能になります。 このような対話ログオンが成功するためには、 そのログオンするユーザー、またはメンバとなっているグループに、 SE_INTERACTIVE_LOGON_NAMEが割り当てられている必要があります。 また、このユーザー権利が割り当てられていても、 SE_DENY_INTERACTIVE_LOGON_NAMEが割り当てられている場合は、 対話ログオンに失敗することになります。

アカウントに割り当てられている特権とユーザー権利を列挙するには、 LsaEnumerateAccountRightsを呼び出します。

NTSTATUS LsaEnumerateAccountRights(
  LSA_HANDLE PolicyHandle,
  PSID AccountSid,
  PLSA_UNICODE_STRING *UserRights,
  PULONG CountOfRights
);

PolicyHandleは、ポリシーオブジェクトのハンドルを指定します。 このハンドルには、POLICY_LOOKUP_NAMESとPOLICY_VIEW_LOCAL_INFORMATIONが割り当てられている必要があります。 AccountSidは、アカウントを表すSIDを指定します。 UserRightsは、列挙された特権及びユーザー権利を受け取るバッファを指定します。 CountOfRightsは、列挙された特権及びユーザー権利の数を受け取る変数のアドレスを指定します。

特定のアカウントに割り当てられた特権及びユーザー権利ではなく、 特定の特権やユーザー権利が割り当てられたアカウントを列挙したい場合は、 LsaEnumerateAccountsWithUserRightを呼び出します。

NTSTATUS LsaEnumerateAccountsWithUserRight(
  LSA_HANDLE PolicyHandle,
  PLSA_UNICODE_STRING UserRights,
  PVOID *EnumerationBuffer,
  PULONG CountReturned
);

PolicyHandleは、ポリシーオブジェクトのハンドルを指定します。 このハンドルには、POLICY_LOOKUP_NAMESとPOLICY_VIEW_LOCAL_INFORMATIONが割り当てられている必要があります。 UserRightsは、特権またはユーザー権利を格納したLSA_UNICODE_STRING構造体のアドレスを指定します。 EnumerationBufferは、列挙されたアカウントを受け取るバッファを指定します。 CountReturnedは、列挙されたアカウントの数を受け取る変数のアドレスを指定します。

今回のプログラムは、アカウントに割り当てられている特権及びユーザー権利を列挙します。 Administratorsのメンバであるユーザーで実行し、UACなどはオフにしておきます。

#include <windows.h>
#include <ntsecapi.h>

BOOL EnumerateAccountRights(LSA_HANDLE hPolicy, LPWSTR lpszAccount, HWND hwndListBox);
BOOL EnumerateAccountsWithUserRight(LSA_HANDLE hPolicy, LPWSTR lpszPrivilege, HWND hwndListBox);
BOOL ConvertSidToName(PSID pSid, LPTSTR lpszName, DWORD dwSizeName);
BOOL ConvertNameToSid(LPWSTR lpszName, PSID *ppSid);
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: {
		NTSTATUS              ns;
		LSA_HANDLE            hPolicy;
		LSA_OBJECT_ATTRIBUTES objectAttributes;
		BOOL                  bEnumPrivilege = TRUE;

		hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);

		ZeroMemory(&objectAttributes, sizeof(LSA_OBJECT_ATTRIBUTES));
		objectAttributes.Length = sizeof(LSA_OBJECT_ATTRIBUTES);

		ns = LsaOpenPolicy(NULL, &objectAttributes, POLICY_VIEW_LOCAL_INFORMATION | POLICY_LOOKUP_NAMES, &hPolicy);
		if (LsaNtStatusToWinError(ns) != ERROR_SUCCESS) {
			MessageBox(NULL, TEXT("ポリシーオブジェクトのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
			return -1;
		}

		if (bEnumPrivilege)
			EnumerateAccountRights(hPolicy, L"Administrators", hwndListBox);
		else
			EnumerateAccountsWithUserRight(hPolicy, L"SeInteractiveLogonRight", hwndListBox);

		LsaClose(hPolicy);
		
		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 EnumerateAccountRights(LSA_HANDLE hPolicy, LPWSTR lpszAccount, HWND hwndListBox)
{
	ULONG               i;
	ULONG               uCount;
	PSID                pSid;
	WCHAR               szBuf[256];
	NTSTATUS            ns;
	PLSA_UNICODE_STRING plsaString;

	ConvertNameToSid(lpszAccount, &pSid);

	ns = LsaEnumerateAccountRights(hPolicy, pSid, &plsaString, &uCount);
	if (LsaNtStatusToWinError(ns) != ERROR_SUCCESS) {
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("LsaEnumerateAccountRightsの呼び出しに失敗しました。"));
		LocalFree(pSid);
		return FALSE;
	}

	for (i = 0; i < uCount; i++) {
		lstrcpynW(szBuf, plsaString[i].Buffer, (plsaString[i].Length / sizeof(WCHAR)) + 1);
		SendMessageW(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
	}

	LsaFreeMemory(plsaString);
	LocalFree(pSid);

	return TRUE;
}

BOOL EnumerateAccountsWithUserRight(LSA_HANDLE hPolicy, LPWSTR lpszPrivilege, HWND hwndListBox)
{
	ULONG                        i;
	ULONG                        uCount;
	TCHAR                        szBuf[256];
	NTSTATUS                     ns;
	LSA_UNICODE_STRING           lsaString;
	PLSA_ENUMERATION_INFORMATION pEnum;
	
	lsaString.Length        = (USHORT)(lstrlenW(lpszPrivilege) * sizeof(WCHAR));
	lsaString.MaximumLength = lsaString.Length + sizeof(WCHAR);
	lsaString.Buffer        = lpszPrivilege;

	ns = LsaEnumerateAccountsWithUserRight(hPolicy, &lsaString, (PVOID *)&pEnum, &uCount);
	if (LsaNtStatusToWinError(ns) != ERROR_SUCCESS) {
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("LsaEnumerateAccountsWithUserRightの呼び出しに失敗しました。"));
		return FALSE;
	}

	for (i = 0; i < uCount; i++) {
		ConvertSidToName(pEnum[i].Sid, szBuf, sizeof(szBuf) / sizeof(TCHAR));
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
	}
	
	LsaFreeMemory(pEnum);

	return TRUE;
}

BOOL ConvertNameToSid(LPWSTR lpszName, PSID *ppSid)
{
	WCHAR        szDomainName[256];
	DWORD        dwSizeDomain = sizeof(szDomainName) / sizeof(WCHAR);
	DWORD        dwSizeSid = 0;
	SID_NAME_USE sidName;

	LookupAccountNameW(NULL, lpszName, NULL, &dwSizeSid, szDomainName, &dwSizeDomain, &sidName);
	
	*ppSid = (PSID)LocalAlloc(LPTR, dwSizeSid);

	return LookupAccountNameW(NULL, lpszName, *ppSid, &dwSizeSid, szDomainName, &dwSizeDomain, &sidName);
}

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);
}

ポリシーオブジェクトのハンドルは、WM_CREATEで取得しています。 ハンドルを取得できたということは、必要なアクセス権がハンドルに割り当てられたということですから、 LsaEnumerateAccountRightsなどの呼び出しは成功するように思えますが、 どうやらLSA関数の場合は少し例外があるようです。 ポリシーオブジェクトは、EveryoneにPOLICY_VIEW_LOCAL_INFORMATIONとPOLICY_LOOKUP_NAMESを許可していますが、 何故かEveryoneのメンバであるユーザーで実行しても、 LsaEnumerateAccountRightsなどではアクセスが拒否されてしまいます。 ハンドルを取得する時点ではアクセスが成功しているため、非常に難しい問題といえます。

bEnumPrivilegeがTRUEの場合は、自作関数のEnumerateAccountRightsが呼ばれます。 この関数では、LsaEnumerateAccountRightsを呼び出して、 指定したアカウントに割り当てられている特権及びユーザー権利をリストボックスに追加します。 呼び出し側ではAdministratorsを指定しているため、列挙される特権は非常に多くなり、 Administratorsの強力さが実感できます。

ConvertNameToSid(lpszAccount, &pSid);

ns = LsaEnumerateAccountRights(hPolicy, pSid, &plsaString, &uCount);
if (LsaNtStatusToWinError(ns) != ERROR_SUCCESS) {
	SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("LsaEnumerateAccountRightsの呼び出しに失敗しました。"));
	LocalFree(pSid);
	return FALSE;
}

for (i = 0; i < uCount; i++) {
	lstrcpynW(szBuf, plsaString[i].Buffer, (plsaString[i].Length / sizeof(WCHAR)) + 1);
	SendMessageW(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
}

まず、ConvertNameToSidという自作関数で、指定されたアカウント名からアカウントのSIDを取得します。 次に、LsaEnumerateAccountRightsの第2引数にこのSIDを指定し、関数が成功した場合は、 第3引数のplsaStringに特権及びユーザー権利の配列が格納されます。 後は、uCountの数だけ特権及びユーザー権利をリストボックスに追加します。

bEnumPrivilegeがFALSEの場合は、自作関数のEnumerateAccountsWithUserRightが呼ばれます。 この関数では、LsaEnumerateAccountsWithUserRightを呼び出して、 指定した特権またはユーザー権利が割り当てられたアカウント名をリストボックスに追加します。 呼び出し側ではSeInteractiveLogonRightを指定しているため、 システムに対話ログオン可能なアカウントのみが列挙されます。 SE_INTERACTIVE_LOGON_NAMEという定数を使用してもよいのですが、 関数側の受け取る型がLPWSTRであるため、明示的にUNICODE文字列を指定しています。

lsaString.Length        = (USHORT)(lstrlenW(lpszPrivilege) * sizeof(WCHAR));
lsaString.MaximumLength = lsaString.Length + sizeof(WCHAR);
lsaString.Buffer        = lpszPrivilege;

ns = LsaEnumerateAccountsWithUserRight(hPolicy, &lsaString, (PVOID *)&pEnum, &uCount);
if (LsaNtStatusToWinError(ns) != ERROR_SUCCESS) {
	SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("LsaEnumerateAccountsWithUserRightの呼び出しに失敗しました。"));
	return FALSE;
}

for (i = 0; i < uCount; i++) {
	ConvertSidToName(pEnum[i].Sid, szBuf, sizeof(szBuf) / sizeof(TCHAR));
	SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
}

LSA_UNICODE_STRING構造体に、引数から受け取った特権またユーザー権利の名前を指定します。 そして、LsaEnumerateAccountsWithUserRightを呼び出してアカウント名の配列を取得し、 uCountの数だけリストボックスに追加します。 pEnumの型であるLSA_ENUMERATION_INFORMATION構造体は、SID型のメンバのみを持っています。


戻る