Chapter 12. Enhancing Page Content with Managed ActiveX Controls

ActiveX controls are double-edged swords stabbed through the heart of almost every page loaded in IE. Flash, QuickTime, Windows Media Player, the financial application your company uses ... these controls are powerful, scary, and inescapable. Solidly built controls bring amazing functionality enhancement to pages; less solid controls can act as open wounds inviting infection. IE has addressed many of the security issues over the years while carefully balancing the need to ensure this powerful extension stays powerful. It's worked so far—ActiveX controls are still a great way to enhance pages and bridge the gap between desktop applications and the Web.

Most ActiveX controls are written in C++. C++ has many advantages over higher-level languages such as Java and the .NET languages: compilation to native code, the ability to easily manipulate memory, more direct access to APIs, and so on. When functionally equivalent ActiveX controls written in C++ vs. C# (for instance) are pitted against each other, C++ will often win in terms of performance, memory consumption, disk usage, and UI responsiveness. The benefits, however, are significantly outweighed by the difficulty to understand aging methodologies. As I am sure you are well aware, this leads to insecure extensions being released into the wild—applications that put users and organizations at risk.

Managed code, specifically .NET, allows ActiveX controls to regain their reputation as really cool and useful extensions once again. Recent years have brought about significant improvements in higher-level languages in the areas where they lagged: speed, responsiveness, and application footprint. The improvements in managed code development are enhanced even more by the fact that they remain simple to understand, use, secure, and maintain. Developers can worry less about many of the low-level security issues they would encounter and need to address when developing ActiveX controls in C++. The abstraction layer offered by .NET allows them to design and build ActiveX controls that are fast, responsive, and secure.

In this chapter I teach you how to build safe, managed ActiveX controls for your web site in .NET. I begin the chapter by going over the basics of the controls, what they are, the nuances, and so forth. Following the introduction is a jump right into examples, starting with the construction of a basic control. I review how to build the public interface, define the properties and functions of a control, register the control, and finally test it on your system. I show how you can add a UI to a control next, using the simple Windows Forms system in .NET. Events are up next—I explain how you can define and raise events that hosts or scripts can pick up. Finally, I discuss safety, building safe controls, and working within the constraints defined by IE. Let's get started!

Getting to Know ActiveX Controls

ActiveX controls are OLE (Object Linking and Embedding) objects—in-process COM servers that implement the IUnknown interface. These controls expose their functionality to COM, and host applications (such as IE) can call upon it through QueryInterface. Hosts query and spin up only the objects that they need, allowing ActiveX controls to act as a lightweight library.

ActiveX controls are typically used in web pages to extend the functionality of traditional markup, script, and styles. These controls accept state requests using the IPersist family of interfaces, implement IOleClientSite so hosts can display their UI, and access other niche interfaces that IE's object model doesn't expose to script.

The IE engine (MSHTML or Trident) can expose a control's public methods to script engines such as VBScript or JScript. Developers can even extend the browser's external objects using IDispatch or IDispatchEx. Controls can expose events to scripts and can also reveal properties to markup.

Instantiated ActiveX controls have a significant number of restrictions, a number that has increased with every release of IE. For example, as of IE 8, controls in the Internet zone in Windows Vista and above will run in a low integrity context by default. ActiveX controls offer many benefits with regard to being loaded in-process with IE, but they must balance such freedom with scripting and persistence restrictions and code-signing requirements, and work within the context of UIPI and object access protection.

Architecting a Basic Control in .NET

Managed code offers developers a great way to create ActiveX controls without spending time writing potentially risky unmanaged workflows. Developers looking to extend the functionality of web pages or link web page content to system content can do so quickly through .NET.

Designing the Public Interface

ActiveX controls have one main job: to act as a gateway between a web page and desktop software. These objects provide a way to access properties, call functions, or receive events raised by the object. The first step in creating a control is to define what this "communication medium" is.

Listing 12-1 shows a C# public interface called IAxSampleBasic. This interface defines what functions and properties a control wishing to use this model must implement. In this case, the IAxSampleBasic interface exposes two properties (a string named StringPropertyTest and an integer named FunctionInputTest), a function (StringFunctionTest()) returning a string, and a void function (FunctionInputTest(...)) that will allow a web page to pass a string to a control.

Example 12.1. The IAxSampleBasic Interface, Defining the Functions and Properties for a Control

