ActiveX

ActiveX 컨트롤에서의 IObjectSafety 인터페이스 구현

디버그정 2008. 7. 25. 14:50

퍼온 글 : http://cafe.naver.com/imp09.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=3


IE를 가지고 인터넷 서핑을 하다가 보안 등급에 걸려서 어떠한 메시지를 본적이 있는가? 특히 ActiveX를 사용하는 상용서비스의 경우 지금 설명하게 될 방법을 적용하여 사용자가 해당 메시지를 볼 수 없게 하겠지만, 개발자의 경우 개발 도중 이러한 메시지를 보고 당황할 지도 모르겠다. 그럼 어떻게 해야 된다는 것인가?

Implementation IObjectSafety Interface for ActiveX control

ActiveX?

MS의 ActiveX 기술은 현재 다음과 같은 3가지로 구분되어 진다.


ActiveX Control 
흔히 우리가 알고 있는 ActiveX 또는 컨트롤이라고 불리는 것이다. 즉, 인터넷 익스플로러라는 컨테이너에 의해 로딩되는 컨트롤 및 ASP 서버 컴포넌트, MTS 컴포넌트, ATL로 만들어지는 각종 COM 등 MS의 왠만한 것들은 모조리 ActiveX 컨트롤이라 불릴 수 있다. 물론 OLE 서버 뿐 아니라 컨테이너도 ActiveX 컨트롤로 불린다.


ActiveX Document
각종 ActiveX 컨트롤들과 연계되어 로딩될 수 있는  문서포맷을 말한다. 대표적인 예가 IE와 오피스가 설치되어 있다면 인터넷에서 IE를 이용해 오피스로 작성된 모든 문서를 바로 볼 수 있는데, 이 경우 오피스 프로그램이 OLE서버가 되고 IE가 컨테이너가 되어 오피스 문서를 로딩하게 된다. 이렇게 ActiveX 기술을 이용해 로딩될 수 있는 문서를 칭하는 명칭이며 좀 더 포괄적으로는 이에 관련된 기술들을 가리킨다.


ActiveX Scripting
인터넷에서 스크립트 언어를 사용하기 위한 기술과 그 개체들을 가리키는 명칭으로, IE가 대표적인 ActiveX 스크립팅의 호스트 프로그램이고 MS의 VBScript와 JScript (Netscape의 자바 스크립트와 스펙은 비슷하지만 약간 다른점이 있다)가 대표적인 ActiveX 스크립팅 엔진의 예이다. 물론 MS에서 정의하고 있는 인터페이스를 따르도록 제작된 스크립트 엔진은 IE에 추가가 가능하다. 결국, 인터넷에서 다양한 스크립트를 지원할 수 있도록 해주는 기술을 말하는 것이라 할 수 있다.

less..

So What?

ActiveX  컨트롤은 자신이 불려질때 ActiveX 스크립팅을 사용하게 되는 경우가 있다. ActiveX  컨트롤이  ActiveX 스크립팅을 사용할때 컨테이너(주로 IE)는 친절하게 사용자에게 위험성이 있다는 경고문을 보여주는 경우가 있다. IE의   경우   보안등급을  보통으로  해놓는  경우  100%  경고메시지를  보여주고 있다. 오브젝트  태그중  파라미터를 넘길 수 있는 경우가 있는데, 이 경우도 ActiveX 스크립팅을 사용하게 되는데 열심히 만들어논 컨트롤이 로딩시마다 이런 에러메시지를 보인다면 실제 사이트에 적용시 문제가 아닐 수 없다. 따라서, ActiveX Control은 Web Page에서 사용될 때 해당 Control이 모든 Web Page상에서 자신이 안전하다는 것을 보장해야 한다. Control이 안전하다는 것은 Web Page를 만드는 사람이 Control을 초기화 하거나 스크립트 코드를 실행할 때 해가 되는 작업을 하지 않는다는 것을 의미한다. 따라서, Control이 안전하다는 것을 보장하기 위해서 Control개발자는 다음과 같은 사항을 고려하여 개발하여야 한다.

  • File System을 조작하지 않는다.
  • Control자신을 등록 또는 해제하는 것을 제외하고는 Registry를 조작하지 않는다.
  • 배열의 범위를 넘어선다던가 메모리를 부적절하게 조작하지 않는다.
  • 모든 입력값에 대하여 데이터의 유효성을 확인한다.
  • 사용자 또는 사용자가 제공한 데이터를 부적절하게 사용하지 않는다.

