EternalWindows
LSA / LSAシークレット

セキュリティ的に重要なデータを管理するためには、データの暗号化が重要な意味を持ちます。 これにより、そのデータは鍵を知るユーザー以外に複合化できなくなります。 しかし、このように暗号化されたデータがどのユーザーからでもアクセスできる場所に保存されていれば、 複合化はできないものの、データの中身を変更することができますし、 データそのものが削除されてしまうことも考えられます。 こうしたことから、重要なデータは暗号化されると共に、安全な場所に保存されるべきといえます。 LSAシークレットとは、システムとAdministratorsグループのみがアクセス可能な場所であり、 データが暗号化されて保存されることから、いくつかの重要なデータが格納されています。 LSAシークレットは、次のレジストリキーで表されます。

HKEY_LOCAL_MACHINE\SECURITY\Policy\Secrets

SECURITYキーは既定でAdministratorsにアクセスを許可していないため、 レジストリエディタなどで上記キーを参照する場合は、 SECURITYキーのセキュリティ記述子を変更することになります。 上記キーには主に下記のようなサブキーが存在し、 これらがLSAシークレットに格納されるデータになります。

データを識別するキー 意味
L$xxx ローカルコンピュータ上から取得可能なデータ。 リモートから取得しようとした場合は、アクセスが拒否される。
G$xxx ドメインの全てのドメインコントローラ上で存在するデータ。
M$ システムのみアクセスできるデータ。
NL$xxx キャッシュドメインログオンで使用するユーザーアカウントやパスワード。
$MACHINE.ACC コンピュータアカウントのパスワード。
DefaultPassword 自動ログオン時に使用されるデフォルトのパスワード。
_SC_xxx サービスが特定のユーザーとしてログオンする場合の、そのユーザーの認証情報。システムのみ取得可。
RasDialParamsxxx ダイヤルアップ接続時に使用するパスワード。

xxxには任意の値が入りますが、キーによってはこの値が想像できる場合もあります。 たとえば、_SC_xxxのxxxにはサービスの名前が入り、 RasDialParamsxxxにはユーザーのSIDが入ります。 LSAシークレットに格納されたデータは、 Administratorsのメンバのユーザーとして動作していれば、 どのプロセスからでも取得することができるため、この点は注意してください。

LSAシークレットにデータを格納するには、LsaStorePrivateDataを呼び出します。

NTSTATUS LsaStorePrivateData(
  LSA_HANDLE PolicyHandle,
  PLSA_UNICODE_STRING KeyName,
  PLSA_UNICODE_STRING PrivateData
);

PolicyHandleは、ポリシーオブジェクトのハンドルを指定します。 このハンドルには、POLICY_CREATE_SECRETアクセス権が割り当てられている必要があります。 KeyNameは、格納するデータを識別するためのキーを指定します。 PrivateDataは、LSAシークレットに格納したいデータを指定します。 NULLを指定した場合は、KeyNameで識別されるデータが削除されます。

LSAシークレットに格納されたデータを取得するには、LsaRetrievePrivateDataを呼び出します。

NTSTATUS LsaRetrievePrivateData(
  LSA_HANDLE PolicyHandle,
  PLSA_UNICODE_STRING KeyName,
  PLSA_UNICODE_STRING *PrivateData
);

PolicyHandleは、ポリシーオブジェクトのハンドルを指定します。 このハンドルには、POLICY_GET_PRIVATE_INFORMATIONアクセス権が割り当てられている必要があります。 KeyNameは、取得したいデータを表すキーを指定します。 PrivateDataは、取得したデータを受け取る変数のアドレスを指定します。

今回のプログラムは、独自のデータをLSAシークレットに格納します。

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