[Guid("439CE9A2-FAFF-4751-B4F7-5341AF09DBD7")]
public interface IAxSampleBasic
{
    // Properties
    string StringPropertyTest { get; set; }
    int IntPropertyTest { get; set; }

    // Functions
    string StringFunctionTest();
    void FunctionInputTest(string input);
}

Immediately preceding the interface is a Guid attribute specifying a uniquely generated GUID for this interface. In sum, this interface will be used by hosts querying for functionality in this control's COM coclass.

The next step in the architecture process is implementing this interface. Listing 12-2 shows a class called AxSampleBasicControl, a class that defines what will be a new ActiveX control. This class implements the functions defined in IAxSampleBasic, and it states that in the class definition.

Example 12.2. The AxSampleBasicControl Class—a Sample ActiveX Control Object

[ClassInterface(ClassInterfaceType.None)]
[Guid("D0E4F5FB-BAB5-45F6-9CF6-ACB1CCB526F1")]
public class AxSampleBasicControl : IAxSampleBasic
{

    public string StringPropertyTest { get; set; }
    public int IntPropertyTest { get; set; }

    public string StringFunctionTest()
    {
        // Return a sample string
        return "Test.";
    }

    public void FunctionInputTest(string input)
    {
        // Show the input string in a message box
        MessageBox.Show(input);
    }
}

This class implements the first two properties of the interface, StringPropertyTest and IntPropertyTest, as C# autoproperties. This is a simple way of asking .NET to automatically create private variables to store the values for those properties and, on creation of a class instance, set the initial value of each to the default value for the type. The function StringFunctionTest() is implemented and performs one task: returning a test string (in this case, "Test.") to the caller. The last function implemented is FunctionInputTest(...), accepting a string input. This function tests how the control will fare when being sent data from a caller; here, the control takes a string argument from the caller and displays it in a MessageBox window.

The ActiveX control is now functionally complete; however, it still cannot be loaded within IE. ActiveX controls, like all other system- or account-wide COM objects, must be registered in the HKEY_CLASSES_ROOTCLSID key of the registry. Registration will add the control's GUID to the available COM objects that hosts may create.

Listing 12-3 shows the basic architecture of the registration node for an ActiveX control. This node begins with a new key whose name matches the string value of the control's main COM-visible GUID. It is located under the HKEY_CLASSES_ROOTCLSID key (which is itself a fusion between HKEY_LOCAL_MACHINEClassesCLSID and HKEY_CURRENT_USERClassesCLSID).

Example 12.3. Registry Architecture for an ActiveX Control Application

HKEY_CLASSES_ROOT
    CLSID
      {GUID}
         InprocServer32
            (Required assemblies)
         Control
         TypeLib
            (Default) = {GUID} (REG_SZ)
            Version
               (Default) = {MajorVersion}.{MinorVersion} (REG_SZ)

The first key underneath the GUID is InprocServer32. This key houses a list of all the DLLs that this control requires as in-process dependencies in order to run. Control is the next child of the GUID key; this key is empty, its presence informing interested loaders that this COM object is an ActiveX control. The last child is TypeLib. This key's (Default) string value is the GUID of this object's TypeLib; it is the TypeLib that bridges COM with the .NET runtime and helps the framework respond to unmanaged calls. This key has a child key named Version; its (Default) string value contains the major and minor revision numbers for this object's TypeLib file.

The metadata hierarchy just described is written to the registry during the registration process for this control. Unlike unmanaged COM controls (such as those using C++), .NET applications are typically registered using RegAsm.exe, part of the .NET client framework. Classes can expose themselves to this invocation by implementing a registration function flagged with the ComRegisterFunction attribute and an unregistration function flagged with the ComUnregisterFunction attribute.

Listing 12-4 displays the registration function for this control. The first step is to create the GUID key under HKEY_CLASSES_ROOTCLSID. This function proceeds to write all the registry keys described in the previous listing: the InprocServer32, Control, and TypeLib keys, along with all of their child keys and values.

Example 12.4. COM Registration Function for the ActiveX Control Example

