#!/usr/bin/env python ''' Name: Microsoft Server Service Remote Path Canonicalization Stack Overflow Vulnerability Description: Anonymously check if a target machine is affected by MS08-067 (Vulnerability in Server Service Could Allow Remote Code Execution) Author: Bernardo Damele A. G. <bernardo.damele@gmail.com> License: Modified Apache 1.1 Version: 0.6 References: * BID: 31874 * CVE: 2008-4250 * MSB: MS08-067 * VENDOR: http://blogs.technet.com/swi/archive/2008/10/25/most-common-questions-that-we-ve-been-asked-regarding-ms08-067.aspx * VENDOR: http://www.microsoft.com/technet/security/advisory/958963.mspx * MISC: http://www.phreedom.org/blog/2008/decompiling-ms08-067/ * MISC: http://metasploit.com/dev/trac/browser/framework3/trunk/modules/exploits/windows/smb/ms08_067_netapi.rb * MISC: http://blog.threatexpert.com/2008/10/gimmiva-exploits-zero-day-vulnerability.html * MISC: http://blogs.securiteam.com/index.php/archives/1150 Tested: * Windows 2000 Server Service Pack 0 * Windows 2000 Server Service Pack 4 with Update Rollup 1 * Microsoft 2003 Standard Service Pack 1 * Microsoft 2003 Standard Service Pack 2 Full Patched at 22nd of October 2008, before MS08-067 patch was released Notes: * On Windows XP SP2 and SP3 this check might lead to a race condition and heap corruption in the svchost.exe process, but it may not crash the service immediately: it can trigger later on inside any of the shared services in the process. ''' import socket import sys from optparse import OptionError from optparse import OptionParser from random import choice from string import letters from struct import pack from threading import Thread from traceback import format_exc try: from impacket import smb from impacket import uuid from impacket.dcerpc.v5 import dcerpc from impacket.dcerpc.v5 import transport except ImportError, _: print 'ERROR: this tool requires python-impacket library to be installed, get it ' print 'from http://oss.coresecurity.com/projects/impacket.html or apt-get install python-impacket' sys.exit(1) try: from ndr import * except ImportError, _: print 'ERROR: this tool requires python-pymsrpc library to be installed, get it ' print 'from http://code.google.com/p/pymsrpc/' sys.exit(1) CMDLINE = False SILENT = False class connectionException(Exception): pass class MS08_067(Thread): def __init__(self, target, port=445): super(MS08_067, self).__init__() self.__port = port self.target = target self.status = 'unknown' def __checkPort(self): ''' Open connection to TCP port to check if it is open ''' try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(1) s.connect((self.target, self.__port)) s.close() except socket.timeout, _: raise connectionException, 'connection timeout' except socket.error, _: raise connectionException, 'connection refused' def __connect(self): ''' SMB connect to the Computer Browser service named pipe Reference: http://www.hsc.fr/ressources/articles/win_net_srv/msrpc_browser.html ''' try: self.__trans = transport.DCERPCTransportFactory('ncacn_np:%s[\\pipe\\browser]' % self.target) self.__trans.connect() except smb.SessionError, _: raise connectionException, 'access denied (RestrictAnonymous is probably set to 2)' except: #raise Exception, 'unhandled exception (%s)' % format_exc() raise connectionException, 'unexpected exception' def __bind(self): ''' DCERPC bind to SRVSVC (Server Service) endpoint Reference: http://www.hsc.fr/ressources/articles/win_net_srv/msrpc_srvsvc.html ''' try: self.__dce = self.__trans.DCERPC_class(self.__trans) self.__dce.bind(uuid.uuidtup_to_bin(('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0'))) except socket.error, _: raise connectionException, 'unable to bind to SRVSVC endpoint' except: #raise Exception, 'unhandled exception (%s)' % format_exc() raise connectionException, 'unexpected exception' def __forgePacket(self): ''' Forge the malicious NetprPathCompare packet Reference: http://msdn.microsoft.com/en-us/library/cc247259.aspx long NetprPathCompare( [in, string, unique] SRVSVC_HANDLE ServerName, [in, string] WCHAR* PathName1, [in, string] WCHAR* PathName2, [in] DWORD PathType, [in] DWORD Flags ); ''' self.__path = ''.join([choice(letters) for _ in xrange(0, 3)]) self.__request = ndr_unique(pointer_value=0x00020000, data=ndr_wstring(data='')).serialize() self.__request += ndr_wstring(data='\\%s\\..\\%s' % ('A'*5, self.__path)).serialize() self.__request += ndr_wstring(data='\\%s' % self.__path).serialize() self.__request += ndr_long(data=1).serialize() self.__request += ndr_long(data=0).serialize() def __compare(self): ''' Compare NetprPathCompare response field 'Windows Error' with the expected value (WERR_OK) to confirm the target is vulnerable ''' self.__vulnerable = pack('<L', 0) # The target is vulnerable if the NetprPathCompare response field # 'Windows Error' is WERR_OK (0x00000000) if self.__response == self.__vulnerable: self.status = 'VULNERABLE' else: self.status = 'not vulnerable' self.result() def result(self): if CMDLINE == True and self.status in ('VULNERABLE', 'not vulnerable'): print '%s: %s' % (self.target, self.status) elif CMDLINE == True and SILENT != True: print '%s: %s' % (self.target, self.status) def run(self): try: self.__checkPort() self.__connect() self.__bind() except connectionException, e: self.status = e self.result() return None # Forge and send the NetprPathCompare operation malicious packet self.__forgePacket() self.__dce.call(32, self.__request) # Get back the NetprPathCompare response and check if it is vulnerable self.__response = self.__dce.recv() self.__compare() if __name__ == '__main__': CMDLINE = True usage = '%s [option] {-t <target>|-l <iplist.txt>}' % sys.argv[0] parser = OptionParser(usage=usage, version='0.4') targets = set() # Create command line options try: parser.add_option('-d', dest='descr', action='store_true', help='show description and exit') parser.add_option('-t', dest='target', help='target IP or hostname') parser.add_option('-l', dest='list', help='text file with list of targets') parser.add_option('-s', dest='silent', action='store_true', help='be silent') (args, _) = parser.parse_args() if not args.descr and not args.target and not args.list: print usage sys.exit(1) except (OptionError, TypeError), e: parser.error(e) descr = args.descr target = args.target tList = args.list SILENT = args.silent if descr: print __doc__ sys.exit(0) if tList: try: fd = open(tList, 'r') except IOError: print 'ERROR: unable to read targets list file \'%s\'' % tList sys.exit(1) for line in fd.readlines(): target = line.replace('\n', '').replace('\r', '') targets.add(target) else: targets.add(target) if not targets: print 'ERROR: no targets specified' sys.exit(1) targets = list(targets) targets.sort() if not SILENT: print print '***********************************************************************' print '* On Windows XP SP2 and SP3 this check might lead to a race condition *' print '* and heap corruption in the svchost.exe process, but it may not *' print '* crash the service immediately, it can trigger later on inside any *' print '* of the shared services in the process. *' print '***********************************************************************' print answer = raw_input('Do you want to continue? [Y/n] ') if answer and answer[0].lower() != 'y': sys.exit(1) for target in targets: current = MS08_067(target) current.start()