class ThreatConnectPlugin(Plugin): @property def enabled(self): return settings.THREATCONNECT_ACCESS_ID is not None and \ settings.THREATCONNECT_SECRET_KEY is not None def activate(self): logger.debug('Activating %s' % self.__class__.__name__) self.service = ThreatConnect(settings.THREATCONNECT_ACCESS_ID, settings.THREATCONNECT_SECRET_KEY, settings.THREATCONNECT_DEFAULT_ORG, settings.THREATCONNECT_BASE_URL) self.service.set_api_result_limit(100) def retrieve_indicator(self, indicator, indicator_type=None): indicators = self.service.indicators() if indicator_type: f = indicators.add_filter(indicator_type) else: f = indicators.add_filter() f.add_owner(['Common Community']) f.add_indicator(indicator) results = sorted(indicators.retrieve(), key=lambda x: x.rating) return results[-1] if results else None def threshold_met(self, report): return hasattr(report, 'rating') and report.rating >= 1 and \ hasattr(report, 'confidence') and report.confidence >= 50 def react(self): if not any(self.reports.values()): return rating = max([0] + [ r.rating for r in self.reports.values() if r and hasattr(r, 'rating') and r.rating ]) return self.reaction(rating) def reaction(self, score): if score == 0: return 'sunny' elif 0 <= score < 0.5: return 'mostly_sunny' elif 0.5 <= score < 1: return 'partly_sunny' elif 1 <= score < 2: return 'barely_sunny' elif 2 <= score < 2.5: return 'cloud' elif 2.5 <= score < 3: return 'rain_cloud' elif 3 <= score < 3.5: return 'thunder_cloud_and_rain' elif 3.5 <= score < 4: return 'lightning' elif score >= 4: return 'tornado' def risk_level(self, score): try: score = float(score) except TypeError: return 'UNKNOWN' if score == 0: return 'UNKNOWN' elif 0 < score < 1: return 'VERY LOW' elif 1 <= score < 2: return 'LOW' elif 2 <= score < 3: return 'MODERATE' elif 3 <= score < 4: return 'HIGH' elif score >= 4: return 'VERY HIGH'
class CbThreatConnectConnector(object): def __init__(self, access_id="", secret_key="", default_org="", base_url="", out_file="tc.json", sources="", ioc_types="", custom_ioc_key="", feed_url="", cb_server_token="", cb_server_url="https://127.0.0.1", cb_server_ssl_verify=False, ioc_min=None, niceness=None, debug=False, log_file=None, max_iocs=5000): logger.info("ThreatConnect Base URL: {0}".format(base_url)) self.tcapi = ThreatConnect(api_aid=access_id, api_sec=secret_key, api_url=base_url, api_org=default_org) self.sources = sources self.ioc_min = ioc_min self.ioc_types = ioc_types logger.info("Configured IOC Types are : {0}".format(self.ioc_types)) logger.info("Configured IOC Min is : {0}".format(self.ioc_min)) self.custom_ioc_key = custom_ioc_key self.max_iocs = max_iocs if self.sources[0] == "*": owners = self.tcapi.owners() try: # retrieve the Owners owners.retrieve() except RuntimeError as e: logger.error(traceback.format_exc()) sys.exit(1) # iterate through the Owners self.sources = [owner.name for owner in owners] logger.info("Sources = {0}".format(self.sources)) self.niceness = niceness if self.niceness is not None: os.nice(self.niceness) self.debug = debug if self.debug: logger.setLevel(logging.DEBUG) self.log_file = log_file self.out_file = out_file self.feed = None self.cb = CbResponseAPI(url=cb_server_url, token=cb_server_token, ssl_verify=cb_server_ssl_verify) self.feed_url = feed_url def stop(self): self.stopEvent.set() def getDebugMode(self): return self._debug def setDebugMode(self, debugOn): self._debug = debugOn if self._debug == True: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) debug = property(getDebugMode, setDebugMode) def _PollThreatConnect(self): self.generate_feed_from_threatconnect() self.createFeed() last = None while (True): if self.stopEvent.isSet(): logger.info( "Threatconnect Connector was signalled to stop...stopping") return else: #poll threat connect if the time delta has passed since the last time we did now = datetime.now() delta = now - last if last is not None else self.interval last = now if delta >= self.interval: self.generate_feed_from_threatconnect() else: time.sleep(self.interval.seconds + 1) logger.debug("Done sleeping...") def RunForever(self): logger.info("ThreatConnect agent starting...") threading.Thread(target=self._PollThreatConnect).start() def createFeed(self): if self.feed is not None: self.feed.upload(self.cb, self.feed_url) def generate_feed_from_threatconnect(self): first = True reports = [] feedinfo = { 'name': 'threatconnect', 'display_name': "ThreatConnect", 'provider_url': "http://www.threatconnect.com", 'summary': "Sends threat intelligence from Threatconnect platform to Carbon Black Response", 'tech_data': "There are no requirements to share any data with Carbon Black to use this feed.", 'icon': 'threatconnect-logo.png', 'category': "Connectors", } feedinfo = CbFeedInfo(**feedinfo) self.feed = CbFeed(feedinfo, reports) created_feed = self.feed.dump(validate=False, indent=0) with open(self.out_file, 'w') as fp: fp.write(created_feed) fp.seek(0) offset = len(created_feed) - 1 # create an Indicators object for source in self.sources: for t in self.ioc_types: indicators = self.tcapi.indicators() filter1 = indicators.add_filter() # filter1.add_owner(source) filter1.add_pf_type(t, FilterOperator.EQ) if self.ioc_min is not None: filter1.add_pf_rating(self.ioc_min, FilterOperator.GE) try: # retrieve Indicators indicators.retrieve() except RuntimeError as e: print('Error: {0}'.format(e)) logger.info("Number of indicators:{0}".format( len(indicators))) for index, indicator in enumerate(indicators): if index > self.max_iocs: logger.info("Max number of IOCs reached") break # print (indicator.type) score = indicator.rating * 20 if indicator.rating is not None else 0 # int(row.get('rating', 0)) * 20 # Many entries are missing a description so I placed this here to default them # to the IOC value in the absence of a description. title = indicator.description if indicator.description is not None else "{0}-{1}".format( source, indicator.id) # row.get('description', None) # if not title: # title = row.get('summary') fields = { 'iocs': {}, 'id': str(indicator.id), 'link': indicator.weblink, 'title': title, 'score': int(score), 'timestamp': int( datetime.strptime( indicator.date_added, "%Y-%m-%dT%H:%M:%SZ").timestamp()), } # The next few lines are designed to insert the Cb supported IOCs into the record. logger.debug("Indacator is {0}".format(indicator)) if indicator.type == "File": fields['iocs'] = { k: [indicator.indicator[k]] for k in indicator.indicator if indicator.indicator[k] is not None } elif indicator.type == "Address": fields['iocs']['ipv4'] = [indicator.indicator] elif indicator.type == "Host": fields['iocs']['dns'] = [indicator.indicator] else: squery = urllib.parse.urlencode({ "cb.urlver": "1", "q": indicator.indicator[self.custom_ioc_key] }) fields['iocs']['query'] = [{ 'index_type': 'modules', 'search_query': squery }] report = CbReport(**fields) try: report.dump(validate=True) except: logger.info("This query is not valid: {0}".format( indicator.indicator[self.custom_ioc_key])) continue # APPEND EACH NEW REPORT ONTO THE LIST IN THE JSON FEED # THIS METHOD IS VERY LONG LIVED # THIS METHOD CALL WILL LAST FOR # HOURS -> DAYS IN LARGE ORGS reports.append(report) self.feed = CbFeed(feedinfo, reports) fp.write(self.feed.dump(validate=False, indent=0))
class ThreatConnectPlugin(Plugin): @property def enabled(self): return settings.THREATCONNECT_ACCESS_ID is not None and \ settings.THREATCONNECT_SECRET_KEY is not None def activate(self): logger.debug('Activating %s' % self.__class__.__name__) self.service = ThreatConnect( settings.THREATCONNECT_ACCESS_ID, settings.THREATCONNECT_SECRET_KEY, settings.THREATCONNECT_DEFAULT_ORG, settings.THREATCONNECT_BASE_URL) self.service.set_api_result_limit(100) def retrieve_indicator(self, indicator, indicator_type=None): indicators = self.service.indicators() if indicator_type: f = indicators.add_filter(indicator_type) else: f = indicators.add_filter() f.add_owner(['Common Community']) f.add_indicator(indicator) results = sorted(indicators.retrieve(), key=lambda x: x.rating) return results[-1] if results else None def threshold_met(self, report): return hasattr(report, 'rating') and report.rating >= 1 and \ hasattr(report, 'confidence') and report.confidence >= 50 def react(self): if not any(self.reports.values()): return rating = max([0] + [r.rating for r in self.reports.values() if r and hasattr(r, 'rating') and r.rating]) return self.reaction(rating) def reaction(self, score): if score == 0: return 'sunny' elif 0 <= score < 0.5: return 'mostly_sunny' elif 0.5 <= score < 1: return 'partly_sunny' elif 1 <= score < 2: return 'barely_sunny' elif 2 <= score < 2.5: return 'cloud' elif 2.5 <= score < 3: return 'rain_cloud' elif 3 <= score < 3.5: return 'thunder_cloud_and_rain' elif 3.5 <= score < 4: return 'lightning' elif score >= 4: return 'tornado' def risk_level(self, score): try: score = float(score) except TypeError: return 'UNKNOWN' if score == 0: return 'UNKNOWN' elif 0 < score < 1: return 'VERY LOW' elif 1 <= score < 2: return 'LOW' elif 2 <= score < 3: return 'MODERATE' elif 3 <= score < 4: return 'HIGH' elif score >= 4: return 'VERY HIGH'