Beispiel #1
0
    def _generate_hit_from_search_result(self, search_result):
        """
        Query urlscan io Result API for the given URL search result - hit.
        :param search_result: dictionary
        """
        result_id = search_result.get('_id', None)
        result_url = "{0}{1}".format(self.urlscan_io_result_api_url, result_id)
        result_response = requests.get(result_url, headers=self.HEADERS)

        if result_response.status_code == 200:
            result_content = result_response.json()

            stats = result_content.get('stats', None)
            if stats:
                malicious_flag = stats.get('malicious', None)

                if malicious_flag == 1:

                    # Some malicious scans show as failed, do not include those
                    if self._verify_for_scan_failed_flag(result_content):
                        return None

                    task = result_content.get('task', None)
                    page = result_content.get('page', None)

                    png_url = task.get('screenshotURL', None) if task else None
                    scan_time = task.get('time', None) if task else None
                    report_url = task.get('reportURL', None) if task else None
                    uniq_countries_int = stats.get('uniqCountries', None)
                    city_country_list = self._prepare_city_contry(
                        page.get('city', None), page.get(
                            'country', None)) if page else None
                    city_country = ",".join(
                        city_country_list) if city_country_list else None
                    server = page.get('server', None) if page else None
                    asn = page.get('asnname', None) if page else None

                    return Hit(
                        StringProp(name="Time Last Scanner", value=scan_time),
                        NumberProp(name="Number of Countries",
                                   value=uniq_countries_int),
                        StringProp(name="City and Country",
                                   value=city_country),
                        StringProp(name="Server", value=server),
                        StringProp(name="ASN Name", value=asn),
                        UriProp(name="Report Link", value=report_url),
                        UriProp(name="Screenshot Link", value=png_url))
        else:
            LOG.info(
                "No Result information found on URL: {0}".format(result_url))
            LOG.debug(result_response.text)
Beispiel #2
0
    def _lookup_net_uri(self, event, *args, **kwargs):
        """Return hits for URL artifacts"""
        hits = []

        # event.artifact is a ThreatServiceArtifactDTO
        artifact_type = event.artifact['type']
        artifact_value = event.artifact['value']

        # Return zero or more hits.  Here's one example.
        hits.append(
            Hit(NumberProp(name="id", value=123),
                StringProp(name="Type", value=artifact_type),
                UriProp(name="Link", value=artifact_value),
                IpProp(name="IP Address", value="127.0.0.1"),
                LatLngProp(name="Location", lat=42.366, lng=-71.081)))
        hits.append(
            Hit(
                NumberProp(name="id", value=456),
                StringProp(name="Type", value=artifact_type),
                UriProp(name="Link", value=artifact_value),
                IpProp(name="IP Address", value="0.0.0.0"),
            ))
        yield hits
    def lookup_artifact(self, event, *args, **kwargs):
        """
        Use YETI to search for artifact value

        """

        if not isinstance(event, ThreatServiceLookupEvent):
            return

        hits = []
        LOG.info("Querying YETI")

        artifact = event.artifact
        artifact_value = artifact['value']
        LOG.info(artifact)
        LOG.info("Looking up ({art_type}): {art_value}".format(
            art_type=artifact['type'], art_value=artifact_value))

        try:
            # init new indicators object
            indicators = self.yeti_client.observable_search(
                regex=False, value=artifact_value)
            LOG.debug(indicators)
        except ValueError as e:
            LOG.error(traceback.format_exc())
            raise e

        if not indicators or len(indicators) < 1:
            return hits

        tags = ""
        for tag in indicators[0]["tags"]:
            if tags != "":
                tags += ", "
            tags += tag["name"]

        description = indicators[0]["description"] if indicators[0][
            "description"] else "None"
        try:
            hits.append(
                Hit(StringProp(name="Type", value=indicators[0]["type"]),
                    StringProp(name="Value", value=indicators[0]["value"]),
                    StringProp(name="Tags", value=tags),
                    StringProp(name="Created", value=indicators[0]["created"]),
                    UriProp(name="URL", value=indicators[0]["human_url"]),
                    StringProp(name="Description", value=description)))
            return hits
        except Exception as e:
            LOG.error(traceback.format_exc())
            raise e
    def _query_hibp_api(self, artifact_value):
        hits = []
        retry = True
        while (retry):
            try:
                # Return zero or more hits.  Here's one example.
                url = "{0}/{1}".format(self.HAVE_I_BEEN_PWNED_URL,
                                       artifact_value)
                response = requests.get(
                    url, headers={'User-Agent': 'Resilient HIBP CTS'})

                if response.status_code == 200:
                    content = json.loads(response.text)
                    breaches = content["Breaches"]
                    if breaches is None:
                        breaches = []
                    pastes = content["Pastes"]
                    if pastes is None:
                        pastes = []

                    hits.append(
                        Hit(
                            NumberProp(name="Breached Sites",
                                       value=len(breaches)),
                            NumberProp(name="Pastes", value=len(pastes)),
                            UriProp(name="View data from Have I Been Pwned",
                                    value=url)))
                    retry = False

                # 404 is returned when an email was not found
                elif response.status_code == 404:
                    LOG.info("No hit information found on email address: {0}".
                             format(artifact_value))
                    retry = False
                elif response.status_code == 429:
                    # Rate limit was hit, wait 2 seconds and try again
                    time.sleep(2)
                else:
                    LOG.warn("Have I Been pwned returned expected status code")
                    retry = False
                    raise ThreatLookupIncompleteException()
            except BaseException as e:
                LOG.exception(e.message)
            return hits
