剖析ActiveX控件安全问题

Dissect ActiveX Control Safety

1、介绍

如果你曾经在网页或者ASP中使用过com对象,你可能会发现,有时候会出现这样讨厌的对话框

     这是因为你的控件没有被标记为安全的,对于初始化不安全或者对于脚本不安全,甚至兼而有之。你每打开一次这样的网页,这种情况就会发生一次,你怎么办?当然,这可以通过设置IE本身的安全等级为low来解决这样的问题,但是如果你要制作一个可发布的控件,你能想象到每一位用户在使用你制作的控件时都要且列抱怨这种强制行为;或者如果你是其中一个使用者,当你同样遇到这种情况之后不得不将自己的IE安全等级设置为low,与此同时你平时上网过程中那些行为不轨的控件有时也会悄然而至,你的灾难来了!作为一个聪明的程序员,你不能要求任何用户做一些不切实际或者不够安全的改变来适应你的产品,因为你知道那只会使你的控件被逐渐打入冷宫,到最后销声匿迹,那不是你想要的。我们要消灭掉这样的对话框而且还不让用户发现丝毫安全上和使用上的失望。本文就是横向探讨在C++编程环境下如何消除这些问题的。

2、原理

     ActiveX控件是一种极其危险的提供功能的方法(目前正在被MS逐渐冷落),因为它是一种组建对象模型(COM)的对象,只要电脑的用户可以完成的任务,它都可以完成。比如它可以存取注册表,可以随意访问本地文件系统等等。一个网页上面的控件一般有2种不安全的状态,一种是脚本的不安全,一种是初始化的不安全。当用户将一个压缩解压缩控件指向一个远程被压缩的包含特洛伊木马的系统文件并且需要控件来解压缩这个文件时,系统安全会被打破。这个状态就是初始化的不安全。从代码的角度来讲,如果控件从IPersist派生,也就是说控件实现了永久性,那么就会触发unsafe for initializing。而在脚本程序安全执行以前,一个控件依赖于特定的系统设置,那么在允许这段代码运行之前,控件的开发人员需要提供一些必要的代码。从代码的角度来讲,如果控件从IDispatch派生,也就是说控件支持脚本,那么就会触发unsafe for scripting。

     从用户下载一个ActiveX控件开始,这个控件甚至可能很容易被攻击,因为网络上任何网络程序都可以使用它,无论是出于友好的目的还是恶意的目的。因此IE浏览器(本文只探讨IE内核的浏览器)总是试图弹出一个对话框来告诉你,这个控件可能是不安全的。这几乎总是一个很好的预防网络攻击的好方法,但是对于那些我们认为总是安全的控件,我们仍然要总是接受这种IE产生的干扰,这就使人厌烦了。其实当身为程序员的你写这样的安全控件时,这样的问题是很容易解决的。

3、解决方法

     目前,对这个问题的解决方法主要有几种:使用数字签名,继承IObjectSafety接口,修改注册表等。

3.1、使用数字签名

     数字签名是一种使控件足够安全的方法,它通过特定的密钥来加密控件的使用,使得使用控件的对象能够根据密钥是否相符来检测控件是否足够安全。通常,拥有自己的可发布的数字签名是要Money的,本文是一篇技术文章,对此并不深入探讨,下面主要介绍代码方面的安全化。

3.2、继承IObjectSafety接口

     IObjectSafety接口是在头文件"objsafe.h"中定义的接口,定义如下:

IObjectSafety : public IUnknown {     public:     virtual HRESULT STDMETHODCALLTYPE GetInterfaceSafetyOptions(      /**//* [in] */ REFIID riid,     /**//* [out] */ DWORD *pdwSupportedOptions,     /**//* [out] */ DWORD *pdwEnabledOptions) = 0;     virtual HRESULT STDMETHODCALLTYPE SetInterfaceSafetyOptions(      /**//* [in] */ REFIID riid,     /**//* [in] */ DWORD dwOptionSetMask,     /**//* [in] */ DWORD dwEnabledOptions) = 0; };

     其中参数意义如下:

//riid                Interface identifier for the object to be made safe.  // //dwOptionSetMask     Options to be changed.  // //dwEnabledOptions    Settings for the options that are to be changed. This can be one of the following values.  //                    INTERFACESAFE_FOR_UNTRUSTED_CALLER //                        Indicates the interface identified by riid should be made safe for scripting.  //                    INTERFACESAFE_FOR_UNTRUSTED_DATA //                        Indicates the interface identified by riid should be made safe for untrusted data during initialization.

     这个接口允许容器询问控件是否安全或者改变控件的安全属性。目前IObjectSafety支持四种安全属性,但是一般我们只使用前两个:脚本安全和初始化安全。这些属性定义如下:

// Option bit definitions for IObjectSafety: #define    INTERFACESAFE_FOR_UNTRUSTED_CALLER        0x00000001    // Caller of interface may be untrusted #define    INTERFACESAFE_FOR_UNTRUSTED_DATA             0x00000002    // Data passed into interface may be untrusted #define    INTERFACE_USES_DISPEX                                             0x00000004    // Object knows to use IDispatchEx #define    INTERFACE_USES_SECURITY_MANAGER                0x00000008    // Object knows to use IInternetHostSecurityManager

3.2.1、实现方法

     首先包含头文件

#include "objsafe.h"

     然后是自己的控件类继承IObjectSafetyImpl

class YourClass :          public IObjectSafetyImpl<YourClass, INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA> {

     其中INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA 代表默认这个控件是脚本安全而且初始化安全的。然后在com接口表中添加接口名

BEGIN_COM_MAP(CCGrid)          COM_INTERFACE_ENTRY(IObjectSafety) END_COM_MAP()

     OK,大功告成。这也是一种很简单的方法。

3.3、修改注册表

     修改注册表项使控件支持安全类别的原理,归根到底其实就是下面的第三个方法,另外两个方法都是对这个方法的外围包装和更加安全的处理。修改注册表在不同的工程中有着不同的表现:

3.3.1、用于ATL属性工程的com接口