하지만, 이러한 사항들도 권고일 뿐 시스템적으로 막는 방법이 없어서 지금껏 시스템의 보안문제를 일으켜왔고, 이에 따라 Vista에서는 OS단에서 보안정책에 따라 아예 사용하지 못하게 막아버렸다. MS의 OS정책이야 어찌됐건, 아직도 많은 사이트에서는 ActiveX Control을 사용하고 있고 만들어야 한다는 괴리성이 생겼다.

아무튼, MS에서는 이런 경우 컨트롤이 안전하다는 증빙을 위해 ISafetyObjec라는 인터페이스를  정의해두고 있으며, 컨트롤 내부에서 이 인터페이스를 구현하면 컨테이너 (IE)가 경고메시지를 보여주지 않도록 준비해두고 있다.

What to do?

ActiveX 컨트롤에 Safe 옵션을 추가하는 방법은 레지스트리 키에 컨트롤이 안전하다는 것을 표시하는 항목을 저장하는 방법 (컨트롤이 인스톨 되거나 그 후에 Certains function을 컨트롤이 직접 호출하는 방법)과 그 컨트롤에 직접 IObjectSafety 인터페이스를 구현하여 Container가 Control의 안전성 여부를 조회하고 변경시킬 것을 요청하는 방법 두가지가 있다.


등록하는 동안 컨트롤을 레지스터에 마크하는 방법


1. MFC ActiveX template Project의 경우

  • 우선  레지스트리에 등록하는 헬퍼 펑션을 포함한 helpers.h와 helpers.cpp를 작성해야  하는데, 이 파일은 MSDN의 ActiveX SDK에서 카피하거나 첨부되어 있는 파일(helpers.h, helpers.cpp)을 다운로드하여 해당 Workspace에 추가 한다.
  • 그 다음 [CONTROL NAME]CTRL.CPP에 HELPERS.H와 OBJSAFE.H를 include한 다음 {ControlClass}::{ControlClass}Factory::UpdateRegistry에 다음 코드를 삽입하면 된다.

    // Mark as safe for scripting-failure OK.
    HRESULT hr = CreateComponentCategory(CATID_SafeForScripting,
        L"Controls that are safely scriptable");
   
    if (SUCCEEDED(hr))
        // Only register if category exists.
        RegisterCLSIDInCategory(m_clsid, CATID_SafeForScripting);
        // Don't care if this call fails.
   
    // Mark as safe for data initialization.
    hr = CreateComponentCategory(CATID_SafeForInitializing,
        L"Controls safely initializable from persistent data");
   
    if (SUCCEEDED(hr))
        // Only register if category exists.
        RegisterCLSIDInCategory(m_clsid, CATID_SafeForInitializing);

        // Don't care if this call fails.

 
2. ATL template Project의 경우
  • 생성된 파일 중 RGS 파일에 다음 코드를 추가 한다.

   HKCR
   {
       ......
       NoRemove CLSID
       {
             NoRemove 'Component Categories'
             {
                  '{7DD95801-9882-11CF-9FA9-00AA006C42C4}' = s '0'
                   {
                       '800' = s 'Safe for scripting'
                }
                    '{7DD95802-9882-11CF-9FA9-00AA006C42C4}' = s '0'
                   {
                        '800' = s 'Safe for initializing'
                    }
               }
              ForceRemove {844C638F-BF36-11D1-94D1-0020AF715AF0} = s 'AsyncEdit Class'
              {
                   'Implemented Categories'
                   {
                       '{7DD95801-9882-11CF-9FA9-00AA006C42C4}'
                    }
                   'Implemented Categories'
                   {
                       '{7DD95802-9882-11CF-9FA9-00AA006C42C4}'
                   }
                    ......
                 }
            }
}