BOOL StorePrivateData(LSA_HANDLE hPolicy, LPWSTR lpszKeyName, LPWSTR lpszPrivateData);
BOOL RetrievePrivateData(LSA_HANDLE hPolicy, LPWSTR lpszKeyName, LPWSTR lpszPrivateData);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	WCHAR                 szKeyName[] = L"L$MySecret";
	WCHAR                 szPrivateData[] = L"sample-data";
	WCHAR                 szRetriveData[1024];
	NTSTATUS              ns;
	LSA_HANDLE            hPolicy;
	LSA_OBJECT_ATTRIBUTES objectAttributes;
	BOOL                  bStorePrivateData = TRUE;

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

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

	if (bStorePrivateData) {
		if (StorePrivateData(hPolicy, szKeyName, szPrivateData))
			MessageBox(NULL, TEXT("LSAシークレットにデータを格納しました。"), TEXT("OK"), MB_OK);
		else
			MessageBox(NULL, TEXT("データの格納に失敗しました。"), NULL, MB_ICONWARNING);
	}
	else {
		if (RetrievePrivateData(hPolicy, szKeyName, szRetriveData))
			MessageBox(NULL, szRetriveData, TEXT("OK"), MB_OK);
		else
			MessageBox(NULL, TEXT("データの取得に失敗しました。"), NULL, MB_ICONWARNING);
	}

	LsaClose(hPolicy);

	return 0;
}

BOOL StorePrivateData(LSA_HANDLE hPolicy, LPWSTR lpszKeyName, LPWSTR lpszPrivateData)
{
	NTSTATUS           ns;
	LSA_UNICODE_STRING lsaName;
	LSA_UNICODE_STRING lsaData;
	
	lsaName.Length        = (USHORT)(lstrlenW(lpszKeyName) * sizeof(WCHAR));
	lsaName.MaximumLength = lsaName.Length + sizeof(WCHAR);
	lsaName.Buffer        = lpszKeyName;
	
	if (lpszPrivateData != NULL) {
		lsaData.Length        = (USHORT)(lstrlenW(lpszPrivateData) * sizeof(WCHAR));
		lsaData.MaximumLength = lsaData.Length + sizeof(WCHAR);
		lsaData.Buffer        = lpszPrivateData;
		
		ns = LsaStorePrivateData(hPolicy, &lsaName, &lsaData);
	}
	else
		ns = LsaStorePrivateData(hPolicy, &lsaName, NULL);

	return LsaNtStatusToWinError(ns) == ERROR_SUCCESS;
}

BOOL RetrievePrivateData(LSA_HANDLE hPolicy, LPWSTR lpszKeyName, LPWSTR lpszPrivateData)
{
	NTSTATUS            ns;
	LSA_UNICODE_STRING  lsaName;
	PLSA_UNICODE_STRING plsaData;

	lsaName.Length        = (USHORT)(lstrlenW(lpszKeyName) * sizeof(WCHAR));
	lsaName.MaximumLength = lsaName.Length + sizeof(WCHAR);
	lsaName.Buffer        = lpszKeyName;

	ns = LsaRetrievePrivateData(hPolicy, &lsaName, &plsaData);
	if (LsaNtStatusToWinError(ns) != ERROR_SUCCESS)
		return FALSE;

	lstrcpynW(lpszPrivateData, plsaData->Buffer, (plsaData->Length / sizeof(WCHAR)) + 1);
	LsaFreeMemory(plsaData);

	return TRUE;
}

szNameがデータを識別する名前を表し、szDataが格納されるデータを表しています。 よって、今回のプログラムを実行すると、 L$MySecretというキーがSecretsキーの下に作成され、そこにszDataのデータが暗号化されて格納されます。 キーの先頭にL$が付加されているため、データはローカルコンピュータからのみアクセスすることができます。

実際のところ、現在のWindowsではLSAシークレットの利用は推奨されていません。 重要なデータはCryptProtectDataなどのDPAPIで暗号化し、 アプリケーション自身でデータを適切に管理することが望まれています。 この1つの原因は、LSAシークレットに格納できるデータの数が4096個と少なく、 さらにこのうちの2048個がシステムによって予約されているためであると思われます。


戻る