[ComRegisterFunction()]
public static void Register(Type type)
{
    // Create  the CLSID (GUID) key for
    // this object in the Classes key
    RegistryKey guidKey =
        Registry.ClassesRoot.CreateSubKey(
            @"CLSID" + type.GUID.ToString("B"));

    // Create the InprocServer32 key
    guidKey.CreateSubKey("InprocServer32");

    // Create the "control" subkey to inform loaders that this
    // is an ActiveX control
    guidKey.CreateSubKey("Control");

    // Create "TypeLib" to specify the typelib GUID associated with the class
    Guid typeLibGuid = Marshal.GetTypeLibGuidForAssembly(type.Assembly);

    // Create the type library key and set the typelib GUID as
// the data for that key's (Default) string value
    RegistryKey typelibKey = guidKey.CreateSubKey("TypeLib");
    typelibKey.SetValue(String.Empty, typeLibGuid.ToString("B"),
        RegistryValueKind.String);

    // Get the major and minor version values for the application
    int majorVersion;
    int minorVersion;
    Marshal.GetTypeLibVersionForAssembly(
        type.Assembly, out majorVersion, out minorVersion);

    // Create the version key and set the major and minor version
    // as the data to the (Default) string value
    RegistryKey versionKey = guidKey.CreateSubKey("Version");
    versionKey.SetValue("", String.Format(
        "{0}.{1}", majorVersion, minorVersion));
}

This class also exposes an unregistration function, this one marked with the ComUnregisterFunction() attribute. When this control is invoked by a RegAsm.exe process using the /unregister flag, this function is called (Listing 12-5).

Example 12.5. Unregister Function for the ActiveX Control Example

[ComUnregisterFunction]
public static void Unregister(Type type)
{
    // Delete the CLSID key of the control
    Registry.ClassesRoot.DeleteSubKeyTree(
        @"CLSID" + type.GUID.ToString("B"));
}

This function removes the GUID registry key (recursively removing all of its child keys and values along with it) from the HKEY_CLASSES_ROOTCLSID key.

Building the Control

The last step in creating this sample control is building and registering it. Normally, ActiveX controls used in IE are installed via a CAB file (which triggers an information bar), a CAB file containing a Setup executable, or a basic Setup executable. For development purposes, it is very convenient to bypass this process and register the output bits with the Global Assembly Cache (GAC) during the build process.

The postbuild events in Visual Studio provide a good place to call the registration functions. Listing 12-6 shows two postbuild events that should be used during the development process. The first call attempts to unregister the target assembly from the GAC if it exists; if it doesn't exist, the call will simply fail and move to the next. The second call performs an unsafe registration of the new assembly; the /codebase tag forces such a registration. This switch registers the assembly outside the GAC directories, and it should not be used in production scenarios.

Example 12.6. Build Events Using RegAsm.exe to Unregister and Then Re-register the Sample Control

"C:WindowsMicrosoft.NETFramework{.NET Version}
egasm.exe"
    /unregister "$(TargetPath)"
"C:WindowsMicrosoft.NETFramework{.NET Version}
egasm.exe"
    /register /codebase /tlb "$(TargetPath)"

These paths depend on both the location and the version of the .NET Framework on a target system. Developers installing an application onto a client machine will need to discover this information during the installation of an application.

Once these settings are in place, the control can be built. The postbuild events will automatically register the control by calling the RegAsm.exe tool; if these postbuild events are not in place, a developer will need to manually register the control (Figure 12-1).

The Registry Editor confirming regisration of the sample control

Figure 12.1. The Registry Editor confirming regisration of the sample control

The screenshot of RegEdit.exe in Figure 12-1 shows the registry keys created during the registration process. The keys here correlate to the keys described by the necessary architecture; also included are other keys automatically generated by the registration services during the process.

Developers looking to perform registration of these controls during the installation process of an application or in a stand-alone installer should note that RegAsm.exe is a stand-alone application.

Figure 12-1 highlights an important behavior of RegAsm.exe—it automatically populates the registry value representing a control's "name." IE and other applications that expose a list of COM objects through their UIs use a control's human-readable name. This name is specified in the (Default) string (REG_SZ) of a control's registration key (its GUID). By default, RegAsm.exe fills out this value for controls that it registers; it uses the format Namespace.Classname, where Namespace is the namespace containing a specific class being registered by it and Classname is the name of that same class. Applications wishing to customize this value can do so by changing the value of this key to a desired string once the registration process is complete.

Signing the Control

Code signing is an important part of the development process for ActiveX controls. In a default installation of IE, the browser will not run ActiveX controls that have not been properly signed using a valid code-signing certificate. Also, controls that have been loaded into IE that are not signed are tagged as not verified by the Manage Add-Ons UI.

Signing a control is clearly not necessary during every compilation or test run when developing a control, but it is necessary for any control that is to be released for wider audiences. Developers should not encourage customers to turn off the IE policies blocking unsigned controls; changing this setting for one application puts users at risk of compromise by unintended parties.

The code-signing process for ActiveX controls is like that of any other executable, library, or MSI file. Developers who have a valid certificate can use the command line–based signtool.exe (part of the Windows SDK, the Visual Studio SDK, and many other Microsoft packages) or their signing tool of choice.

Running the Control

The simplest way to test this newly built control is through some simple markup and script. The last piece of the sample project is an HTML page that is placed into the project's target directory when built.

The sample page, shown in Listing 12-7, loads the control through the <object> tag. This tag requests this specific ActiveX through the classid attribute, requesting the GUID exposed by the AxSampleBasicControl class ({D0E4F5FB-BAB5-45F6-9CF6-ACB1CCB526F1}).

Example 12.7. Web Page Used to Script and Test the Sample ActiveX Control

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
    <title>ActiveX - Basic Demo</title>
