ActiveX的初始化和脚本安全

ActiveX的初始化和脚本安全

简 介
      很多微软的ActiveX控件(本地/远程)都需要使用持久性数据进行初始化,而且它们大多数都是可以通过脚本进行操作的 (支持一个方法,事件和属性的集合提供脚本语言操作)。初始化(使用持久性数据)和脚本操作都需要一个确定的安全性机制保证其安全性不被违背。
    一个说明控件初始化安全性风险的例子就是压缩/解压控件。如果用户点击了一个包含木马程序的远端系统文件(诸如Kernel.dll) 并且要求组件解压该文件,系统安全性就已经被破坏了。
      一个说明控件脚本操作安全性风险的例子就是一个基于系统设置的控件在无法获得相应设置之前就被执行。因此开发者有义务提供必要的代码在运行脚本执行之前获得相应的系统设置信息。
IE3的安全层次
      IE3具有三个安全性层次: 低级,中级和高级。等用户试图显示一个包含非安全性初始化和脚本操作的控件的页面时,IE3给出下列基于当前安全级别的警告信息。
     低级:没有警告
     中级:提示用户潜在的风险
     高级:提示用户潜在的风险并且禁止控件运行
      注意: 大多数控件开发者都是将它们的IE3的安全性级别设置为最低以便于开发调试。但是在实施安全性初始化和脚本操作代码之前,安全级别应该重新设置为高级(缺省)来确保提供测试足够的可信度。
初始化安全性
      当一个控件初始化时,可以从一个 IPersist* 接口获得数据 (来自一个本地/远端的URL)提供初始化状态。这是一个潜在的安全隐患,因为数据可能来自一个不可信的数据源。不提供安全性保证的控件将无视数据源的安全性。
      有两种方法可以检测控件的初始化安全性。第一种使用组件分组管理器(Component Categories Manager)创建一个正确的入口到系统注册表。IE3检测注册表之后才调用你的控件决定是否这些入口存在。第二种方法实现一个名称为IObjectSafe的接口到你的控件。如果IE3发现你的控件支持IObjectSafety,它调用     IObjectSafety::SetInterfaceSafetyOptions 方法然后才载入你的控件。
脚本操作安全性
      代码签字可以保证用户其代码的可信度。但是运行一个 ActiveX 控件可以被脚本访问将带来几个新的安全性问题。即使控件被认为是可靠的,如果使用不可信脚本代码访问也不能保证其安全性。比如,微软的     Word 被认为是一个安全的程序,但是一个宏可以使用自动化模型的脚本删除用户计算机上的文件,载入宏病毒或者蠕虫病毒。
      有两种方法提供你的控件的脚本操作安全性保证。第一种是使用组件分组管理器 (Component Categories Manager) -- 在组件导入以后在注册表上面创建正确的入口。IE3在脚本操作之前检查注册表确认安全性。第二种是实现一个     IObjectSafety 接口到你的控件。如果IE3发现你的控件支持 IObjectSafety,就在导入控件之前调用 IObjectSafety::SetInterfaceSafetyOptions     方法来确保安全性脚本操作。
使用组件分组管理器
      正像我们前面提到的,IE3通过检测注册表绝对一个控件是否是可以安全性初始化和脚本操作的。IE3通过调用 ICatInformation::IsClassOfCategories     方法决定是否控件支持给出的安全性分组。
      如果一个控件使用组件分组管理器将自己注册为安全的,该控件的注册表入口就包含一个实现的分组关键字,该关键字含有一个或者两个子键。一个子键设置控件支持安全性初始化,另一个设置支持安全性脚本操作。安全性初始化子键依赖于     CATID_SafeForInitializing; 安全性脚本操作子键依赖于 CATID_SafeForScripting。(其他组件分组子键定义在     Comcat.h 文件,而安全性初始化和脚本操作子键定义在 Objsafe.h 文件。)
  下列演示显示了一个注册表入口(Tabular Data Control),该ActiveX控件同IE绑定支持独则创建数据驱动的网页。因为控件是可以安全性脚本操作和初始化的,注册表中将其标记为安全性脚本操作(7DD95801-9882-11CF-9FA9-00AA006C42C4)     且安全性初始化(7DD95802-9882-11CF-9FA9-00AA006C42C4)。    

    