Beispiel #5
0
    def _lookup_net_uri(self, event, *args, **kwargs):
        LOG.info("Looking up with Google Safe Browsing API")

        value = event.artifact['value']
        LOG.info("Looking up URL: " + str(value))

        sb = SafeBrowsingAPI(self.apikey)
        resp = sb.lookup_urls(value)
        hits = []
        for match in resp.get("matches", []):
            linkurl = match["threat"]["url"]
            link = LINK_URL.format(match["threat"]["url"])
            hits.append(Hit(
                StringProp(name="Threat Type", value=match["threatType"]),
                UriProp(name="Report Link", value=link),
                StringProp(name="Platform Type", value=match["platformType"]),
                StringProp(name="URL Name", value=linkurl)
            ))
        return hits
Beispiel #6
0
    def _lookup_artifact(self, event, *args, **kwargs):
        """Lookup an artifact"""

        # This is a generic handler - we only care about lookup events but might be sent others
        if not isinstance(event, ThreatServiceLookupEvent):
            return

        # event.artifact is a ThreatServiceArtifactDTO
        artifact_type = event.artifact['type']
        artifact_value = event.artifact['value']

        # Check that the event matches an artifact type that we want to search in MISP
        if artifact_type not in MISP_TYPES:
            # Nothing to do
            LOG.info(u"MISP lookup not implemented for %s", artifact_type)
            return

        # Doc says: MISP search for IP addresses using CIDR, uses '|' (pipe) instead of '/' (slashes) in the value.
        # But this appears not to be the case, we just search for the unchanged value.
        # if artifact_type == "net.cidr":
        #     artifact_value = str(artifact_value).replace('/', '|')

        LOG.info("MISP Lookup: " + str(artifact_value))

        misp_api = PyMISP(self.misp_url, self.misp_key, self.misp_verifycert,
                          'json')

        # MISP search_index: produces a list of events, and their key attributes, based on search criteria.
        # But does not filter by attribute type (only by the value that matched),
        # and the value matches include substrings, so we can't filter the results effectively.
        # matches = misp_api.search_index(published=1,
        #                                 tag=self.misp_tag,
        #                                 org=self.misp_org,
        #                                 attribute=str(artifact_value))

        # MISP search: produce events or attribute values, but only for a single type of attribute (or all types).
        # Attribute value matches partial strings (LIKE %value%), but we usually want exact-match searching.
        # Our search strategy is this:
        # - search for each type, returning only attributes;
        # - filter the attribute results for case-insensitive exact matches only,
        # - collect the list of events that the attributes belong to,
        # - finally retrieve the event metadata for our results.
        #
        # (This strategy will likely change with future versions of MISP!)

        misp_types = MISP_TYPES[artifact_type]
        search_value = str(artifact_value).lower()
        event_ids = set()

        def matches(an_attribute):
            """Does the attribute value match?  MISP does substring searches but we want exact."""
            if artifact_type == "net.cidr":
                # Match any CIDR, we assume the server did the logical search
                return True
            if an_attribute["value"].lower() == search_value:
                # Match the whole attribute value, lowercase.
                return True
            return False

        for misp_type in misp_types:
            # Search for this one attribute-type
            result = misp_api.search(controller='attributes',
                                     values=[search_value],
                                     type_attribute=misp_type,
                                     tags=self.misp_tag,
                                     org=self.misp_org,
                                     withAttachments=0)
            LOG.debug("Search for %s", misp_type)
            LOG.debug(json.dumps(result))
            if result:
                response = result.get("response", {})
                if isinstance(response, dict):
                    attributes = response.get("Attribute", [])
                    for attribute in attributes:
                        if matches(attribute):
                            event_ids.add(attribute["event_id"])

        hits = []
        if not event_ids:
            # Nothing to do
            return hits

        # Finally let's get summary for each event.
        # (Don't use the search api for this, it will match partial event ids).
        for event_id in event_ids:
            result = misp_api.get_event(event_id)
            if "Event" in result:
                event = result["Event"]
                event_id = event["id"]
                link = self.misp_link_url + "/events/view/" + str(event_id)
                info = event.get("info")
                datestr = event.get("date")
                hit = Hit(
                    StringProp(name="Info", value=info),
                    StringProp(name="Date", value=datestr),
                    UriProp(name="MISP Link", value=link),
                )
                # Add all the tags as separate properties
                for tag in event.get("Tag"):
                    tag_name, tag_value = tag["name"].split(":", 1)
                    hit.append(
                        StringProp(name="{}:".format(tag_name),
                                   value=tag_value))
                hits.append(hit)

        return hits