</head>
<body>
    <h1>
        ActiveX - Basic Demo
    </h1>
    <object id="TestControl" name="TestControl"
        classid="clsid:D0E4F5FB-BAB5-45F6-9CF6-ACB1CCB526F1">

    </object>
    <script language="javascript" type="text/javascript">
        // Test writing to/reading from properties
        TestControl.IntPropertyTest = 42;
        TestControl.StringPropertyTest = "Hello."
        alert("Int: " + TestControl.IntPropertyTest +
              "
String: " + TestControl.StringPropertyTest);

        // Test function output
        alert(TestControl.StringFunctionTest());

        // Test function input
        TestControl.FunctionInputTest("Some great input.");
    </script>
</body>
</html>

IE will load this control when it encounters the object tag during the parsing of the page.

Once the load process completes, the testing of this control is performed with some JavaScript. The script begins by setting a value of 42 to the property IntPropertyTest and then sets a string of "Hello." to StringPropertyTest. The following line confirms that these values were set by reading and displaying them through an alert box (Figure 12-2).

Dialog confirming read/write success to properties on the sample ActiveX control

Figure 12.2. Dialog confirming read/write success to properties on the sample ActiveX control

This script moves on to grabbing a string returned from StringFunctionTest(). This function with no parameters returns a test string back to the script. The script takes this string and displays it in an alert box (Figure 12-3).

Test dialog showing that functions with return values work properly in the sample control

Figure 12.3. Test dialog showing that functions with return values work properly in the sample control

The last test performed by the script is a call to a single-parameter, void return function, FunctionInputTest. The script passes the string "Some great input." to the function; as a result, the ActiveX displays a message box of its own containing the text passed to it (Figure 12-4).

Message box showing that function input works properly with the sample ActiveX control

Figure 12.4. Message box showing that function input works properly with the sample ActiveX control

This sample page successfully requests, loads, and communicates with the ActiveX control created in this section. While this example exposes basic interaction between a page and an ActiveX control, the functionality will be insufficient for some situations because the control lacks a UI and event system. The next sections describe how this functionality can be added to this basic control.

Constructing UIs

Simple functions and properties exposed by an ActiveX control can go a long way toward enhancing the functionality of a web page, but this alone is not enough for some situations; the original Facebook Image Uploader, the GE Centricity PACS system (for viewing digital MRI scans), and the MeadCo ScriptX print control are just some of the many, many controls that take advantage of the enhanced UI functionality that binary applications provide.

This section outlines the process of adding some basic UI elements to the sample managed ActiveX control in the previous example.

Adding a UI to a Managed Control

UI elements can be tough to muster when building an ActiveX control with unmanaged code. Controls built in C++, for instance, would be required to declare fonts, windows, and property pages, and even wire up to window events and handle WndProc data. Simple controls can get messy. Visual Basic introduced a simpler model for UIs in ActiveX/COM development in the late 1990s—user controls. The Visual Basic development environment offered up a designer, created code-behind automatically, and made it simple to create a COM-compatible control.

Developers can use user controls to provide a UI for an ActiveX control implemented in managed code. The previous example meets this requirement—the class is a valid COM object, exposes a type library, and correctly registers itself to the system. This ActiveX control can add a simple UI by wrapping a basic user control with an object recognizable to COM.

Listing 12-8 begins the process of adding a UI onto an ActiveX control. The control in this example (AxSampleInterfaceControl) contains all the same properties and features as the AxSampleBasicControl and expands upon them to add a UI.

Example 12.8. Sample ActiveX Control Implementing the UserControl Class Object

[ClassInterface(ClassInterfaceType.None)]
[Guid("3AEA8E0C-2AD3-455F-86A0-662A24397B80")]
public partial class AxSampleInterfaceControl : UserControl, IAxSampleInterface
{
    public AxSampleInterfaceControl()
    {
        // Initialize form components
        InitializeComponent();
    }
    ...
}

In the class definition, the UserControl class (part of the System.Windows.Forms namespace) is used as a base class for this new control, AxSampleInterfaceControl. The second new item is the addition of an InitializeComponent() call in the control's constructor; this is used to initialize the UI elements and the UI emitted by the UserControl base object instance.

The common visual and access properties on the base UserControl class should be emitted to hosts and script. Listing 12-9 shows the IAxSampleInterface interface that AxSampleInterfaceControl is built on. Two properties—Visual and Enabled—along with one function—Refresh()—are exposed to other objects through this interface.

Example 12.9. UserControl Properties and Functions Exposed Through the ActiveX Interface Definition

[Guid("F939EFDB-2E4D-473E-B7E1-C1728F866CEE")]
public interface IAxSampleInterface
{
    bool Visible { get; set; }
    bool Enabled { get; set; }
    void Refresh();
    ...

The addition of this set of properties and function allows pages, other controls, and hosts to get more information about the state and visibility of the control, as well as refresh and repaint the object as needed.

The sample code in this section uses the Visual Studio designer to create a simple UI. There is a title label stating the control's purpose, a text box the user may type into, and a button that can show the text box data in a message box.

After compilation, developers will need to perform the registration process for this sample control again. Re-registration needs to be performed after the control is changed to ensure that the latest version of the control is acknowledged by applications using it (such as IE).

Figure 12-5 shows the UI for this control in a web page.

User interface and input demo with the sample ActiveX control

Figure 12.5. User interface and input demo with the sample ActiveX control

This demo accepts user input using a TextBox control named MessageTextBox. The ShowMessageButton has a click handler that reads the current Text property inside of the MessageTextBox instance and displays it to a user via a MessageBox window. Listing 12-10 holds the code for this event handler.

Example 12.10. Button Click Event Showing Input Text in a Message Box

private void ShowMessageButton_Click(object sender, EventArgs e)
{
    // Show a message box with the current text
    MessageBox.Show(MessageTextBox.Text);
}

The control can be compiled and tested once this item is added. Figure 12-6 shows a test run using this control. The string "Super test" was placed into the text box and the main button was clicked.

Message box signaling success from a sample ActiveX UI input test

Figure 12.6. Message box signaling success from a sample ActiveX UI input test

As expected, this control pops up a MessageBox with the content "Super test."

Setting a Control's OLE UI Flags

The last step in getting a managed ActiveX control to work in an IE window (or other OLE windows) is to register the OLEMISC flags that this application supports. Without these flags, the application might not receive the right focus or paint events by its parent object.

Flag registration for OLE UI support should be done during the registration process. The use of these flags is shown in Listing 12-11.

Example 12.11. COM Registration Function for the Sample ActiveX Control Now Setting OLE Flags

[ComRegisterFunction()]
public static void Register(Type type)
{
    ...

    // Set the misc status key, informing hosts of the visibility
    RegistryKey miscStatusKey = guidKey.CreateSubKey("MiscStatus");

    int miscStatus =
        NativeMethods.OLEMISC.OLEMISC_RECOMPOSEONRESIZE +
        NativeMethods.OLEMISC.OLEMISC_CANTLINKINSIDE +
        NativeMethods.OLEMISC.OLEMISC_INSIDEOUT +
        NativeMethods.OLEMISC.OLEMISC_ACTIVATEWHENVISIBLE +
        NativeMethods.OLEMISC.OLEMISC_SETCLIENTSITEFIRST;

    miscStatusKey.SetValue(String.Empty, miscStatus.ToString(),
        RegistryValueKind.String);
    ...
}

The constants defined here are included in the companion sample code for this book. These values are set to the registry any time the control is registered using RegAsm.exe.

Exposing Events to ActiveX Hosts

The sample ActiveX control presented in the last few sections is nearly complete. It provides added functionality, getters and setters, and even a UI. Unfortunately, pages implementing this control have no way of receiving messages back from the control or getting notifications when something of interest in the control may be occurring.

The following section describes the process of adding the last major piece of functionality, event support, to the ActiveX control.

Creating the Event Interface

Managed ActiveX controls can expose events to host applications through the addition of an event interface. This event interface describes each public event that the ActiveX control class will expose and assigns each a DISPID value; this value allows the interface to be accessed via IDispatch.

Listing 12-12 shows the basic events interface being added to the previous examples. This interface first defines a Guid for itself, and then declares that its interface type is compatible with IDispatch. The event interface exposes one event, OnClick(), with a DISPID value of 0x1.

Example 12.12. Custom Event Interface for the Sample ActiveX Control

[Guid("F1A732D4-5924-4DA7-85D7-4808BD7E6818")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface AxSampleEventsEvents
{
    // Expose the OnClick event with DISPID 0x1
    [DispId(0x1)]
    void OnClick();
}

With the event interface definition in place, the next step is tying the main ActiveX control to the events defined within it. Listing 12-13 shows the basic premise of the process. First, an attribute is appended to the class definition for the ActiveX control (in this case, AxSampleEventsControl) called ComSourceInterfaces. This attribute is passed the type of the AxSampleEventsEvents interface; when the project is run, this GUID will be referenced when searching for and firing events.

Example 12.13. Addition of Event Interface, Delegate, and Event Object to the Sample ActiveX Control

[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(AxSampleEventsEvents))]
[Guid("C85CA4EB-3996-444A-91FE-B9045C94AD38")]
public partial class AxSampleEventsControl : UserControl, IAxSampleEvents
{
    public delegate void OnClickEventHandler();
    public new event OnClickEventHandler OnClick = null;
    ...

The delegates, events, and callers must be added once the class references the new interface. This example continues by adding a new OnClickEventHandler() delegate that contains the parameter types (in this case, none) of events implementing it. Next, the OnClick() event references the OnClickEventHandler delegate and will use its signature.

The last step is firing the event. The button from the previous examples was changed to a button that fires the OnClick() event when clicked. Its name was changed to FireEventButton, and Listing 12-14 shows its personal Click() event handler.

Example 12.14. Button Click Handler Firing a Click Event to Hosts of the Sample ActiveX Control

private void FireEventButton_Click(object sender, EventArgs e)
{
    // Show a message box with the current text
    if (null != OnClick) OnClick();
}

Whenever this button is clicked, this function fires the OnClick() event to hosts using the current control instance. If there are no event listeners (the OnClick() event is null and not bound to a callback), nothing happens at all.

Listing 12-15 presents a web page used to test the events exposed by this application. As in the previous examples, it loads the control via an <object> tag. Script is used to hook into the event—the event handler, formatted as TestControl::OnClick(), is defined. When it is raised by the object instance, this function throws an alert box stating "The button was clicked!"

Example 12.15. HTML and Script for Testing the OnClick() Event Exposed by the Sample Control

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
    <title>ActiveX - Events Demo</title>
</head>
<body>
    <h1>
        ActiveX - Events Demo
    </h1>
    <object id="TestControl" name="TestControl"
        classid="clsid:C85CA4EB-3996-444A-91FE-B9045C94AD38"
        viewastext>
    </object>
    <script type="text/javascript">
        // Create an OnClick handler for the control
        function TestControl::OnClick()
        {
            alert("The button was clicked!");
        }
    </script>
</body>
</html>

This syntax may look familiar to C++ developers, but it is completely unrelated. The double-colon syntax (::) is defined by the Microsoft JScript engine (the JScript engine is oftentimes referred to as JavaScript in the context of HTML pages) as a way to connect to an event defined by an object. When the object is loaded by JScript during page download, valid object events (those that have correct DispIds defined) are noted by the engine. When the script is parsed and executed, any function whose name is in the format of ObjectInstance::ExposedEvent (where ObjectInstance is the object instance in question and ExposedEvent is the name of a valid event) is treated as a callback. Thus, these functions are called whenever the events they refer to are raised.

Figure 12-7 shows the page in action. The left screenshot shows the normal page with the ActiveX loaded within in it. Upon clicking the button within the control, the browser pops up an alert(...) box, as shown in the right image.

The ActiveX events test page and the result of the OnClick() event handler

Figure 12.7. The ActiveX events test page and the result of the OnClick() event handler

Practicing Safe ActiveX with IObjectSafety

IE offers a myriad of built-in safeguards aimed at protecting users from system infection and data breaches. ActiveX controls have been placed under considerable scrutiny since their inception, a scrutiny that increases as more and more unsavory attackers exploit the attack surface of installed ActiveX controls.

Staying Safe in the Great IUnknown

The IObjectSafety interface was created to plug a hole in the almost unchecked restrictions that ActiveX controls have given their nature as in-process COM servers. All versions of IE block unfettered access to initialization and scripting of ActiveX controls by Internet content unless the control is marked as safe for use by untrusted callers. Controls that wish to by hosted by Internet content must implement this interface and declare that they are "safe" for use. ActiveX controls that are not marked as safe will not run at all in all zones other than the Local Machine zone, where the prompt in Figure 12-8 is shown.

Error dialog informing the user that a page is accessing a possibly unsafe control

Figure 12.8. Error dialog informing the user that a page is accessing a possibly unsafe control

Initialization protection keeps ActiveX controls safe from repurpose attacks that may pass malicious data to the control via PARAM tags (which are sent to the control via the IPersist family of interfaces). Controls that persist state and properties implement IPersist functionality to do so; this allows external applications the ability to pass state back and forth between target controls. Controls not specifically designed to safely handle untrusted input could allow a potential attacker to send malformed state to a control in an attempt to take control of a system.

Similarly, scripting safety refers to protection against attempts by a script or external application to use an ActiveX control's public properties, functions, or events, since controls not designed to handle untrusted callers may be exploitable.

Implementing IObjectSafety

Before initializing an ActiveX control or permitting calls to it, IE requires controls to indicate that they will safely handle untrusted input and callers. After instantiating a control, IE attempts to call the IObjectSafety.SetInterfaceSafetyOptions() method. This call determines if a control explicitly states it will behave safely if it encounters untrusted initialization data and callers.

Controls declare themselves safe for initialization by returning INTERFACESAFE_FOR_UNTRUSTED_DATA. This constant informs IE that the control can securely handle any initialization data it may be passed (e.g., in the PARAM attributes of an OBJECT tag). Controls may also declare themselves "safe for scripting" by returning INTERFACESAFE_FOR_UNTRUSTED_CALLER. This constant informs IE that the control's methods and properties may be called or set by JavaScript or VBScript. If a control fails to implement the SetInterfaceSafetyOptions() method, or fails to indicate that it is safe for untrusted data or untrusted callers, IE will leak the control to prevent memory safety issues when unloading the object.

The control used in the previous section is once again extended, this time to support this protection mechanism. Listing 12-16 shows the C# implementation of the IObjectSafety interface. This implementation contains two functions: GetInterfaceSafetyOptions(...) and SetInterfaceSafetyOptions(...).

Example 12.16. IObjectSafety Implementation for C#

[ComImport]
[Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IObjectSafety
{
    [PreserveSig]
    int GetInterfaceSafetyOptions(ref Guid riid, out int pdwSupportedOptions,
                                   out int pdwEnabledOptions);

    [PreserveSig]
    int SetInterfaceSafetyOptions(ref Guid riid, int dwOptionSetMask,
                                  int dwEnabledOptions);
}

The GetInterfaceSafetyOptions(...) function is called by host controls to determine what security measures a child control has in place. Child controls (such as this one) report back whether the control in question protects against persistence and scripting attacks. SetInterfaceSafetyOptions(...) goes in the opposite direction, enabling the host to demand certain protections; the ActiveX control is expected to return an error code if the desired safety is not supported.

The constants used to define whether the interface is safe or unsafe in the different scenarios outlined by IObjectSafety are defined in the INTERFACESAFE class in Listing 12-17. Controls that protect against or are not vulnerable to attacks of persistence or state can respond with INTERFACESAFE_FOR_UNTRUSTED_DATA in GetInterfaceSafetyOptions(...); those that protect against or are not vulnerable to attacks against exposed script can return the constant INTERFACESAFE_FOR_UNTRUSTED_CALLER in that same function.

Example 12.17. INTERFACESAFE Constants Used by IObjectSafety

public static class INTERFACESAFE
{
    public const int INTERFACESAFE_FOR_UNTRUSTED_CALLER = 0x00000001;
    public const int INTERFACESAFE_FOR_UNTRUSTED_DATA = 0x00000002;
}

The last step is actually implementing the interface in the ActiveX control. Listing 12-18 shows the implementation of both functions defined within it. The control, since its functionality is heavily restricted as to what data can be placed into it and what can be called, informs parent controls that it is safe to provide it untrusted data and that untrusted callers may call its functions and set its properties (Listing 12-18).

Example 12.18. Implementation of the IObjectSafety Interface in the Sample ActiveX Control

int NativeMethods.IObjectSafety.GetInterfaceSafetyOptions(
    ref Guid riid, out int pdwSupportedOptions,
    out int pdwEnabledOptions)
{
    // Reveal supported and enabled safety options
    pdwSupportedOptions = pdwEnabledOptions =
        NativeMethods.INTERFACESAFE.INTERFACESAFE_FOR_UNTRUSTED_CALLER |
        NativeMethods.INTERFACESAFE.INTERFACESAFE_FOR_UNTRUSTED_DATA;



    return S_OK;
}
int NativeMethods.IObjectSafety.SetInterfaceSafetyOptions(
    ref Guid riid, int dwOptionSetMask, int dwEnabledOptions)
{
    // As our code does not have an UNSAFE mode, tell
    // the Host that we support any safety options it
    // had demanded
    return S_OK;
}

The implementation of this interface enables this control to run in any security zone where IE demands that controls safely handle untrusted data and callers.

Looking At Alternative Platforms and Technologies

The examples in this chapter show that in-page extensibility is a powerful tool for linking access to user data and processing power of stand-alone applications to the versatility and breadth of information that the Web has to offer. ActiveX controls, however, are an IE-only technology. While Google, at the time of this writing, has demonstrated cross-browser encapsulation of these controls through its Chrome browser (and the open source Chromium browser), use of these controls outside of IE has not yet become a mainstream practice. Developers building ActiveX controls should be aware that these controls will only work on IE.

This is not to say that other technologies, even ones very similar, don't exist in other browsers and as part of other platforms. Netscape, Mozilla Firefox, and other browsers using the Netscape Plugin Application Programming Interface (NPAPI) have similar abilities to link page content to binary code through the browser. In fact, IE supported NPAPI controls in early versions of the browser until IE 5.5. These controls even share a somewhat checkered past in terms of security: both have been found to have significant security flaws in the past (though ActiveX received the brunt of the public and PR backlash). The only major architectural difference is that NPAPI has an API constructed specifically for use in browsers and web pages, whereas ActiveX controls are the same COM objects used in all of Windows development plus a fancy name.

ActiveX controls should not be confused with script-based technologies such as Silverlight and Flash. In fact, IE versions of these products are in and of themselves ActiveX controls! These products, in many ways, wrap and expose binary functionality that is of particular use to web development. Their ease of use coupled with their wide distribution allows developers to enhance their pages without requiring custom ActiveX or NPAPI controls, and allows users to run millions of applications across the Web without having to install a new control more than once.

Summary

In this chapter I reviewed ActiveX controls: what they are, how to create them, how to lock them down, and how to make them work for you. The chapter started out with a primer on the controls themselves. I jumped right in with the construction of a basic control, the interfaces and implementations involved, the registration of a control, and the testing of a control using a sample page. User interfaces were next, where I presented a simple way of incorporating Windows Forms and user controls into the control and using those to create a good user experience. I covered events next, going into detail on how events can be registered for and thrown to hosts by an ActiveX control. Last, I covered safety issues and how to convey the safety and security of a control to IE, allowing use of the control by untrusted pages.

ActiveX controls are a wonderful solution for developers looking for a little extra system access or computing power in web pages. This is especially true for controls hosted on intranet or enterprise web applications. Despite their power, poorly built extensions have been a target for exploitation for years. IE has significantly reduced its attack surface in the last few releases—a balancing act that has resulted in a safer extension model and one that remains useful in adding some great functionality to pages.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.220.120