将一个控件注册为安全的
      系统注册表含有一个组件分组键来罗列每一个安装在系统中的组件的功能性分组。下面演示了一个组件分组键。假设 CATID_SafeForScripting    (7DD95801-9882-11CF-9FA9-00AA006C42C4) 和 CATID_SafeForInitializing (7DD95802-9882-11CF-9FA9-00AA006C42C4)     在列表之中。    

    

要创建一个组件分组的子键,你的控件必须包含以下步骤:
  ★ 创建一个组件分组管理器(Component Categories Manager)实例来接收 ICatRegister 接口的地址。
      ★ 设置正确的 CATEGORYINFO 结构分量。
      ★ 调用 ICatRegister::RegisterCategories 方法,将初始化的 CATEGORYINFO 结构变量传递给这个方法。
     
      下面的例子演示如何使用这些步骤来完成和合并一个名称为 CreateComponentCategory 函数到实例控件。    

#include "comcat.h"
    HRESULT CreateComponentCategory(CATID catid,WCHAR* catDescription)
    {
 
ICatRegister* pcr = NULL ;
    HRESULT hr = S_OK ;
 
file://创建一个组件管理器实例(进程内)
hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,
NULL,CLSCTX_INPROC_SERVER,IID_ICatRegister,(void**)&pcr);
    if (FAILED(hr))
    return hr;
 
// 确信 HKCR\\Component Categories\\{..catid...} 键已经被注册
CATEGORYINFO catinfo;
    catinfo.catid = catid;
    catinfo.lcid = 0x0409 ; // 英语
     
// 确信提供的描述在127个字符以内
int len = wcslen(catDescription);
    if (len>127)
    len = 127;
    wcsncpy(catinfo.szDescription,catDescription,len);
    // 确信描述使用'\\0'结束
catinfo.szDescription[len] = '\\0';
 
hr = pcr->RegisterCategories(1,&catinfo);
    pcr->Release();
 
return hr;
    }

  当一个子键被创建到需要的分组,控件应该注册到该分组,需要以下步骤:
  ★ 创建一个组件分组管理器实例接收 ICatRegister 接口地址。
      ★ 调用 ICatRegister::RegisterClassImplCategories 方法,将控件的 CLSID 和需要的 category    ID 作为参数传递给函数。
     
      下面的例子演示如何将一个名称为 RegisterCLSIDInCategory 加入实例控件。    

#include "comcat.h"
    HRESULT RegisterCLSIDInCategory(REFCLSID clsid,CATID catid)
    {
    // 注册你的组件分组信息
ICatRegister* pcr = NULL ;
    HRESULT hr = S_OK ;
    hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,
NULL,CLSCTX_INPROC_SERVER,IID_ICatRegister,(void**)&pcr);
    if (SUCCEEDED(hr))
    {
    // 注册已实现的类到分组
CATID rgcatid[1] ;
    rgcatid[0] = catid;
    hr = pcr->RegisterClassImplCategories(clsid,1,rgcatid);
    }
 
if (pcr != NULL)
    pcr->Release();
 
return hr;
    }

  一个控件应该在调用 DLLRegisterServer 函数是注册安全性初始化和脚本操作。(DLLRegisterServer 由组件对象模型     [COM] 调用创建注册表入口) 在实例组件中 DLLRegisterServer 函数调用了 CreateComponentCategory 和 RegisterCLSIDInCategory     函数 (它们保证控件的安全性初始化和脚本操作)。下面的就是 DLLRegisterServer 的实现。    

STDAPI DllRegisterServer(void)
    {
    HRESULT hr; // return for safety functions
 
AFX_MANAGE_STATE(_afxModuleAddrThis);
 
if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(),_tlid))
    return ResultFromScode(SELFREG_E_TYPELIB);
 
if (!COleObjectFactoryEx::UpdateRegistryAll(TRUE))
    return ResultFromScode(SELFREG_E_CLASS);
 
// 注册控件是安全性初始化的
     
