2022年12月22日

ユーザを偽装してSYSTEMサービスからネットワークリソースにアクセスする

このアーティクルはDelphi Advent Calendar 2022の22日目の記事です(20日ぶり8回目)。

以前WNetAddConnection2を使って他のPCのネットワーク共有にアクセスする方法について書きましたが、これをサービスから実行するとWNetAddConnection2が時々エラー1312(ERROR_NO_SUCH_LOGON_SESSION)になる、という現象が発生します。調べてみたところ、サービスの場合はSystemではなくNetworkServiceで動作していないとこのような状態になるようです(常にエラーになるわけではなく、成功したりエラーになったりという感じ)。もちろんサービスをNetworkServiceで実行すればネットワークアクセスでエラーになることはないのですが、ローカルコンピュータに対するアクセスがUsersグループ相当に制限されてしまいます(サービスで使用する(Local)System/LocalService/NetworkServiceアカウントについてはWindowsテクニカルドキュメント(旧MSDN)のService User AccountsLocalService AccountNetworkService AccountLocalSystem Accountや@ITのWindowsのサービスで使用される「System」「Local Service」「Network Service」アカウントとは?:Tech TIPSを参照)。

そこでSystemアカウントで動作するサービスから実行することを前提に、ネットワークリソースにアクセスするときだけNetworkServiceアカウントに偽装するようにしてみます。
まずLogonUserでNetworkServiceとしてログインし、返されたトークンハンドルでImpersonateLoggedOnUserを呼び出すことでユーザを偽装します。
const
  LOGON32_LOGON_NEW_CREDENTIALS   = 9;
  {$EXTERNALSYM LOGON32_LOGON_NEW_CREDENTIALS}
var
  LogonUserUser: String;
  LogonUserDomain: String;
  LogonUserLogonType: DWORD;
  LogonUserLogonProvider: DWORD;
  hToken: THandle;
begin
  { Logon as 'NetworkService' user }
  LogonUserUser := 'NetworkService';
  LogonUserDomain := 'NT AUTHORITY';
  LogonUserLogonType := LOGON32_LOGON_NEW_CREDENTIALS;
  LogonUserLogonProvider := LOGON32_PROVIDER_DEFAULT;
  if LogonUser(PChar(LogonUserUser),PChar(LogonUserDomain),nil,
               LogonUserLogonType,LogonUserLogonProvider,hToken) = False then
  begin
    RaiseLastOSError;
  end;

  { Impersonate }
  if ImpersonateLoggedOnUser(hToken) = False then
  begin
    RaiseLastOSError;
  end;
これでこの後WNetAddConnection2で接続を試みるときにSystemアカウントではなくNetworkServiceアカウントであるかのように扱われます。

切断するときはまずWNetCancelConnection2を呼び出した後で、RevertToSelfで偽装を解除し、CloseHandleでトークンハンドルをクローズすることでログオフします。
  { Revert impersonation }
  if RevertToSelf() = False then
  begin
    RaiseLastOSError;
  end;

  { Loggoff }
  CloseHandle(hToken);
  hToken := INVALID_HANDLE_VALUE;
なおプロセス上で複数のスレッドが動作している場合(特にサービス)など、同一の共有リソース('\\<computername>\<sharename>')に対してWNetAddConnection2をネストして呼び出すとERROR_ALREADY_ASSIGNEDでエラーになります。これを防ぐには同一の共有リソースに対してWNetAddConnection2/WNetCancelConnection2が1回ずつ呼び出されるように、接続している共有リソースをプロセス全体で適切に管理する必要もあります。

0 件のコメント: