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; 第二个指定了组件容器需要的安全性配置; 第三个指定了容器如何设置这些选项。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值