hr = CreateComponentCategory(CATID_SafeForInitializing,L"Controls
    safely initializable from persistent data!");
    if (FAILED(hr))
    return hr;
 
hr = RegisterCLSIDInCategory(CLSID_SafeItem,CATID_SafeForInitializing);
    if (FAILED(hr))
    return hr;
 
// 注册控件是安全性脚本操作的
     
hr = CreateComponentCategory(CATID_SafeForScripting,L"Controls
    safely scriptable!");
    if (FAILED(hr))
    return hr;
 
hr = RegisterCLSIDInCategory(CLSID_SafeItem,CATID_SafeForScripting);
    if (FAILED(hr))
    return hr;
 
return NOERROR;
    }
 

  作为一个创建所有安全性分组入口到注册表的控件,它也应该负责卸载所有的分组信息。COM 调用控件的 DLLUnRegisterServer 函数删除相应的注册表入口然后卸载该控件。
       
      要卸载一个安全性初始化和脚本操作控件,控件应该完成以下任务:
  ★ 创建一个组件分类管理器实例接收 ICatRegister 接口地址。
      ★ 调用 ICatRegister::UnRegisterClassImplCategories 方法,将控件的 CLSID 和必要的 category    ID 作为参数传递
     
      下面的例子演示如何完成一个 UnRegisterCLSIDInCategory 。    

HRESULT UnRegisterCLSIDInCategory(REFCLSID clsid,CATID catid)
    {
    ICatRegister* pcr = NULL ;
    HRESULT hr = S_OK ;
 
hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,
NULL,CLSCTX_INPROC_SERVER,IID_ICatRegister,(void**)&pcr);
    if (SUCCEEDED(hr))
    {
    // 从分组卸载组件
CATID rgcatid[1] ;
    rgcatid[0] = catid;
    hr = pcr->UnRegisterClassImplCategories(clsid,1,rgcatid);
    }
 
if (pcr != NULL)
    pcr->Release();
 
return hr;
    }

  我们前面提过,一个控件负责删除安全性初始化和脚本操作入口,下面演示如何完成这两个步骤:    

STDAPI DllUnregisterServer(void)
    {
    HRESULT hr; // HResult used by Safety Functions
 
AFX_MANAGE_STATE(_afxModuleAddrThis);
    // 卸载组件库
if (!AfxOleUnregisterTypeLib(_tlid))
    return ResultFromScode(SELFREG_E_TYPELIB);
 
if (!COleObjectFactoryEx::UpdateRegistryAll(FALSE))
    return ResultFromScode(SELFREG_E_CLASS);
 
// 删除注册表入口
     
hr=UnRegisterCLSIDInCategory(CLSID_SafeItem,CATID_SafeForInitializing);
    if (FAILED(hr))
    return hr;
 
hr=UnRegisterCLSIDInCategory(CLSID_SafeItem,CATID_SafeForScripting);
    if (FAILED(hr))
    return hr;
 
return NOERROR;
    }

  支持IObjectSafety接口
      IObjectSafety 接口允许一个容器要求它的控件是安全的,或者获得当前的初始化或者脚本操作能力的信息。该接口在 Objsafe.h 中定义。当前支持两个操作:     安全性初始化和脚本操作。这些功能对应下列位串标志位,定义在 Objsafe.h。
     
     INTERFACESAFE_FOR_UNTRUSTED_DATA   特指接口是安全性初始化的
     INTERFACESAFE_FOR_UNTRUSTED_CALLER  特指接口是安全性脚本操作的
      
      IObjectSafety 接口支持两个方法: IObjectSafety::GetInterfaceSafetyOptions 和 IObjectSafety::SetInterfaceSafetyOptions。第一个方法返回控件的安全性级别     (即上述的宏定义位串标志位)。第二个方法允许一个容器要求控件将自己配置为安全性初始化和教本操作的。下面的 Objsafe.h 的宏定义包含了需要的方法    

// IObjectSafety的信息位串定义:
    #define INTERFACESAFE_FOR_UNTRUSTED_CALLER 0x00000001 // 接口调用者可以是非安全的
#define INTERFACESAFE_FOR_UNTRUSTED_DATA 0x00000002 // 传递给接口的数据可以是非安全的
     
// {CB5BDC81-93C1-11cf-8F20-00805F2CD064}
    DEFINE_GUID(IID_IObjectSafety,0xcb5bdc81,0x93c1,0x11cf,0x8f,0x20,
0x0,0x80,0x5f,0x2c,0xd0,0x64);
 
