The VML vulnerability continues to haunt us. According to SANS the exploit is "spreading," although SANS is not giving any details on what the relative spread is, or what intelligence it is basing that claim on. There is also now an unofficial patch from a group calling themselves the "Zeroday Emergency Response Team". The patch is truly just that: it creates a copy of the vulnerable DLL and performs a binary patch on it. It then unregisters the vulnerable original, and registers the patched version. Not having verified the actual machine code that constitutes the patched code I cannot say how exactly that works. Suffice to say that I would be extremely wary about deploying an unofficial patch. In the end, it is a risk-management decision though. The same considerations I discussed in regards to the WMF vulnerability in January applies here too. If you have an absolute need to view VML pages between now and October 10, when the official security update is currently scheduled to come out, and you feel that you cannot live with the risk of getting attacked, then the unofficial patch may be your only option. Personally, I worry about putting unverified and untrusted binaries on my system, and about the likelihood that they are going to be any higher quality than the ones Microsoft released. Consequently, I will not use the unofficial patch, nor can I think of anyone I would recommend it to.
This leads us to look for another option for protecting against the attack. On Tuesday I showed one method. That method works by setting an access control list (ACL) on the associated DLL to prevent read-access to it. This works, but can cause problems with Windows 2000. On Windows 2000 the Automatic Updates mechanism will attempt to open the DLL and get an access denied error. This error will cause it to try to replace the file with the most recent version, which is the one announced in Microsoft Security Bulletin MS04-028. However, that replacement will fail as well, causing the cycle to repeat all over again.
There are several other techniques to resolve the problem though, and perhaps the most robust is to unregister the vulnerable DLL entirely. This is done using the regsvr32.exe tool, but requires us to run commands on all the systems we want to protect. If we have an Enterprise Management System (EMS) this should be a snap, but if we do not, we need to develop a way to achieve the same result. A natural method to run commands are either logon scripts or startup scripts. Logon scripts are batch files that run when a user logs on to the system. A startup script is essentially like a logon script, but rather than runnig in the user's security context when the user logs on it runs in the context of the LocalSystem account when the machine boots. This makes it a much better option than a logon script for what we are trying to achieve because all the work-arounds for the problem represent administrative actions that users running with least privilege, as all users should, cannot take.
If we are going to use a startup script to protect against the VML vulnerability (CVE-2006-4868) we may as well make it block the DirectAnimation Path issue (I will refer to it as the "DAXCTL vulnerability" from now on; officially it is CVE-2006-4777) that came out last week while we are at it. This vulnerability is similar in scope to the VML vulnerability, but has a different mitigation strategy. The DAXCTL vulnerability is mitigated using a kill bit instead of by unregistering the DLL. The kill bit is a flag on an ActiveX control that causes IE to refuse to invoke the control. That requires modifying the registry on the system, which you can do several ways, including with a custom group policy template. However, if you are using a startup script already it is easy to add this functionality to the script.
The VML mitigation is simple. All we do is run the command Microsoft gives in the advisory:
regsvr32 -u "%ProgramFiles%\Common Files\Microsoft Shared\VGX\vgx.dll"
We will modify this just slightly. by replacing "%ProgramFiles%\Common Files" with "%CommonProgramFiles%" we can make the script work on non-English systems as well. We are also going to add the -s switch to run the command silently.
To mitigate the DAXCTL vulnerability we need a registry script that sets the kill bit. We can then call that script using the regedit.exe command to import it into the registry. However, that means we need to put it somewhere where the clients can read it while starting up. This does turn out to be a bit tricky. The current working directory while processing startup scripts is %systemroot%, and the assumption is that we cannot deploy files to every host, which means we cannot rely on the registry script being in the current working directory.
The natural place to put the registry script is in the sysvol folder on the domain controllers, but that means we need an absolute path to that folder. The first thing we need for that is the name of the domain. While there is an environment variable called %USERDNSDOMAIN% that we could use to obtain this value, the environment block where it is specified is not available when startup scripts run. In addition, if we put the script in the Group Policy Object (GPO) directory we would also have to modify the script to include the GPO GUID, which is unique for each script. One way to resolve all this is to use the %PATH% variable is available to us and contains this value:
\\<dns domain name>\sysvol\<dns domain name>\policies\<GPO GUID>\machine
I am a bit distrustful of relative paths though specified in environment variables though, especially as I do not get instant feedback on whether they worked or not, so I do not like this idea. Alternatively, we can specify the full path to the registry script in our startup script. but that means that we have to modify the script each time we move it to another domain or each time we specify it in a different GPO.
The approach I like best is to just write the script on the fly. Since it is such a simple script this is easy. That way our startup script is totally portable regardless of the name of the domain we put it on.
Putting it all together we end up with a script that looks like this:
=======START OF SCRIPT=========
@echo off
REM Print some debugging info
date /t >> %systemdrive%\ProtectIE.out
time /t >> %systemdrive%\ProtectIE.out
REM Start writing the header to our registry script
echo Windows Registry Editor Version 5.00 > DAXCTL.reg
echo. >> DAXCTL.reg
echo [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\ActiveX Compatibility\{D7A7D7C3-D47F-11D0-89D3-00A0C90833E6}] >> DAXCTL.reg
if "%1" == "" (
echo ERROR: You must specify either "enable" or "disable" on the command line for this script to have any effect >> %systemdrive%\ProtectIE.out
goto cleanup
)
if /i disable == %1 (
echo Disabling VML and DAX >> %systemdrive%\ProtectIE.out
REM In case the ACL-workaround has been applied, remove the Everyone ACE
cacls "%CommonProgramFiles%\Microsoft Shared\VGX\vgx.dll" /e /r Everyone >> %systemdrive%\ProtectIE.out
REM This unregisters the vgx control that enables VML
regsvr32.exe /s /u "%CommonProgramFiles%\Microsoft Shared\VGX\vgx.dll" >> %systemdrive%\ProtectIE.out
REM Finish creating our reg file. This one sets the kill bit
echo "Compatibility Flags"=dword:00000400 >> DAXCTL.reg
REM Call the reg file
regedit /s DAXCTL.reg >> %systemdrive%\ProtectIE.out
goto cleanup
) else (
if /i enable == %1 (
echo Enabling VML and DAX >> %systemdrive%\ProtectIE.out
REM In case the ACL-workaround has been applied, remove the Everyone ACE
cacls "%CommonProgramFiles%\Microsoft Shared\VGX\vgx.dll" /e /r Everyone >> %systemdrive%\ProtectIE.out
REM This registers the vgx control that enables VML
regsvr32.exe /s "%CommonProgramFiles%\Microsoft Shared\VGX\vgx.dll" >> %systemdrive%\ProtectIE.out
REM Finish creating our reg file. This one sets the kill bit
echo "Compatibility Flags"=dword:00000000 >> DAXCTL.reg
REM Call the reg file
regedit /s DAXCTL.reg >> %systemdrive%\ProtectIE.out
goto cleanup
) else (
echo ERROR: You must specify either "enable" or "disable" on the command line for this script to have any effect >> %systemdrive%\ProtectIE.out
goto cleanup
)
)
:cleanup
REM Delete the registry script
DEL DAXCTL.reg
echo Finished processing >> %systemdrive%\ProtectIE.out
echo. >> %systemdrive%\ProtectIE.out
echo ======================================= >> %systemdrive%\ProtectIE.out
=======END OF SCRIPT=========
The script itself should be pretty self-explanatory at this point. I added some debugging information to it that seemed interesting. You can remove that (most of the "echo" and >> commands) if you do not want it.
One thing that is worth explaining though is that this same script can be used both to mitigate the problems by disabling the related functionality and to turn the functionality back on again once you have deployed the security updates. The script takes a command line argument of either "enable" or "disable" that tells it which of the actions to take. "Disable" disables the vulnerable functionality and turns on the protection, and "enable" enables the functionality and therefore turns off the protection. Note also that you can test the script by running it interactively, although you get no output other than the debugging file if you do so. Of course, you can also call the script from an EMS if you have one of those.
To create a GPO that runs this script, take the following steps:
Copy the script above (everything between the begin and end tags) and paste it into a new text document. Save the document as "ProtectIE.cmd". Alternatively, just download and expand the ProtectIE_vX.zip file attached to this post.
Copy the ProtectIE.cmd file to \\<your domain>\sysvol\<your domain>\scripts. where you replace "<your domain>" with the full DNS name of your domain.
- Open the GPMC (if you do not have the Group Policy Management Console, you need to get it. Strictly speaking you can manage GPOs without it, but you really don't want to)
Right-click the domain or OU where you want to link the GPO - you may as well do it at the domain level - and select "Create and Link a GPO Here..." Name your new GPO "ProtectIE"
Right-click the GPO ProtectIE and select "Edit..."
Expand "Computer Configuration:Windows Settings" and click on "Scripts (Startup/Shutdown).
- Double-click "Startup" in the right-hand pane
- Click "Add..."
- Browse to \\<your domain>\sysvol\<your domain>\scripts and select "ProtectIE.cmd". Click "Open"
- In the "Script Parameters:" box type "Disable" without the quotes. Click "OK."
- Click "OK" again.
Close the GPO editor and go back to the GPMC
In the "Security Filtering" pane remove "Authenticated Users" and click Add...
In the text box called "Enter the object name..." type "Domain Computers" or some other relevant group that you want to apply the policy to. Click OK.
Now you need to reboot all the computers before they get the policy applied to them. One of the great comments I got on Tuesday's post was a pointer from Joe to http://www.specopssoft.com/products/specopsgpupdate/default.asp. Special Operations Software has a tool that enables you to propagate group policy to all the systems on your domain, as well as remotely trigger a restart if you want. That presents its own challenges though. First, if you restart the whole domain at the same time it is likely that your domain controllers (DC) are not ready to serve group policy when your clients are ready to receive it, not to mention how loudly the DCs will complain about every client trying to authenticate at the same time. Used judiciously this could be a valuable tool though. I also would not be overly concerned with servers. These vulnerabilities are primarily a problem when people surf untrusted web sites. If people are using your servers (not including Terminal Servers, naturally) to surf untrusted web sites you have a lot of other problems.
The security updates to fix these issues are currently scheduled for October 10. Once you have those applied you can go back to these instructions and change the parameter "Disable" in step 10 to "Enable" to turn all the functionality back on again.