本記事は、COM Advent Calendar 2014 – Qiitaの23日目の記事です。


前回の続きです。前回(OBJREFモニカーによるコンピュータ間の通信を試す)では、OBJREFモニカーでリモート(他コンピュータ)からのアクセスができることが分かりました。今回はそれをアクセス許可の設定で禁止する話です。

なお、無条件に以下のようなリモートアクセス禁止の処理を入れるべきとは考えていません。こういうことも可能だという紹介です。


あるプロセス内のオブジェクトへのアクセス許可はDACLで制御できます。ファイルやレジストリ、各種カーネルオブジェクトと同じですね。アクセスマスクは次の5つですが、現在意味があるは下4つです。

  • COM_RIGHTS_EXECUTE
  • COM_RIGHTS_EXECUTE_LOCAL
  • COM_RIGHTS_EXECUTE_REMOTE
  • COM_RIGHTS_ACTIVATE_LOCAL
  • COM_RIGHTS_ACTIVATE_REMOTE

COM_RIGHTS_EXECUTEは過去のもので現在無意味、EXECUTE_*系はプロセスが起動していない場合に起動する許可、ACTIVATEは起動しているプロセスに接続する許可です。

この設定を行うにはCoInitializeSecurity関数を使います。アクセス許可の指定方法は3種類ありますが、今回ではセキュリティデスクリプターを使いました。以下、サンプルプログラムです。前回のコードにCoInitializeSecurity関数の呼び出しを追加しただけです。

#define _ATL_NO_AUTOMATIC_NAMESPACE
 
#include <iostream>
#include <sstream>
#include <stdlib.h>
#include <windows.h>
#include <aclapi.h>
#include <comdef.h>
#include <atlbase.h>
#include <atlcom.h>
#include <atlutil.h>
#include <boost/scope_exit.hpp>
 
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
class ATL_NO_VTABLE Hoge
  : public ATL::CComObjectRootEx<ATL::CComMultiThreadModel>
  , public ATL::CComCoClass<Hoge>
  , public IDispatch
{
public:
  BEGIN_COM_MAP(Hoge)
    COM_INTERFACE_ENTRY(IDispatch)
  END_COM_MAP()
 
  IFACEMETHOD(GetTypeInfoCount)(
    __RPC__out UINT*) override
  {
    return E_NOTIMPL;
  }
 
  IFACEMETHOD(GetTypeInfo)(
    UINT,
    LCID,
    __RPC__deref_out_opt ITypeInfo**) override
  {
    return E_NOTIMPL;
  }
 
  IFACEMETHOD(GetIDsOfNames)(
    __RPC__in REFIID,
    /*__RPC__in_ecount_full(cNames)*/ LPOLESTR*,
    __RPC__in_range(0,16384) UINT,
    LCID,
    /*__RPC__out_ecount_full(cNames)*/ DISPID*) override
  {
    return E_NOTIMPL;
  }
 
  IFACEMETHOD(Invoke)(
    _In_ DISPID dispIdMember,
    _In_ REFIID riid,
    _In_ LCID,
    _In_ WORD flags,
    _In_ DISPPARAMS *pDispParams,
    _Out_opt_ VARIANT* pVarResult,
    _Out_opt_ EXCEPINFO*,
    _Out_opt_ UINT*) override
  {
    if (dispIdMember != DISPID_VALUE || flags != DISPATCH_METHOD)
      return DISP_E_MEMBERNOTFOUND;
    if (riid != IID_NULL)
      return DISP_E_UNKNOWNINTERFACE;
    if (pDispParams->cArgs != 0)
      return DISP_E_BADPARAMCOUNT;
    if (pDispParams->cNamedArgs != 0)
      return DISP_E_NONAMEDARGS;
    VariantInit(pVarResult);
    std::cout << "Hoge::Invoke" << std::endl;
    return S_OK;
  }
};
 
std::wstring GetObjrefMonikerDisplayString(IUnknown* obj)
{
  IMonikerPtr moniker;
  ATLENSURE_SUCCEEDED(CreateObjrefMoniker(obj, &moniker));
  IBindCtxPtr bc;
  ATLENSURE_SUCCEEDED(CreateBindCtx(0, &bc));
  LPOLESTR monikerStr;
  ATLENSURE_SUCCEEDED(moniker->GetDisplayName(bc, nullptr, &monikerStr));
  BOOST_SCOPE_EXIT_ALL(&monikerStr) { CoTaskMemFree(monikerStr); };
  return monikerStr;
}
 
