/* This mitigation should help block attempts to exploit the IIS semicolon zero-day (BID 37460), but no warranties and no guarantees. It didn't crash my web servers during testing, but I make no representations as to how it will or won't perform on anyone else's web servers. This mitigation is only intended for IIS 4.0, 5.x, and 6.0; IIS 7.0 does not appear to be vulnerable (although in my testing, IIS 4.0, 5.0, and 5.1 didn't appear to be vulnerable, either), and 7.5 is also reportedly not vulnerable. If an attack is blocked, it should show up in the IIS logs with HTTP status 403 and the made-up substatus 59; for example, on IIS 6.0: 2009-12-27 12:34:56 W3SVC1 169.254.123.200 GET /evil.asp;x.jpg - 80 - 169.254.123.100 Mozilla/4.0+(compatible;+MSIE+...) 403 59 0 When passed a malformed file name by IIS due to the vulnerability, most (non-.NET) ISAPI extensions will process the file, including: asp.dll / asp51.dll: .asa, .asp, .cdx, .cer, .htr httpodbc.dll: .idc ssinc.dll: .shtm, .shtml, .stm So to get the most protection, you'll need to remap the file extensions for each different ISAPI extension to a different copy of the mitigation DLL -- it uses the file name with which it loads to figure out which ISAPI extension it's wrapping. You'll also need to set up the registry to tell it where to find the ISAPI extension it's replacing. For example: [HKEY_LOCAL_MACHINE\SOFTWARE\iissemi1] "iissemi1_asp.dll" = "C:\WINDOWS\system32\inetsrv\asp.dll" "iissemi1_ssinc.dll" = "C:\WINDOWS\system32\inetsrv\ssinc.dll" Try out a malformed file name after deploying to make sure you have everything set up properly. Sorry I can't host an installer right now to automate all of this. Hopefully most people won't need to use this mitigation, but if the other options for reducing exposure aren't viable in your situation, I hope this one will be. -- Derek P.S. The vulnerability can also be exploited if a subdirectory containing ".xxx;" can be created; for instance, accessing "/~y.asp;y/z.jpg" would cause "z.jpg" to be executed by the ASP ISAPI extension as though it were an ASP script. */ //////////////////////////////////////////////////////////////// // iissemi1.cpp //============================================================== // Mitigation to prevent processing of file names that contain a // semicolon or aberrant colon, to guard against the IIS // semicolon zero-day. // // All file extensions that would normally execute an ISAPI // extension must be manually remapped to a copy of the // mitigation DLL. The mitigation works by passing requests to // the original ISAPI extension unless they contain a semicolon // or improper colon. It is hard-coded to track down asp.dll // and a few other DLLs, if the mitigation DLL itself loads with // a file name that indicates which ISAPI extension it's // replacing; to override this default behavior, or to support // an unrecognized ISAPI extension, add a registry value // matching the mitigation DLL's file name in the // "HKEY_LOCAL_MACHINE\SOFTWARE\iissemi1" registry key. // // To build: // // 1. Start Visual Studio 2008 (2005 should also work) // 2. File -> New -> Project // 3. Choose Visual C++: Win32: Win32 Project // 4. Enter "iissemi1" for the name // 5. In the Win32 Application Wizard, choose an // "Application type" of "DLL", and under "Additional // options", check "Empty project" // 6. In the Solution Explorer, right-click on "Source Files", // Add -> New Item // 7. Choose "C++ File (.cpp)" and enter "iissemi1.cpp" for // the name // 8. Paste all of this source code into the new .cpp file // 9. In the Solution Explorer, right-click again on "Source // Files", Add -> New Item // 10. Choose "Module-Definition File (.def)" and enter // "iissemi1.def" for the name // 11. Paste everything in the block comment below (between the // rows of ****'s) into the new .def file // 12. Build -> Configuration Manager; for "Active solution // configuration", choose "Release" // 13. For maximum portability, Project -> Properties, // Configuration Properties: C/C++: Code Generation: set // "Runtime Library" to "Multi-threaded (/MT)"; this will // keep iissemi1.dll from requiring MSVCR*.DLL // 14. (While you're in there, Project -> Properties, // Configuration Properties: Linker: Input, and make sure // that "Module Definition File" contains "iissemi1.def") // 15. Build -> Build Solution // // To use: // // 16. Open Internet Information Services Manager MMC snap-in // (run "inetmgr") // 17. Right-click on "Web Sites" or machine node, Properties // (Master Properties: Edit, as applicable) // 18. Home Directory tab, Configuration // 19. Copy iissemi1.dll to a different file name for each // distinct ISAPI extension you need to replace; e.g., // "iissemi1_asp.dll" if you need to replace "asp.dll", // "iissemi1_httpodbc.dll" for "httpodbc.dll", etc. // 20. Remap the file extensions for every ISAPI extension which // you wouldn't want users to be able to supply with // arbitrary files, to instead use a copy of iissemi1.dll. // Make sure to keep a record of each mapping changed, so // that you can easily reverse the changes later. // (Note that you might get "requested resource is in use" // errors if you don't remap every file extension belonging // to an ISAPI extension -- e.g., if you remap ".asp" but // not ".asa", you might get HTTP 500 errors when you // access an ASA file.) // 21. IIS 6.0: In the IIS Manager, go to the "Web Service // Extensions" folder, Action -> Add a new web service // extension, enter "iissemi1" for the extension name, add // each file created in Step 19 to the "Required files" // list, check "Set extension status to Allowed", OK // 22. In Regedit, create the registry key // "HKEY_LOCAL_MACHINE\SOFTWARE\iissemi1" // 23. In this new key, create string (REG_SZ or REG_EXPAND_SZ) // registry values named after the copies of iissemi1.dll // created in Step 19. The data of each registry value // should be the full path and file name of the ISAPI // extension that was replaced; e.g., set the value named // "iissemi1_asp.dll" to // "C:\WINDOWS\system32\inetsrv\asp.dll", set the value named // "iissemi1_httpodbc.dll" to // "C:\WINDOWS\system32\inetsrv\httpodbc.dll", etc. // 24. Restart W3SVC (run "iisreset") // // To uninstall, remap the file extension mappings changed in // Step 20 back to their original executable paths. // // NO WARRANTIES. Use at your own risk. Redistribution of this // source code in its original, unmodified form is permitted. // // Copyright (C) Derek Soeder - 12/27/2009 //////////////////////////////////////////////////////////////// /**** Paste the following into a new .def file: ************* LIBRARY "iissemi1.dll" EXPORTS AspStatusHtmlDump DllCanUnloadNow PRIVATE DllGetClassObject PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE GetExtensionVersion HttpExtensionProc TerminateExtension ***************************************************************/ #define WIN32_LEAN_AND_MEAN #define _CRT_SECURE_NO_WARNINGS #include #include #include #include //////////////////////////////////////////////////////////////// // Original ISAPI wrapper //////////////////////////////////////////////////////////////// WCHAR g_wszMyself[1024]; CRITICAL_SECTION g_csWrappers; HMODULE g_hmISAPI; WCHAR g_wszISAPI[1024]; typedef BOOL (WINAPI * PFNASPSTATUSHTMLDUMP)( PVOID, PVOID ); typedef HRESULT (STDAPICALLTYPE * PFNDLLREGISTERSERVER)(); typedef HRESULT (STDAPICALLTYPE * PFNDLLUNREGISTERSERVER)(); PFNASPSTATUSHTMLDUMP g_pfnAspStatusHtmlDump; LPFNCANUNLOADNOW g_pfnDllCanUnloadNow; LPFNGETCLASSOBJECT g_pfnDllGetClassObject; PFNDLLREGISTERSERVER g_pfnDllRegisterServer; PFNDLLUNREGISTERSERVER g_pfnDllUnregisterServer; PFN_GETEXTENSIONVERSION g_pfnGetExtensionVersion; PFN_HTTPEXTENSIONPROC g_pfnHttpExtensionProc; PFN_TERMINATEEXTENSION g_pfnTerminateExtension; const struct { void * ppfn; const char * sz; } LOOKUP[] = { { &g_pfnAspStatusHtmlDump, "AspStatusHtmlDump" }, { &g_pfnDllCanUnloadNow, "DllCanUnloadNow" }, { &g_pfnDllGetClassObject, "DllGetClassObject" }, { &g_pfnDllRegisterServer, "DllRegisterServer" }, { &g_pfnDllUnregisterServer, "DllUnregisterServer" }, { &g_pfnGetExtensionVersion, "GetExtensionVersion" }, { &g_pfnHttpExtensionProc, "HttpExtensionProc" }, { &g_pfnTerminateExtension, "TerminateExtension" } } ; BOOL read_reg_path( LPCWSTR wszKeyName, LPCWSTR wszValueName ) { HKEY hkey; DWORD dwtype, cb, cwch; WCHAR wsz[ sizeof(g_wszISAPI) / sizeof(g_wszISAPI[0]) ]; if ( RegOpenKeyExW( HKEY_LOCAL_MACHINE, wszKeyName, 0, KEY_READ, &hkey ) != ERROR_SUCCESS ) { return FALSE; } cb = (sizeof(g_wszISAPI) - sizeof(g_wszISAPI[0])); if (RegQueryValueExW( hkey, wszValueName, NULL, &dwtype, (LPBYTE)g_wszISAPI, &cb ) != ERROR_SUCCESS || cb < sizeof(g_wszISAPI[0]) || cb > (sizeof(g_wszISAPI) - sizeof(g_wszISAPI[0])) ) { RegCloseKey( hkey ); return FALSE; } RegCloseKey( hkey ); g_wszISAPI[cb / sizeof(g_wszISAPI[0])] = L'\0'; if (dwtype == REG_EXPAND_SZ) { cwch = ExpandEnvironmentStringsW( g_wszISAPI, wsz, ((sizeof(wsz) / sizeof(wsz[0])) - 1) ); if ( cwch == 0 || cwch >= (sizeof(wsz) / sizeof(wsz[0])) - 1) { return FALSE; } wsz[cwch] = L'\0'; memcpy( g_wszISAPI, wsz, ((cwch + 1) * sizeof(g_wszISAPI[0])) ); } else if (dwtype != REG_SZ) return FALSE; return TRUE; } //read_reg_path const wchar_t * __wcsstri( const wchar_t * _Str, const wchar_t * _SubStr ) { size_t cwchstr, cwchsub; cwchstr = wcslen( _Str ); cwchsub = wcslen( _SubStr ); if (cwchstr < cwchsub) return 0; for (cwchstr -= cwchsub; ; cwchstr--, _Str++) { if (_wcsnicmp( _Str, _SubStr, cwchsub ) == 0) return _Str; if (cwchstr == 0) break; } return 0; } //__wcsstri BOOL try_defaults( LPCWSTR wszMyFileName ) { LPCWSTR wszdll; if (__wcsstri( wszMyFileName, L"asp51" )) { if ( read_reg_path( L"SOFTWARE\\Classes\\CLSID\\" L"{D97A6DA0-A861-11cf-93AE-00A0C90C2BD8}\\" L"InprocServer32", NULL ) ) { return TRUE; } wszdll = L"\\asp51.dll"; } else if (__wcsstri( wszMyFileName, L"asp" )) { if ( read_reg_path( L"SOFTWARE\\Classes\\CLSID\\" L"{D97A6DA0-A861-11cf-93AE-00A0C90C2BD8}\\" L"InprocServer32", NULL ) ) { return TRUE; } wszdll = L"\\asp.dll"; } else if (__wcsstri( wszMyFileName, L"httpodbc" )) wszdll = L"\\httpodbc.dll"; else if (__wcsstri( wszMyFileName, L"ssinc" )) wszdll = L"\\ssinc.dll"; else if (__wcsstri( wszMyFileName, L"urlauth" )) wszdll = L"\\urlauth.dll"; else if (__wcsstri( wszMyFileName, L"w3isapi" )) wszdll = L"\\w3isapi.dll"; else return FALSE; if ( read_reg_path( L"SOFTWARE\\Microsoft\\InetStp", L"InstallPath" ) && ( (sizeof(g_wszISAPI) / sizeof(g_wszISAPI[0])) - wcslen( g_wszISAPI ) ) > (wcslen( wszdll ) + 1) ) { wcscat( g_wszISAPI, wszdll ); return TRUE; } g_wszISAPI[0] = L'\0'; return FALSE; } //try_defaults BOOL load_isapi() { LPCWSTR wcp; size_t i; EnterCriticalSection( &g_csWrappers ); __try { if (g_hmISAPI != NULL) return TRUE; wcp = wcsrchr( g_wszMyself, L'\\' ); if (wcp) wcp++; else wcp = g_wszMyself; if (!read_reg_path( L"SOFTWARE\\iissemi1", wcp )) { if (!try_defaults( wcp )) return FALSE; } g_hmISAPI = LoadLibraryW( g_wszISAPI ); if (g_hmISAPI == NULL) return FALSE; for ( i = 0; i < (sizeof(LOOKUP) / sizeof(LOOKUP[0])); i++ ) { *(void * *)(LOOKUP[i].ppfn) = (void *)GetProcAddress( g_hmISAPI, LOOKUP[i].sz ); } } __finally { LeaveCriticalSection( &g_csWrappers ); } return TRUE; } //load_isapi //////////////////////////////////////////////////////////////// // ISAPI extension replacement exports //////////////////////////////////////////////////////////////// BOOL WINAPI AspStatusHtmlDump( PVOID pArg0, PVOID pArg1 ) { if (!load_isapi() || g_pfnAspStatusHtmlDump == NULL) return FALSE; return g_pfnAspStatusHtmlDump( pArg0, pArg1 ); } //AspStatusHtmlDump STDAPI DllCanUnloadNow() { if (!load_isapi() || g_pfnDllCanUnloadNow == NULL) return S_OK; return g_pfnDllCanUnloadNow(); } //DllCanUnloadNow STDAPI DllGetClassObject( REFCLSID rclsid, REFIID riid, LPVOID * ppv ) { if (!load_isapi() || g_pfnDllGetClassObject == NULL) return E_UNEXPECTED; return g_pfnDllGetClassObject( rclsid, riid, ppv ); } //DllGetClassObject STDAPI DllRegisterServer() { if (!load_isapi() || g_pfnDllRegisterServer == NULL) return E_UNEXPECTED; return g_pfnDllRegisterServer(); } //DllRegisterServer STDAPI DllUnregisterServer() { if (!load_isapi() || g_pfnDllUnregisterServer == NULL) return E_UNEXPECTED; return g_pfnDllUnregisterServer(); } //DllUnregisterServer BOOL WINAPI GetExtensionVersion( HSE_VERSION_INFO * pVer ) { if (!load_isapi() || g_pfnGetExtensionVersion == NULL) return FALSE; return g_pfnGetExtensionVersion( pVer ); } //GetExtensionVersion DWORD WINAPI HttpExtensionProc( EXTENSION_CONTROL_BLOCK * pECB ) { HSE_CUSTOM_ERROR_INFO hseerr; BOOL fdanger; const char * cp; if (!load_isapi() || g_pfnHttpExtensionProc == NULL) return HSE_STATUS_ERROR; if (pECB != NULL && pECB->lpszPathTranslated != NULL) { fdanger = FALSE; if (strchr( pECB->lpszPathTranslated, ';' )) fdanger = TRUE; #if 1 // also check for colon that doesn't belong cp = pECB->lpszPathTranslated; if ( cp[0] == '\\' && cp[1] == '\\' && (cp[2] == '.' || cp[2] == '?') && cp[3] == '\\' && cp[4] != '\0' && cp[5] == ':' ) { cp += 6; } else if ( cp[0] == '\\' && cp[1] == '?' && cp[2] == '?' && cp[3] == '\\' && cp[4] != '\0' && cp[5] == ':' ) { cp += 6; } else if (cp[0] != '\0' && cp[1] == ':') { cp += 2; } if (strchr( cp, ':' )) fdanger = TRUE; #endif if (fdanger) { if (pECB->ServerSupportFunction != NULL) { pECB->ServerSupportFunction( pECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "403 Forbidden", NULL, NULL ); hseerr.pszStatus = "403 Forbidden"; hseerr.uHttpSubError = 59; pECB->ServerSupportFunction( pECB->ConnID, HSE_REQ_SEND_CUSTOM_ERROR, &hseerr, NULL, NULL ); } //if(pECB->ServerSupportFunction) pECB->dwHttpStatusCode = 403; return HSE_STATUS_SUCCESS; } //if(fdanger) } //if(pECB&&pECB->lpszPathTranslated) return g_pfnHttpExtensionProc( pECB ); } //HttpExtensionProc BOOL WINAPI TerminateExtension( DWORD dwFlags ) { if (!load_isapi() || g_pfnTerminateExtension == NULL) return FALSE; return g_pfnTerminateExtension( dwFlags ); } //TerminateExtension //////////////////////////////////////////////////////////////// // DLL initialization //////////////////////////////////////////////////////////////// BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ) { DWORD cwch; if (fdwReason == DLL_PROCESS_ATTACH) { cwch = GetModuleFileNameW( hinstDLL, g_wszMyself, ( ( sizeof(g_wszMyself) / sizeof(g_wszMyself[0]) ) - 1 ) ); if ( cwch == 0 || cwch >= ( ( sizeof(g_wszMyself) / sizeof(g_wszMyself[0]) ) - 1 ) ) { return FALSE; } InitializeCriticalSection( &g_csWrappers ); g_hmISAPI = NULL; g_wszISAPI[0] = L'\0'; } else if (fdwReason == DLL_PROCESS_DETACH) { // do this in TerminateExtension? //if (g_hmISAPI != NULL) //{ // FreeLibrary( g_hmISAPI ); // g_hmISAPI = NULL; //} DeleteCriticalSection( &g_csWrappers ); } return TRUE; } //DllMain