EternalWindows
共有管理 / Nullセッション アクセス

前節で述べたように、リモートコンピュータ上に存在するリソースにアクセスするためには、 そのコンピュータ上にネットワークログオンしておく必要があります。 WNetAddConnection2を呼び出した場合、リモートコンピュータ上に指定したユーザー名とパスワードを持つユーザーが存在すれば、 ログオンタイプがNetworkであるログオンセッションがそのコンピュータ上に作成され、 ネットワークログオンは成功したことになります。 この状態でリソースにアクセスした場合、ネットワークログオンをしたユーザーとしてアクセスが行われ、 そのユーザーにアクセスを許可するリソースを参照することができます。

明示的にネットワークログオンを実行しなくても、リモートコンピュータ上でGuestアカウントが有効になっていれば、 アクセスは成功することがあります。 これは、リモート上で認証されなかった場合に、 Guestアカウントとして認証が行われることになっているからです。 よって、目的のリソースがGuestアカウントにアクセスを許可している場合は、 Guestアカウントのログオンセッションが作成され、 リソースへのアクセスは成功することになります。 Guestアカウントが無効である場合は、Guestアカウントのログオンセッションが自動的に作成されることはありませんから、 明示的にネットワークログオンをしない限り、リソースにアクセスすることはできません。

リモートコンピュータ上のユーザー情報を把握しておらず、さらにGuestアカウントも無効になっている場合は、 そのコンピュータにアクセスする余地がないように思えますが、実際にはそうではありません。 仮に一切のアクセスが許可されていなければ、 ドメインのメンバであるコンピュータを列挙したり、 コンピュータが公開している共有フォルダを列挙したりすることができなくなり、 ネットワークを構成する上で支障を来たすことになります。 よって、こうした目的の達成するための方法として、匿名ログオンというものがあります。 匿名ログオンとは、ユーザー名とパスワードを指定せずにログオンする方法で、 WNetAddConnection2を次のように呼び出します。

WNetAddConnection2(&netResource, TEXT(""), TEXT(""), 0);

上記のように匿名ログオンを実行した場合、リモートコンピュータ上にユーザー名をANONYMOUS LOGONとしたログオンセッションが作成されます。 このようなログオンセッションはNullセッションと呼ばれ、 これを利用してリモートコンピュータ上のリソースにアクセスすることをNullセッション アクセスと呼びます。 ログオンセッションを確認したい場合は、LsaEnumerateLogonSessionsを呼び出すことになります。

匿名ログオンは誰にでも実行できるものですから、ある意味でセキュリティ的に危険であるといえます。 リモートコンピュータ上にNullセッションを作成したということは、 ANONYMOUS LOGONでリソースへのアクセスが行えることを意味しますから、 このアカウントにアクセスを許可するリソースについては、 事実上誰でもアクセスすることが可能になります。 こうした問題を解消するためには、次のレジストリキーを確認する必要があります。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa

上記のレジストリキーにおいて、匿名ログオンに関わるエントリを次に示します。 これらのエントリの効果は直ちに反映されるため、変更後にシステムを再起動する必要はありません。

エントリ 意味
everyoneincludesanonymous ANONYMOUS LOGONのトークンにEveryoneを含めるかどうか。 0の場合は含まれないが、1の場合は含まれる。 Windows 2000では、デフォルトでEveryoneが含まれている。 ANONYMOUS LOGONにEveryoneが含まれている場合は、 Everyoneにアクセスを許可するリソースにANONYMOUS LOGONでアクセスできることになる。
restrictanonymous ANONYMOUS LOGONによるアカウントや共有の列挙を制限するかどうか。 0の場合は制限されないが、1の場合は制限される。
restrictanonymoussam ANONYMOUS LOGONによるアカウントの列挙において、 Everyoneにアクセスを許可するリソースを、 Authenticated Usersへのアクセス許可と解釈するかどうか。 つまり、ANONYMOUS LOGONによるアカウントの列挙を制限するかどうか。 0の場合は制限されないが、1の場合は制限される。

ここで、EveryoneとAuthenticated Usersの違いについて説明します。 共有フォルダなどを作成する際、誰からでもアクセスできるようにEveryoneを許可対象にすることがありますが、 先のeveryoneincludesanonymousが1である場合は、 ANONYMOUS LOGONにもそのリソースへアクセスすることが許可されてしまいます。 これを防ぎたい場合は、EveryoneではなくAuthenticated Usersを許可対象にします。 Authenticated Usersは、認証されたユーザーが属することできるグループであり、 ANONYMOUS LOGONは決してこれに属することができません。 通常のユーザーは正規の認証を終えてログオンするため、 EveryoneにもAuthenticated Usersにも属することになります。

先のレジストリエントリの変更によって、影響を受ける関数の一部を示します。

関数 匿名ログオンで成功する条件
NetUserGetInfo
NetUserModalsGet(レベル1と2)
NetShareEnum(レベル0と1と501)
everyoneincludesanonymous : 1
それ以外のエントリは問わない。
NetUserEnum
NetLocalGroupEnum
NetUserModalsGet(レベル0と3)
everyoneincludesanonymous : 1
restrictanonymous : 0
restrictanonymoussam : 0
NetServerEnum
LookupAccountName
成功しない。

情報レベルによって取得できる情報量が異なることから、 指定する情報レベルによっては関数が失敗することがあります。 たとえば、NetShareEnumに情報レベル2を指定した場合は、 レジストリエントリの値に関係なく失敗します。

今回のプログラムは、リモートコンピュータ上に匿名ログオンを実行し、 そのコンピュータ名を指定してNetShareEnumを呼び出します。 リモートコンピュータ上のeveryoneincludesanonymousが1である場合は、 共有フォルダを列挙することができます。

#include <windows.h>
#include <lm.h>

#pragma comment (lib, "netapi32.lib")
#pragma comment (lib, "mpr.lib")

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: {
		BOOL          bNetworkLogon = FALSE;
		WCHAR         szComputerName[256];
		TCHAR         szBuf[256];
		DWORD         i;
		DWORD         dwEntryCount;
		DWORD         dwTotalEntries;
		DWORD         dwResult;
		PSHARE_INFO_0 pShareInfo;

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

		if (bNetworkLogon) {
			NETRESOURCE netResource;

			lstrcpyW(szComputerName, L"\\\\RemoteName");
		
			ZeroMemory(&netResource, sizeof(NETRESOURCE));
			netResource.dwType       = RESOURCETYPE_ANY;
			netResource.lpLocalName  = NULL;
			netResource.lpRemoteName = szComputerName;

			dwResult = WNetAddConnection2(&netResource, TEXT(""), TEXT(""), 0);
			if (dwResult != NO_ERROR) {
				wsprintf(szBuf, TEXT("匿名ログオンに失敗しました。error %d"), dwResult);
				MessageBox(NULL, szBuf, NULL, MB_ICONWARNING);
				return 0;
			}
		}
		else {
			lstrcpyW(szComputerName, L"");
			ImpersonateAnonymousToken(GetCurrentThread());
		}

		dwResult = NetShareEnum(szComputerName, 0, (LPBYTE *)&pShareInfo, MAX_PREFERRED_LENGTH, &dwEntryCount, &dwTotalEntries, NULL);
		
		if (bNetworkLogon)
			WNetCancelConnection2(szComputerName, 0, FALSE);
		else
			RevertToSelf();

		if (dwResult != NERR_Success) {
			wsprintf(szBuf, TEXT("共有フォルダの列挙に失敗しました。errror %d"), dwResult);
			MessageBox(NULL, szBuf, NULL, MB_ICONWARNING);
			return -1;
		}

		for (i = 0; i < dwEntryCount; i++)
			SendMessageW(hwndListBox, LB_ADDSTRING, 0, (LPARAM)pShareInfo[i].shi0_netname);
			
		NetApiBufferFree(pShareInfo);

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

bNetworkLogonがTRUEの場合は、NetShareEnumの第1引数にリモートコンピュータの名前を指定することになります。 この呼び出しの際に、何らかのログオンセッションがリモートコンピュータ上に作成されていなければ、 アクセスが失敗することになるため、WNetAddConnection2で匿名ログオンを実行しています。 この場合、仮にリモートコンピュータ上でGuestアカウントが有効になっている場合でも、 アクセスはGuestではなくANONYMOUS LOGONで行われることになる点に注意してください。 つまり、匿名ログオンを実行することで、逆にアクセスが失敗する原因を作ってしまう場合があります。 NetShareEnumの呼び出しを終えたら、WNetCancelConnection2で接続をキャンセルし、 NetShareEnumの戻り値を確認することになります。 エラーの値が5である場合は、アクセスが拒否されたことを意味します。

bNetworkLogonがFALSEの場合は、NetShareEnumの第1引数に空文字を指定します。 つまり、ローカルコンピュータ上でANONYMOUS LOGONとしての実行をテストします。 ImpersonateAnonymousTokenを呼び出せば、システムの匿名トークンを偽装することができるため、 スレッドはANONYMOUS LOGONとしてコードを実行することになります。 NetShareEnumの呼び出しを終えたら、元のアカウントとしてコードを実行するためにRevertToSelfを呼び出します。

ANONYMOUS LOGONによるファイルアクセス

今回は、ANONYMOUS LOGONとして関数呼び出しを行いましたが、 UNCパスを指定したファイルアクセスは、どのような場合に成功するのでしょうか。 結論から述べると、共有フォルダにへのアクセスが許可されていれば、 中に格納されているファイルにはアクセスできるようになります。 ただし、これには不思議な点がいくつもあります。 まず、ANONYMOUS LOGONではなくEveryoneにアクセスを許可した場合は、ファイルにアクセスすることができません。 everyoneincludesanonymousを1に設定し、他のエントリを調整しても結果は同じです。 また、仮にファイルがANONYMOUS LOGONへのアクセスを許可しても、 フォルダがANONYMOUS LOGONへのアクセスを許可していない場合は、アクセスは失敗します。 これは、ANONYMOUS LOGONにSE_CHANGE_NOTIFY_NAME特権が割り当てられていないからであり、 この場合は目的のファイルの上位に存在するフォルダのアクセスチェックも成功しなければならないからです。 しかし、Everyoneにはこの特権が割り当てられており、 everyoneincludesanonymousが1の場合は、匿名トークンの特権リストにこの特権が含まれるはずなのですが、実際には含まれていません。 これも不思議な点といえます。 Windows Vistaでは整合性レベルの存在から、ANONYMOUS LOGONがファイルにアクセスするためには、 ファイルの整合性レベルがUntrusted(everyoneincludesanonymousが1の場合はLow以下)でなければなりませんが、 これは考慮されていないようです。



戻る