int main()
{
  try
  {
    ATLENSURE_SUCCEEDED(CoInitializeEx(
      nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE));
 
    // 前回からの追加分ここから
 
    union
    {
      char buffer[SECURITY_MAX_SID_SIZE];
      SID sid;
    } networkLogon = {};
    DWORD sidNetworkSize = sizeof networkLogon;
    if (!CreateWellKnownSid(
      WinNetworkSid, nullptr, &networkLogon.sid, &sidNetworkSize))
    {
      ATL::AtlThrowLastWin32();
    }
    // COM_RIGHTS_EXECUTEは常にORする必要がある。
    EXPLICIT_ACCESS ea[] =
    {
      {
        /*.grfAccessPermissions =*/ COM_RIGHTS_EXECUTE
          | COM_RIGHTS_ACTIVATE_LOCAL,
        /*.grfAccessMode =*/ SET_ACCESS,
        /*.grfInheritance =*/ NO_INHERITANCE,
        /*.Trustee =*/ {
          /*.pMultipleTrustee =*/ nullptr,
          /*.MultipleTrusteeOperation =*/ NO_MULTIPLE_TRUSTEE,
          /*.TrusteeForm =*/ TRUSTEE_IS_NAME,
          /*.TrusteeType =*/ TRUSTEE_IS_UNKNOWN,
          /*.ptstrName =*/ TEXT("EVERYONE"),
        },
      },
#if 1
      {
        /*.grfAccessPermissions =*/ COM_RIGHTS_EXECUTE
          | COM_RIGHTS_EXECUTE_LOCAL | COM_RIGHTS_EXECUTE_REMOTE
          | COM_RIGHTS_ACTIVATE_LOCAL | COM_RIGHTS_ACTIVATE_REMOTE,
        /*.grfAccessMode =*/ DENY_ACCESS,
        /*.grfInheritance =*/ NO_INHERITANCE,
        /*.Trustee =*/ {
          /*.pMultipleTrustee =*/ nullptr,
          /*.MultipleTrusteeOperation =*/ NO_MULTIPLE_TRUSTEE,
          /*.TrusteeForm =*/ TRUSTEE_IS_SID,
          /*.TrusteeType =*/ TRUSTEE_IS_UNKNOWN,
          /*.ptstrName =*/ reinterpret_cast<LPTSTR>(&networkLogon.sid),
        },
      },
#endif
    };
 
    PACL dacl = nullptr;
    DWORD result = SetEntriesInAcl(ARRAYSIZE(ea), ea, nullptr, &dacl);
    ATLENSURE_THROW(result == ERROR_SUCCESS, HRESULT_FROM_WIN32(result));
 
    SECURITY_DESCRIPTOR sd;
    if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))
      ATL::AtlThrowLastWin32();
    if (!SetSecurityDescriptorDacl(&sd, TRUE, dacl, FALSE))
      ATL::AtlThrowLastWin32();
    union
    {
      char buffer[SECURITY_MAX_SID_SIZE];
      SID sid;
    } admin = {};
    DWORD sidSize = sizeof admin;
    if (!CreateWellKnownSid(
      WinBuiltinAdministratorsSid, nullptr, &admin.sid, &sidSize))
    {
      ATL::AtlThrowLastWin32();
    }
    if (!SetSecurityDescriptorOwner(&sd, &admin.sid, TRUE))
    {
      ATL::AtlThrowLastWin32();
    }
    if (!SetSecurityDescriptorGroup(&sd, &admin.sid, TRUE))
    {
      ATL::AtlThrowLastWin32();
    }
    ATLENSURE_SUCCEEDED(CoInitializeSecurity(&sd, -1, nullptr, nullptr,
      RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr,
      EOAC_NONE, nullptr));
 
    // 前回からの追加分ここまで
 
    IDispatchPtr hoge;
    ATLENSURE_SUCCEEDED(Hoge::CreateInstance(&hoge));
 
    std::wstring monikerStr = GetObjrefMonikerDisplayString(hoge);
    ATLENSURE_SUCCEEDED(CoLockObjectExternal(hoge, TRUE, FALSE));
 
    std::wcout << monikerStr << std::endl;
 
    std::cin.get();
  }
  catch (const ATL::CAtlException& e)
  {
    std::clog << std::hex << std::showbase;
    std::clog << e.m_hr << ' ' << ATL::AtlGetErrorDescription(e) << std::endl;
  }
 
  CoUninitialize();
}

クライアントのVBScript側のコードは前回から変更ありません。C++側を上記に差し替えて実行すると、今度はエラーになります。また、#if 1を#if 0にすると、再び成功します。

上のプログラムでは、所有者: Administrators、所有グループ: Administatorsとし、DACLは以下のようにしています。画像はDCOM構成ユーティリティーででっち上げました。コード上にコメントしているように、COM_RIGHTS_EXECUTEは常にORしておかないといけないようです(そうMSDNライブラリに書かれています)。

  • EVERYONE: COM_RIGHTS_ACTIVATE_LOCALを許可 (SET_ACCESS)
    Everyoneにローカルからのアクティブ化を許可のダイアログ表示
  • NETWORK: すべて拒否 (DENY_ACCESS)
    NETWORKに対してすべてを拒否

EVERYONEでリモート関係の2つを許可しないだけで大丈夫かと思いましたが、やってみたらOBJREFモニカーの場合、接続できてしまいました。上記プログラムの#if 1を0にするとその状態になります。そこでネットワークログオンで必ず付与されるNETWORKに対してすべて拒否を与えたところ、うまくいきました。


許可を与える対象がEVERYONEであることなど、改善点はあるとは思いますが、ひとまずネットワークからのアクセスの拒否は可能そうだということが分かりました。

今回の主な情報元はMSDNライブラリの以下のページです。

今回はこういう目的に使いましたが、もちろんほかの使い方も考えられます。たとえば、「Administratorsグループ(あるいは特定ユーザ)からしか使用できないようにする」、「保護モードのInternet Explorerなど低ILプロセスからの接続を許可する」などです。

私はオブジェクト単位でのアクセス許可が設定できると良いと思ったのですが、残念ながらそういう仕組みはないようです。プロセスを分けるか自前でやる(各メソッドにアクセス許可の確認処理を入れる)しかなさそうでした。


スポンサード リンク

この記事のカテゴリ

  • ⇒ アクセス許可の指定でリモートアクセスを禁じる