위 코드에서 암호와 같은 값들에 대해 잠깐 설명하자면, Control이 항상 안전하다는 것을 Registry에 저장하기 위해서는 2개의 Key가 사용된다. 이 2개의 Registry Key는 다음과 같은 형식을 갖는다.


\HKEY_CLASSES_ROOT\CLSID\컨트롤의 CLSID\Implemented Categories\컴포넌트 카테고리GUID


위의 컴포넌트 카테고리 GUID라는 값은 ObjSafe.h 파일에 정의 되어 있는데, 우리가 관심있는 값은 CATID_SafeForInitializing과 CATID_SafeForScripting이다. 각각 CATID_SafeForInitializing은 {7DD95801-9882-11CF-9FA9-00AA006C42C4}로 CATID_SafeForScripting은 {7DD95802-9882-11CF-9FA9-00AA006C42C4}로 정의되어 있다.


Component Category에 대해서는 언젠가 설명할 날이 오겠지? ㅡ,.ㅡ;;;


IObjectSafety 인터페이스를 구현하는 방법

1. MFC ActiveX template project의 경우
 
    이 인터페이스를 구현하기 위해서는 GetInterfaceSafetyOptions와 SetInterfaceSafetyOptions  두개의 함수가 필요하다.

    좀더 쉽게 코드를 이해하기 위해 ActiveX Control의 이름을 MyAx로 정했을 경우를 예로 들어 설명하겠다. 실제 작업시에는

    아래의 코드를 Copy하여 해당 파일에 Paste하고 ActiveX Control의 이름만 자신이 만든 이름으로 수정하면 된다.

  • [Controlname]Ctrl.h 파일에 ObjSafe.h를 Include하고 클래스에 다음과 같은 코드를 삽입합니다.

   #include <ObjSafe.h>

    .......


    class CMyAxCtrl : public COleControl
    {

          .......


          DECLARE_DYNCREATE(CMyAxCtrl )
   
          DECLARE_INTERFACE_MAP()
   
        BEGIN_INTERFACE_PART(ObjSafe, IObjectSafety)
        STDMETHOD_(HRESULT, GetInterfaceSafetyOptions)

        (
                /* [in] */ REFIID riid,
                /* [out] */ DWORD __RPC_FAR *pdwSupportedOptions,
                /* [out] */ DWORD __RPC_FAR *pdwEnabledOptions
        );
           
        STDMETHOD_(HRESULT, SetInterfaceSafetyOptions)

        (
                /* [in] */ REFIID riid,
                /* [in] */ DWORD dwOptionSetMask,
                /* [in] */ DWORD dwEnabledOptions
        );
        END_INTERFACE_PART(ObjSafe);

        ......

    }

  • 그리고 cpp 파일에 다음과 같은 코드를 삽입해 주면 된다.

   /////////////////////////////////////////////////////////////
    // Interface map for IObjectSafety
   
    BEGIN_INTERFACE_MAP( CMyAxCtrl , COleControl )
        INTERFACE_PART(CMyAxCtrl , IID_IObjectSafety, ObjSafe)
    END_INTERFACE_MAP()
   
    /////////////////////////////////////////////////////////////
    // IObjectSafety member functions
   
    ULONG FAR EXPORT CMyAxCtrl ::XObjSafe::AddRef()
    {
        METHOD_PROLOGUE(CMyAxCtrl , ObjSafe)
        return pThis->ExternalAddRef();
    }
   
    ULONG FAR EXPORT CMyAxCtrl ::XObjSafe::Release()
    {
        METHOD_PROLOGUE(CMyAxCtrl , ObjSafe)
        return pThis->ExternalRelease();
    }
   
    HRESULT FAR EXPORT CMyAxCtrl ::XObjSafe::QueryInterface(
        REFIID iid, void FAR* FAR* ppvObj)
    {
        METHOD_PROLOGUE(CMyAxCtrl , ObjSafe)
        return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
    }
   
    const DWORD dwSupportedBits =
            INTERFACESAFE_FOR_UNTRUSTED_CALLER |
            INTERFACESAFE_FOR_UNTRUSTED_DATA;
    const DWORD dwNotSupportedBits = ~ dwSupportedBits;
           
    HRESULT STDMETHODCALLTYPE
        CMyAxCtrl ::XObjSafe::GetInterfaceSafetyOptions(
            /* [in] */ REFIID riid,
            /* [out] */ DWORD __RPC_FAR *pdwSupportedOptions,
            /* [out] */ DWORD __RPC_FAR *pdwEnabledOptions)
    {
        METHOD_PROLOGUE(CMyAxCtrl , ObjSafe)
   
        HRESULT retval = ResultFromScode(S_OK);
   
        // does interface exist?
        IUnknown FAR* punkInterface;
        retval = pThis->ExternalQueryInterface(&riid,
                        (void * *)&punkInterface);
        if (retval != E_NOINTERFACE) {  // interface exists
            punkInterface->Release(); // release it--just checking!
        }
       
        // we support both kinds of safety and have always both set,
        // regardless of interface
        *pdwSupportedOptions = *pdwEnabledOptions = dwSupportedBits;
   
        return retval; // E_NOINTERFACE if QI failed
    }
   
    HRESULT STDMETHODCALLTYPE
        CMyAxCtrl ::XObjSafe::SetInterfaceSafetyOptions(
            /* [in] */ REFIID riid,
            /* [in] */ DWORD dwOptionSetMask,
            /* [in] */ DWORD dwEnabledOptions)
    {
        METHOD_PROLOGUE(CMyAxCtrl , ObjSafe)
       
        // does interface exist?
        IUnknown FAR* punkInterface;
        pThis->ExternalQueryInterface(&riid,
            (void**)&punkInterface);
        if (punkInterface) {    // interface exists
            punkInterface->Release(); // release it--just checking!
        }
        else { // interface doesn't exist
            return ResultFromScode(E_NOINTERFACE);
        }
   
        // can't set bits we don't support
        if (dwOptionSetMask & dwNotSupportedBits) {
            return ResultFromScode(E_FAIL);
        }
       
        // can't set bits we do support to zero
        dwEnabledOptions &= dwSupportedBits;
        // (we already know there are no extra bits in mask )
        if ((dwOptionSetMask & dwEnabledOptions) !=
             dwOptionSetMask) {
            return ResultFromScode(E_FAIL);
        }                              
       
        // don't need to change anything since we're always safe
        return ResultFromScode(S_OK);
    }

 