     因为VC的属性工程已经加入了安全控件的注册表支持,并且直接加入了关键字implements_category,因此我们仅仅象下面这样既可完成安全属性设置:

[     coclass/progid/vi_prgid,            //必须至少有其中一个          implements_category("CATID_SafeForScripting"),     implements_category("CATID_SafeForInitializing"),      ]

     不过要注意的是,对于类似下面的代码(往往是非属性工程的)是不能添加的:

[     uuid(),     helpstring("") ] coclass YourCtrl {     [default] interface ,         [default,source] dispinterface  }

     因为这种coclass是IDL的属性,而IDL本身并没有安全功能。

     而上面的那种coclass是VC的属性,MS提供了对安全控件的支持。

3.3.2、主要用于非属性工程的通用方法

     在类声明文件中加入头文件

#include "objsafe.h"

     在类声明中添加如下代码:

   BEGIN_CATEGORY_MAP( YourCtrlClassName )        IMPLEMENTED_CATEGORY( CATID_SafeForScripting )        IMPLEMENTED_CATEGORY( CATID_SafeForInitializing )    END_CATEGORY_MAP()

     即可使其支持safety属性,当然你也可以选择性的只支持一种安全属性。

3.3.3、主要用于MFC ActiveX Control的通用方法

     在$project.cpp文件中添加以下代码:

#include "comcat.h" #include "Objsafe.h" // 控件的CLSID,注册表用(一定要是实际使用的控件的) const GUID CDECL CLSID_SafeItem ={ 0x7AE7497B, 0xCAD8, 0x4E66, { 0xA5,0x8B,0xDD,0xE9,0xBC,0xAF,0x6B,0x61 } }; // 版本控制 const WORD _wVerMajor = 1; // 次版本号 const WORD _wVerMinor = 0; /**/////////////////////////////////////////////////////////////////////// // 创建组件种类 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; } // 注册组件种类 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; } // 卸载组件种类 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))     {         // Unregister this category as being implemented by the class.         CATID rgcatid[1] ;         rgcatid[0] = catid;         hr = pcr->UnRegisterClassImplCategories(clsid, 1, rgcatid);     }     if (pcr != NULL)         pcr->Release();     return hr; }

     修改以下函数:

// DllRegisterServer - Adds entries to the system registry STDAPI DllRegisterServer(void) {     HRESULT hr;     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; } /**/////////////////////////////////////////////////////////////////// // DllUnregisterServer - Removes entries from the system registry STDAPI DllUnregisterServer(void) {     HRESULT hr;     AFX_MANAGE_STATE(_afxModuleAddrThis);     if (!AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor))         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;

     至此对工程的修改完成了,现在控件就可以在自注册时就注册为安全控件了!

3.3.4、手动修改注册表

     我们可以自己手动为控件在注册表中添加安全支持,实际上我们所需要的两个项(Key)就是这样的形式:

\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\<GUID of control class>\Implemented Categories\<GUID of category>

     ActiveX SDK头文件ObjSafe.H 已经为类别CATID_SafeForInitializing和CATID_SafeForScripting定义了GUID值。因此对于特定的工程,如果控件类的GUID是{20048BB3-DB68-11CF-9CAF-00AA006CB425},那么我们就只需要添加两个注册表项:

   REGEDIT4            [HKEY_CLASSES_ROOT\CLSID\{20048BB3-DB68-11CF-9CAF-00AA006CB425}\Implemented Categories]            [HKEY_CLASSES_ROOT\CLSID\{20048BB3-DB68-11CF-9CAF-00AA006CB425}\Implemented Categories\{7DD95801-9882-11CF-9FA9-00AA006C42C4}]            [HKEY_CLASSES_ROOT\CLSID\{20048BB3-DB68-11CF-9CAF-00AA006CB425}\Implemented Categories\{7DD95802-9882-11CF-9FA9-00AA006C42C4}]

     将以上代码拷贝到一个*.reg中去,保存后执行就可以将GUID为{20048BB3-DB68-11CF-9CAF-00AA006CB425}的控件类注册为安全控件。有时候注册表中不一定有那两个类别,因此我们还需要自己来描述这两个重要的类别:

REGEDIT4 [HKEY_CLASSES_ROOT\Component Categories] [HKEY_CLASSES_ROOT\Component Categories\{7DD95801-9882-11CF-9FA9-00AA006C42C4}] "409"="Controls that are safely scriptable" [HKEY_CLASSES_ROOT\Component Categories\{7DD95802-9882-11CF-9FA9-00AA006C42C4}] "409"="Controls safely initializable from persistent data"

     同样,将以上代码拷贝到*.reg文件中执行后,就可以生成这两个类别。以后就可以使用这两个类别来注册安全控件了。注意:除非你没有别的办法,千万不要使用这个方法,也不要使用这个方法来标记实际上并不安全的控件为安全控件。

     那么方法3和方法1和2有什么不同的呢?其实他们最大的不同就是方法1和2实际上都加入各种判断代码在修改注册表之前和之后,实际上也防止了不安全控件注册为安全控件的行为。因此除非万不得已,不要用方法3。就算是方法1和方法2,也要在确定自己控件足够安全的情况下才使用。

3.4、另一个问题

  在施行了这些方法后,那个讨厌的对话框没了,但是对于window XP SP2及以上版本来说,用默认安全级别的IE打开任何包括了控件或者脚本的网页时,在运行代码前都会弹出一个这样的条框:

     这也是为了不断提醒用户这样的操作可能会有安全上的危险,可是这样也会影响用户的操作,对于一个频繁访问的某一个页面来说,这个横条和对话框都是不易忍受的,如果你正在编写这样包括脚本和控件的页面,那么这里有一个窍门来解决这样的问题。请把下面这句加在网页文件源代码的<html>和<head>之间:

<!-- saved from url=(0017)http://localhost/ -->

     在你做网页时,如果网页需要运行ActiveX或脚本,并且他们位于客户端以外的地方,那么可以添加这个注释语句,IE当然不会不理他, IE会按照他指出的URL去找脚本的位置。 这句话的作用是让Internet Explorer 使用 Internet 区域的安全设置,而不是本地计算机区域的设置。其中0017代表后面网址的字符个数,后面的网址字符串要指向注册了这个组件或脚本的地方。如果是象上面那样,就表明是在自己的电脑上。把这句话删除,有些脚本就不执行了。

3.5、什么是足够安全的控件

     我怎么知道我的控件是不是足够安全呢?请好好看看这一节。一旦标注出现在控件上而非网页上时,那些标注为安全的控件就一定要在所有可能的网页上是安全的。因此一个控件被标注为安全的就表示它能够保护自己并与网页制作者可能在初始化或脚本过程中做的不愉快的东西隔离。实际上很容易检验当被用于网页时一个控件是否安全的:当你标注你的控件为初始化安全时,相当于你在发誓无论用什么样的值来初始化你的控件,都不可能发生伤害用户的系统或者威胁到用户的安全;当你标注你的控件为脚本安全时,那就是说你有把握说无论控件的函数和属性如何被网页的脚本操作,控件本身都不会做出危及用户安全的行为。换句话说,它必须接受任何脚本中任何顺序的函数和/或属性调用而不会发生危险。 在设计控件过程中,下面是一些表明此控件是安全的条款:

  • 不要操作用户的文件系统;
  • 不要操作注册表(除非是注册和注销它本身);
  • 不要数组越界或其他的内存错误;
  • 验证(或更正)所有的输入,包括初始化,方法的参数和属性的Set函数;
  • 不要滥用与用户有关的活是用户提供的数据;
  • 做大量的测试。

     这份表还远没有结束,但这些至少都是必要的。还有一点很重要,就是千万不要把本来实际上不安全的控件注册为安全的,尽管这很诱人(比如说你没有控件的源代码)。无论控件做什么事情的,一旦控件被标注为安全的,那么所有的网页都会省略对这个控件的安全检测。到目前为止,还没有方法能把一个控件标注为仅对特定网页是安全的。标注不安全控件为安全空间的一个简单安全的选择就是写一个包含了不安全控件的新安全控件。只要确保新安全控件的初始化,方法和属性都是安全的就行了。

4、总结

     控件的安全问题可以通过各种各样的方法来巧妙的解决,但是对于一个负责的程序员来说,一定要确保控件本身是绝对安全的。一个欲发布的控件往往是要有用户使用的,如果本身不安全的控件被当成安全的控件流传出去,那就是恶意行为了。为了对用户负责,也为了对自己负责,程序员一定要再三检查自己的控件是否足够安全,而后再决定是否发布及以何种方式发布。

时间: 2024-11-05 13:30:29

剖析ActiveX控件安全问题的相关文章

基于MFC的ActiveX控件开发教程------------浏览器插件之ActiveX开发

一般的Web应用对于浏览器插件能不使用的建议尽量不使用,因为其涉及到安全问题以及影响用户安装(或自动下载注册安装)体验问题.在有特殊需求(如涉及数据安全的金融业务数据交互.需插件才能实现的与本地设备的交互等)的情况下可以酌情慎用. 浏览器插件总体可以划分为两大阵营,即IE支持的插件以及非IE支持的插件.本来在Netscape时代,对于浏览器插件是有公用的规范的(NPAPI),一开始所有浏览器都支持该规范,包括IE.后来出于商业原因,微软的IE不再支持NPAPI,改而自己开发了一套基于COM的Ac

ActiveX控件(ATL篇)

目录 第1章 VC++6.0创建    2 1.1 目标    2 1.2 创建项目    2 1.3 增加COM类    4 1.4 属性    7 1.5 事件    8 1.6 实现连接点    9 1.7 编码    11 1.7.1 增加成员变量    11 1.7.2 初始化成员变量    11 1.7.3 完成属性赋值代码    11 1.7.4 完成控件绘制代码    11 1.7.5 响应鼠标左键按下消息    13 1.7.6 修改DllUnregisterServer   

ActiveX控件(MFC篇)

目录 第1章 VC++6.0创建控件    1 1.1 目标    1 1.1.1 方法    1 1.1.2 属性    1 1.1.3 事件    1 1.2 创建项目    2 1.3 项目结构    6 1.3.1 COM接口    6 1.3.2 COM类    7 1.3.3 属性页    7 1.3.4 应用程序类    8 1.3.5 注册与注销    8 1.4 方法    9 1.4.1 增加    9 1.4.2 删除    11 1.5 属性    12 1.5.1 Te

16.COM组件技术应用之一:ActiveX控件

一.ActiveX控件(COM组件技术的应用之一) flash.ocx 1. 概念 1.1 ActiveX控件的概念 基于COM组件技术,可以被任何支持COM标准的计算机程序的项目使用. 可是采用拖拽的方式创建,使用向一般控件一样方便.文件的后缀是ocx,有时称ocx控件. 1.2 COM组件的概念 Component Object Model,组件对象模型.可以改善项目的架构,使得项目可维护性和可复用性更好. COM组件是一些小的可以执行的二进制文件,以接口的方式为其他的程序.系统和组件提供服

C#开发ActiveX控件

昨天写了篇博客<Winform 程序嵌入WPF程序 并发送消息>,没有说明为什么要嵌入WPF程序,那么今天就来唠叨唠叨其中的一个使用场景,开发ActiveX控件 首先,新建一个类库工程HuaYun.ActiveX,右键工程属性,在“应用程序”页,点击“程序集信息”按钮,在弹出的窗体里勾选“使程序集COM可见”,具体操作如下图 第二步,切换到“生成”的选项卡,勾选“为COM互操作注册”,如下图 第三步,在AssemblyInfo.cs里添加[assembly: AllowPartiallyTru

【转载】基于MFC的ActiveX控件开发(3)

原文:http://iysm.net/?p=122 3.事件 ActiveX 控件使用事件通知容器控件上发生了某些事情.事件的常见示例包括单击控件.使用键盘输入数据和控件状态更改.当发生这些操作时,控件将引发事件以提醒容器. MFC 支持两种事件:常用和自定义.常用事件是 COleControl 类自动处理的事件.自定义事件使控件得以在该控件特定的操作发生时通知容器.控件内部状态发生更改或收到某个窗口消息即属于此类事件. 常用事件 常用事件由 COleControl 类自动引发.COleCont

如何取消IE“已限制此网页运行可以访问计算机的脚本或ActiveX控件”

在本地调试html页,如果其中包含js或flash,IE经常会提示“IE已限制此网页运行可以访问计算机的脚本或ActiveX控件”.虽然IE出于安全考虑阻止本地脚本运行这个做法没错,但作为程序开发者来说,每次都要点允许,非常之讨厌,于是开始想办法去掉这个提示. 方法是:工具 – Internet选项 – 高级标签 – 在安全分类下面,有一项“允许活动内容在我的计算机上的文件中运行*”.我们要找的就是他!打上对勾后重启IE就行了(如果还开着其它程序,只关IE浏览器窗口是没有效果的,还需要重启电脑)

Delphi 编写ActiveX控件(OCX控件)的知识和样例(有详细步骤)

一.ActiveX应用情况简介: ActiveX控件也就是一般所说的OCX控件,它是 ActiveX技术的一部分.ActiveX是微软公司推出的基于组件对象模型COM的技术,包括对Windows 32位应用编程接口(Win32 API)和组件对象模型的一系列扩充和增强,目标是把计算机桌面环境与因特网环境集成起来,同时保护在Windows技术中现有的开发投资.微软的 ActiveX技术根本上就是修改过的OCX技术,使它能够跨越Internet,主要是使用WWW来传递控件.            A

IE保护模式下ActiveX控件打不开共享内存的解决方案

原文:http://www.cppblog.com/Streamlet/archive/2012/10/25/193831.html 感谢溪流漫话的投递 IE保护模式下,ActiveX控件会打不开别的进程创建的共享内存,原因是IE运行在低完整性级别权限下,一般应用程序运行在中完整性级别.别的应用程序创建的共享内存,即使赋予Everyone权限,ActiveX控件仍然会打不开. 解决方案:创建共享内存的时候,设置下完整性级别: bool SetLowLabelToKernelObject(LPCT