interface IObjectSafety : public IUnknown
    {
    public:
    virtual HRESULT __stdcall GetInterfaceSafetyOptions(
    /* [in] */ REFIID riid,
    /* [out] */ DWORD __RPC_FAR *pdwSupportedOptions,
    /* [out] */ DWORD __RPC_FAR *pdwEnabledOptions) = 0;
 
virtual HRESULT __stdcall SetInterfaceSafetyOptions(
    /* [in] */ REFIID riid,
    /* [in] */ DWORD dwOptionSetMask,
    /* [in] */ DWORD dwEnabledOptions) = 0;
 
};

  即使一些容器调用 IObjectSafety::GetInterfaceSafetyOptions 获得缺省的控件性能指标,IE3则不。它调用     IObjectSafety::SetInterfaceSafetyOptions 在先,然后初始化控件。并且如果组件支持脚本操作,它就再次调用 IObjectSafety::SetInterfaceSafetyOptions。该函数具有三个参数:     第一个指定了容器需要访问的控件接口的接口 ID; 第二个指定了组件容器需要的安全性配置; 第三个指定了容器如何设置这些选项。

默认情况下,MFC ActiveX 控件未标记为对脚本编写是安全的和对初始化是安全的。 控制运行在 Internet Explorer 中使用的安全级别设置为中或高时,这一点很明显。 在上述这些模式中,控件的数据是不安全或不可能可安全执行脚本以使用该控件,则可能显示警告消息。

有两个控件可用于消除这些错误的方法。 第一个涉及实现 IObjectSafety 接口的控件,并对于想要更改其行为变得"安全"如果在 Internet 浏览器的上下文中运行的控件很有用。 第二步是修改控件的 DllRegisterServer 函数,可在注册表中标记该控件的"安全"。 本文介绍了这些方法中的第二个。 第一种方法,实现 IObjectSafety 接口,Internet 客户端 SDK 中介绍。

请记住控件应该只被标为安全,如果它是,事实上,安全。 请这说明互联网客户端 SDK 文档,参阅。 在组件开发部分,请参阅"安全初始化和脚本的 ActiveX 控件"。

注意: 本文不介绍如何将控件标记为可安全执行下载。 有关代码下载和代码签名的详细信息,请参阅 Internet 客户端 SDK。

Collapse image更多信息

