Never Thought I'd Still be Dealing with This: Insecure ActiveX Controls!

Over the last couple of months, I have worked with some customers still using custom-written ActiveX controls, and in more than one instance, the controls were vulnerable to attack. One customer asked how they can go through their controls quickly to triage which controls to review first. As a general rule I look to see if the control supports the IDispatch interface (so it can be scripted and browser defenses aside, called from client-side script in HTML) and the IObjectSafety interface (so it can inform the host that the object is safe, honest!)

There're other things you can look for, but these are just quick, rough, first-order triage items I look for. Note that simply supporting IObjectSafety does not imply that the developer has decided to object is safe to call from script, because the control might be SiteLock'd.

So the next question is how do you check a control, say, Happy.House.1, to see if it supports these two interfaces? In the good ol' days, I'd use a tool like OleView, but I can't find it anywhere trustworthy. Which means I have to write some code! So here's some sample C++ code I threw together to do a first-order pass.

Note that there are other nuances about Safe for Scripting and Safe for Init, but I won't go over that in this post just yet.

 #include <objbase.h>
#include <objsafe.h>
#include <iostream>

int wmain(int argc, wchar_t **argv) {
 if (argc != 2) {
  wcout << L"Please enter the friendly name of the COM object (eg; foo.bar.1)" << endl;
  return -1;
 }
  wchar_t *wszObjectName = argv[1];
 wcout << L"Checking " << wszObjectName << endl;
 
 CoInitialize(0);
  CLSID clsid;
 HRESULT hr = CLSIDFromProgID((LPCOLESTR)wszObjectName, &clsid);
 if (FAILED(hr)) {
  wcout << L"Could not find the object, is the name correct?" << endl;
  return -1;
 }
  struct { IID iid; wchar_t *pwszIid; }
  iid[] = { {IID_IDispatch, L"IDispatch"}, 
      {IID_IObjectSafety, L"IObjectSafety" } 
  };
  for (int i = 0; i < _countof(iid); i++) {
  void *pv = NULL;
  HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, iid[i].iid, (void **)&pv);
  if (hr == E_NOINTERFACE)
   wcout << L"\tDoes not support " << iid[i].pwszIid << endl;
  else if (FAILED(hr))
   wcout << L"\tError creating interface " << iid[i].pwszIid << endl;
  else
   wcout << L"\tSupports " << iid[i].pwszIid << endl;
  if (pv) ((IUnknown*)pv)->Release();
 }
  CoUninitialize();
  return 0;
}

If the output of this shows that IDispatch and IObjectSafety are supported, then you need to hand review all the methods and properties of the object to determine if they really are safe to call from untrusted script.

As a side note, no-one should create new ActiveX controls. All new code should be written using modern and safer technologies. It is 2016, after all :)

Finally, if you decide that the control should no longer be used and/or you have a viable replacement, you should consider Kill Bit'ing the control.

Big thanks to Dave Ross (@randomdross) for his review and comments.