2. ATL template Project의 경우

ATL은 IObjectSafetyImpl 템플릿 클래스에 IObjectSafety 인터페이스의 디폴트 구현 코드를 제공한다. 따라서, IObjectSafetyImpl 템플릿 클래스를 사용하여 컨트롤에 스크립팅과 초기화 안정성을 보장하는 기능을 구현할 수 있다.

  • 생성한 Control 클래스가 IObjectSafetyImpl 클래스에서 계승되도록 한다.

  class ATL_NO_VTABLE CMyAx :

                  ........
           public IObjectSafetyImpl<CMyAx,

                                  INTERFACESAFE_FOR_UNTRUSTED_CALLER|

                                  INTERFACESAFE_FOR_UNTRUSTED_DATA>

  • COM Map에 IObjectSafety 인터페이스를 노출시킨다.

  BEGIN_COM_MAP(CAsyncEdit)

            ......
     
COM_INTERFACE_ENTRY(IObjectSafety)
  END_COM_MAP()


What's my Recommendation?

모든 일을 처리하는데 방법이 여러가지라고 해서 모든 것이 다 유용한 것 만은 아니다. 즉 위의 방법에서도 내가 애용하는 방법은 쓸데없이 레지스트리를 더럽히는 방법보다는 IObjectSafety 인터페이스를 구현하는 방법이다. 앞에서도 언급했듯이 말이 구현이지 실제로는 위에 나온 소스를 그대로 복사해 넣고 자신이 개발한 ActiveX 컨트롤 클래스명만 수정해주면 끝이니 얼마나 쉬운가.


Reference

less..