请按照以下步骤将 MFC ActiveX 控件标记为已对脚本编写是安全的和对初始化是安全的:
  1. 通过将下面的 cathelp.h 和 cathelp.cpp 文件添加到您的项目中实现的 CreateComponentCategory 和 RegisterCLSIDInCategory 的帮助器函数。

    Cathelp.h

          #include "comcat.h"
    
          // Helper function to create a component category and associated
          // description
          HRESULT CreateComponentCategory(CATID catid, WCHAR* catDescription);
    
          // Helper function to register a CLSID as belonging to a component
          // category
          HRESULT RegisterCLSIDInCategory(REFCLSID clsid, CATID catid);
    						

    Cathelp.cpp

          #include "comcat.h"
    
          // Helper function to create a component category and associated
          // description
          HRESULT CreateComponentCategory(CATID catid, WCHAR* catDescription)
          {
             ICatRegister* pcr = NULL ;
             HRESULT hr = S_OK ;
    
             hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,
                                   NULL,
                                   CLSCTX_INPROC_SERVER,
                                   IID_ICatRegister,
                                   (void**)&pcr);
             if (FAILED(hr))
                return hr;
    
             // Make sure the HKCR\Component Categories\{..catid...}
             // key is registered
             CATEGORYINFO catinfo;
             catinfo.catid = catid;
             catinfo.lcid = 0x0409 ; // english
    
             // Make sure the provided description is not too long.
             // Only copy the first 127 characters if it is
             int len = wcslen(catDescription);
             if (len>127)
                len = 127;
             wcsncpy(catinfo.szDescription, catDescription, len);
             // Make sure the description is null terminated
             catinfo.szDescription[len] = '\0';
    
             hr = pcr->RegisterCategories(1, &catinfo);
             pcr->Release();
    
             return hr;
          }
    
          // Helper function to register a CLSID as belonging to a component
          // category
          HRESULT RegisterCLSIDInCategory(REFCLSID clsid, CATID catid)
          {
             // Register your component categories information.
             ICatRegister* pcr = NULL ;
             HRESULT hr = S_OK ;
             hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,
                                   NULL,
                                   CLSCTX_INPROC_SERVER,
                                   IID_ICatRegister,
                                   (void**)&pcr);
             if (SUCCEEDED(hr))
             {
                // Register this category as being "implemented" by
                // the class.
                CATID rgcatid[1] ;
                rgcatid[0] = catid;
                hr = pcr->RegisterClassImplCategories(clsid, 1, rgcatid);
             }
    
             if (pcr != NULL)
                pcr->Release();
    
             return hr;
          }
    						
  2. 修改 DllRegisterServer 标记为安全的控件。在.cpp 文件在项目中找到 DllRegisterServer 的实现。您将需要此.cpp 文件中添加几个方面。包括实现 CreateComponentCategory 和 RegisterCLSIDInCategory 文件:
          #include "CatHelp.h"
    						
    定义与安全组件类别关联的 GUID:
          const CATID CATID_SafeForScripting     =
          {0x7dd95801,0x9882,0x11cf,{0x9f,0xa9,0x00,0xaa,0x00,0x6c,0x42,0xc4}};
          const CATID CATID_SafeForInitializing  =
          {0x7dd95802,0x9882,0x11cf,{0x9f,0xa9,0x00,0xaa,0x00,0x6c,0x42,0xc4}};
    						
    定义与您的控件关联的 GUID。为简单起见,可以借用从控件的主.cpp 文件中的IMPLEMENT_OLECREATE_EX宏的 GUID。稍有调整格式,以使其看起来如下所示:
          const GUID CDECL BASED_CODE _ctlid =
          { 0x43bd9e45, 0x328f, 0x11d0,
                  { 0xa6, 0xb9, 0x0, 0xaa, 0x0, 0xa7, 0xf, 0xc2 } };
    						
    若要将控件标记为安全的脚本和初始化,修改 DllRegisterServer 函数,如下所示:
          STDAPI DllRegisterServer(void)
          {
              AFX_MANAGE_STATE(_afxModuleAddrThis);
    
              if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid))
                  return ResultFromScode(SELFREG_E_TYPELIB);
    
              if (!COleObjectFactoryEx::UpdateRegistryAll(TRUE))
                  return ResultFromScode(SELFREG_E_CLASS);
    
              if (FAILED( CreateComponentCategory(
                      CATID_SafeForScripting,
                      L"Controls that are safely scriptable") ))
                    return ResultFromScode(SELFREG_E_CLASS);
    
              if (FAILED( CreateComponentCategory(
                      CATID_SafeForInitializing,
                      L"Controls safely initializable from persistent data") ))
                    return ResultFromScode(SELFREG_E_CLASS);
    
              if (FAILED( RegisterCLSIDInCategory(
                      _ctlid, CATID_SafeForScripting) ))
                    return ResultFromScode(SELFREG_E_CLASS);
    
              if (FAILED( RegisterCLSIDInCategory(
                      _ctlid, CATID_SafeForInitializing) ))
                    return ResultFromScode(SELFREG_E_CLASS);
    
              return NOERROR;
          }
    						
这些原因有两个不正常情况下应修改的 DllUnregisterServer 函数:
  • 您不想删除组件类别,因为其他控件可能正在使用它。
  • 虽然没有定义一个 UnRegisterCLSIDInCategory 函数,默认情况下 DllUnregisterServer 控件的项,从注册表删除完全。因此,从控件的注册删除类别是几乎没有什么用处。
在编译并注册您的控件,会在注册表中找到以下项:
   HKEY_CLASSES_ROOT\Component
   Categories\{7DD95801-9882-11CF-9FA9-00AA006C42C4}

   HKEY_CLASSES_ROOT\Component
   Categories\{7DD95802-9882-11CF-9FA9-00AA006C42C4}

   HKEY_CLASSES_ROOT\CLSID\{"your controls GUID"}\Implemented
   Categories\{7DD95801-9882-11CF-9FA9-00AA006C42C4}

   HKEY_CLASSES_ROOT\CLSID\{"your controls GUID"}\Implemented
   Categories\{7DD95802-9882-11CF-9FA9-00AA006C42C4}

 

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值