Beispiel #7
0
    def _generate_hit(self, artifact_value, tags_hits_list):
        """
        Query RiskIQ PassiveTotal API for the given 'net.name' (domain name artifact), 'net.uri' (URL) or 'net.ip'
        (IP address) and generate a Hit.
        :param artifact_value
        :param tags_hits_list
        """
        # Passive DNS Results - Hits
        # We grab the totalRecords number and show the First Seen date to Last Seen date interval
        pdns_results_response = self._passivetotal_get_response(
            self.passivetotal_passive_dns_api_url, artifact_value)
        pdns_hit_number, pdns_first_seen, pdns_last_seen = None, None, None
        if pdns_results_response.status_code == 200:
            pdns_results = pdns_results_response.json()
            pdns_hit_number = pdns_results.get("totalRecords", None)
            pdns_first_seen = pdns_results.get("firstSeen", None)
            pdns_last_seen = pdns_results.get("lastSeen", None)
            LOG.info(pdns_hit_number)
            LOG.info(pdns_first_seen)
            LOG.info(pdns_last_seen)
        else:
            LOG.info(
                "No Passive DNS information found for artifact value: {0}".
                format(artifact_value))
            LOG.debug(pdns_results_response.text)

        # URL Classification - suspicious, malicious etc
        classification_results_response = self._passivetotal_get_response(
            self.passivetotal_actions_class_api_url, artifact_value)
        classification_hit = None
        if classification_results_response.status_code == 200:
            classification_results = classification_results_response.json()
            classification_hit = classification_results.get(
                "classification", None)
            LOG.info(classification_hit)
        else:
            LOG.info(
                "No URL classification found for artifact value: {0}".format(
                    artifact_value))
            LOG.debug(classification_results_response.text)

        # Count of subdomains
        subdomain_results_response = self._passivetotal_get_response(
            self.passivetotal_enrich_subdom_api_url, artifact_value)
        subdomain_hits_number, first_ten_subdomains = None, None
        if subdomain_results_response.status_code == 200:
            subdomain_results = subdomain_results_response.json()
            subdomain_hits = subdomain_results.get("subdomains", None)
            subdomain_hits_number = len(
                subdomain_hits) if subdomain_hits else None
            first_ten_subdomains = ', '.join(
                subdomain_hits[:10]) if subdomain_hits else None
            LOG.info(subdomain_hits_number)
            LOG.info(first_ten_subdomains)
        else:
            LOG.info("No subdomain information found for artifact value: {0}".
                     format(artifact_value))
            LOG.debug(subdomain_results_response.text)

        # Convert tags hits list to str
        tags_hits = ", ".join(tags_hits_list) if tags_hits_list else None

        # Construct url back to to PassiveThreat
        report_url = self.passivetotal_community_url + artifact_value

        return Hit(
            NumberProp(name="Number of Passive DNS Records",
                       value=pdns_hit_number),
            StringProp(name="First Seen", value=pdns_first_seen),
            StringProp(name="Last Seen", value=pdns_last_seen),
            NumberProp(name="Subdomains - All", value=subdomain_hits_number),
            StringProp(name="Subdomains - First ten Hostnames",
                       value=first_ten_subdomains),
            StringProp(name="Tags", value=tags_hits),
            StringProp(name="Classification", value=classification_hit),
            UriProp(name="Report Link", value=report_url))