def test_hex(): exp_and = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("file:mime_type", "image/bmp"), stix2.EqualityComparisonExpression("file:magic_number_hex", stix2.HexConstant("ffd8"))]) exp = stix2.ObservationExpression(exp_and) assert str(exp) == "[file:mime_type = 'image/bmp' AND file:magic_number_hex = h'ffd8']"
def test_invalid_and_observable_expression(): with pytest.raises(ValueError) as excinfo: stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("user-account:display_name", "admin"), stix2.EqualityComparisonExpression("email-addr:display_name", stix2.StringConstant("admin"))]) assert "All operands to an 'AND' expression must have the same object type" in str(excinfo)
def test_multiple_file_observable_expression(observation_class, op): exp1 = stix2.EqualityComparisonExpression( "file:hashes.'SHA-256'", stix2.HashConstant( "bf07a7fbb825fc0aae7bf4a1177b2b31fcf8a3feeaf7092761e18c859ee52a9c", 'SHA-256', ), ) exp2 = stix2.EqualityComparisonExpression( "file:hashes.MD5", stix2.HashConstant("cead3f77f6cda6ec00f57d76c9a6879f", "MD5"), ) bool1_exp = stix2.OrBooleanExpression([exp1, exp2]) exp3 = stix2.EqualityComparisonExpression( "file:hashes.'SHA-256'", stix2.HashConstant( "aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f", 'SHA-256', ), ) op1_exp = stix2.ObservationExpression(bool1_exp) op2_exp = stix2.ObservationExpression(exp3) exp = observation_class([op1_exp, op2_exp]) assert str( exp ) == "[file:hashes.'SHA-256' = 'bf07a7fbb825fc0aae7bf4a1177b2b31fcf8a3feeaf7092761e18c859ee52a9c' OR file:hashes.MD5 = 'cead3f77f6cda6ec00f57d76c9a6879f'] {} [file:hashes.'SHA-256' = 'aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f']".format( op) # noqa
def test_root_types(): ast = stix2.ObservationExpression( stix2.AndBooleanExpression( [stix2.ParentheticalExpression( stix2.OrBooleanExpression([ stix2.EqualityComparisonExpression("a:b", stix2.StringConstant("1")), stix2.EqualityComparisonExpression("b:c", stix2.StringConstant("2"))])), stix2.EqualityComparisonExpression(u"b:d", stix2.StringConstant("3"))])) assert str(ast) == "[(a:b = '1' OR b:c = '2') AND b:d = '3']"
def test_file_observable_expression(): exp1 = stix2.EqualityComparisonExpression("file:hashes.'SHA-256'", stix2.HashConstant( "aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f", 'SHA-256')) exp2 = stix2.EqualityComparisonExpression("file:mime_type", stix2.StringConstant("application/x-pdf")) bool_exp = stix2.AndBooleanExpression([exp1, exp2]) exp = stix2.ObservationExpression(bool_exp) assert str(exp) == "[file:hashes.'SHA-256' = 'aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f' AND file:mime_type = 'application/x-pdf']" # noqa
def test_multiple_qualifiers(): exp_and = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("network-traffic:dst_ref.type", "domain-name"), stix2.EqualityComparisonExpression("network-traffic:dst_ref.value", "example.com")]) exp_ob = stix2.ObservationExpression(exp_and) qual_rep = stix2.RepeatQualifier(5) qual_within = stix2.WithinQualifier(stix2.IntegerConstant(1800)) exp = stix2.QualifiedObservationExpression(stix2.QualifiedObservationExpression(exp_ob, qual_rep), qual_within) assert str(exp) == "[network-traffic:dst_ref.type = 'domain-name' AND network-traffic:dst_ref.value = 'example.com'] REPEATS 5 TIMES WITHIN 1800 SECONDS" # noqa
def test_and_observable_expression(): exp1 = stix2.AndBooleanExpression([ stix2.EqualityComparisonExpression("user-account:account_type", "unix"), stix2.EqualityComparisonExpression("user-account:user_id", stix2.StringConstant("1007")), stix2.EqualityComparisonExpression("user-account:account_login", "Peter") ]) exp2 = stix2.AndBooleanExpression([ stix2.EqualityComparisonExpression("user-account:account_type", "unix"), stix2.EqualityComparisonExpression("user-account:user_id", stix2.StringConstant("1008")), stix2.EqualityComparisonExpression("user-account:account_login", "Paul") ]) exp3 = stix2.AndBooleanExpression([ stix2.EqualityComparisonExpression("user-account:account_type", "unix"), stix2.EqualityComparisonExpression("user-account:user_id", stix2.StringConstant("1009")), stix2.EqualityComparisonExpression("user-account:account_login", "Mary") ]) exp = stix2.AndObservationExpression([ stix2.ObservationExpression(exp1), stix2.ObservationExpression(exp2), stix2.ObservationExpression(exp3) ]) assert str( exp ) == "[user-account:account_type = 'unix' AND user-account:user_id = '1007' AND user-account:account_login = '******'] AND [user-account:account_type = 'unix' AND user-account:user_id = '1008' AND user-account:account_login = '******'] AND [user-account:account_type = 'unix' AND user-account:user_id = '1009' AND user-account:account_login = '******']" # noqa
def test_invalid_and_observable_expression(): with pytest.raises(ValueError): stix2.AndBooleanExpression([ stix2.EqualityComparisonExpression( "user-account:display_name", "admin", ), stix2.EqualityComparisonExpression( "email-addr:display_name", stix2.StringConstant("admin"), ), ])
def test_hash_followed_by_registryKey_expression(): hash_exp = stix2.EqualityComparisonExpression("file:hashes.MD5", stix2.HashConstant("79054025255fb1a26e4bc422aef54eb4", "MD5")) o_exp1 = stix2.ObservationExpression(hash_exp) reg_exp = stix2.EqualityComparisonExpression(stix2.ObjectPath("windows-registry-key", ["key"]), stix2.StringConstant("HKEY_LOCAL_MACHINE\\foo\\bar")) o_exp2 = stix2.ObservationExpression(reg_exp) fb_exp = stix2.FollowedByObservationExpression([o_exp1, o_exp2]) para_exp = stix2.ParentheticalExpression(fb_exp) qual_exp = stix2.WithinQualifier(stix2.IntegerConstant(300)) exp = stix2.QualifiedObservationExpression(para_exp, qual_exp) assert str(exp) == "([file:hashes.MD5 = '79054025255fb1a26e4bc422aef54eb4'] FOLLOWEDBY [windows-registry-key:key = 'HKEY_LOCAL_MACHINE\\\\foo\\\\bar']) WITHIN 300 SECONDS" # noqa
def test_create_comparison_expression(): exp = stix2.EqualityComparisonExpression( "file:hashes.'SHA-256'", stix2.HashConstant("aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f", "SHA-256"), ) # noqa assert str(exp) == "file:hashes.'SHA-256' = 'aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f'"
def test_list2(): # alternate way to construct an "IN" Comparison Expression exp = stix2.EqualityComparisonExpression( "process:name", ['proccy', 'proximus', 'badproc'], ) assert str(exp) == "process:name IN ('proccy', 'proximus', 'badproc')"
def test_invalid_constant_type(): with pytest.raises(ValueError) as excinfo: stix2.EqualityComparisonExpression( "artifact:payload_bin", {'foo': 'bar'}, ) assert 'Unable to create a constant' in str(excinfo)
def test_binary(): const = stix2.BinaryConstant("dGhpcyBpcyBhIHRlc3Q=") exp = stix2.EqualityComparisonExpression( "artifact:payload_bin", const, ) assert str(exp) == "artifact:payload_bin = b'dGhpcyBpcyBhIHRlc3Q='"
def test_artifact_payload(): exp1 = stix2.EqualityComparisonExpression("artifact:mime_type", "application/vnd.tcpdump.pcap") exp2 = stix2.MatchesComparisonExpression("artifact:payload_bin", stix2.StringConstant("\\xd4\\xc3\\xb2\\xa1\\x02\\x00\\x04\\x00")) and_exp = stix2.AndBooleanExpression([exp1, exp2]) exp = stix2.ObservationExpression(and_exp) assert str(exp) == "[artifact:mime_type = 'application/vnd.tcpdump.pcap' AND artifact:payload_bin MATCHES '\\\\xd4\\\\xc3\\\\xb2\\\\xa1\\\\x02\\\\x00\\\\x04\\\\x00']" # noqa
def test_invalid_constant_type(): with pytest.raises(ValueError): stix2.EqualityComparisonExpression( "artifact:payload_bin", {'foo': 'bar'}, )
def test_boolean(): exp = stix2.EqualityComparisonExpression( "email-message:is_multipart", True, ) assert str(exp) == "email-message:is_multipart = true"
def process_reports(self, reports): if reports is None: printer.error("No results") return for report in reports: name = report["name"] id = report["id"] stix2_objects = [] stix2_object_refs = [] # FFS AV, consistency! if 'tlp' in report: tlp_id = REF_TLPS[report['tlp'].upper()] elif 'TLP' in report: tlp_id = REF_TLPS[report['TLP'].upper()] else: tlp_id = REF_TLPS['WHITE'] sectors = report['industries'] if sectors: unmatched_sectors = [] added_sector = False for sector in [html.unescape(x.upper()) for x in sectors]: sector_name = None sector_id = None if sector in SECTOR_MAPPINGS: # sector_ids.append(self.octi_sectors[SECTOR_MAPPINGS[sector]]) sector_name = SECTOR_MAPPINGS[sector] try: sector_id = self.octi_sectors[ SECTOR_MAPPINGS[sector]] except Exception as e: printer.error(e) continue else: printer.debug(f"Looking for sector {sector}") match = difflib.get_close_matches( sector, self.octi_sectors.keys(), 1) if not len(match): printer.error( f"Unable to determine a matching sector for {sector}" ) unmatched_sectors.append(sector) continue # sector_ids.append(self.octi_sectors[match[0]]) sector_name = match[0] sector_id = self.octi_sectors[match[0]] if sector_name is not None: s = stix2.Identity(id=sector_id, name=sector_name, identity_class='class', custom_properties={ 'x_opencti_identity_type': 'sector' }) printer.debug(f"Adding sector {sector_name}") stix2_objects.append(s) stix2_object_refs.append(s) added_sector = True if not added_sector: printer.warn("Adding 'UNKNOWN' placeholder sector") s = stix2.Identity(id=self.octi_sectors["UNKNOWN"], name="Unknown", identity_class='class', custom_properties={ 'x_opencti_identity_type': 'sector' }) stix2_objects.append(s) stix2_object_refs.append(s) description = report['description'] if len(unmatched_sectors): description = description + "\n\n###\nUnable to find a match for the following sectors, " \ "please review manually:\n - " + '\n - '.join(unmatched_sectors) printer.info(f"Generating STIX2 for {name} ({id})") author = stix2.Identity(name=report['author_name'], identity_class='organization') stix2_objects.append(author) adversary = None if report['adversary']: printer.debug("Adding adversary {}".format( report['adversary'])) adversary = stix2.IntrusionSet(name=report['adversary']) stix2_object_refs.append(adversary) stix2_objects.append(adversary) if report['targeted_countries']: for country in report['targeted_countries']: printer.debug(f"Adding country {country}") c = stix2.Identity(name=country, identity_class='organization', custom_properties={ 'x_opencti_identity_type': 'country' }) stix2_objects.append(c) stix2_object_refs.append(c) external_refs = [] for eref in report['references']: external_refs.append( stix2.ExternalReference(source_name=tldextract.extract( eref).registered_domain, url=eref)) indicators = report["indicators"] if indicators: for indicator in indicators: resolved_type = self.resolve_type( indicator["type"].lower()) if resolved_type != None and indicator["is_active"]: observable_type = resolved_type observable_value = indicator["indicator"] pattern_type = 'stix' try: if observable_type in PATTERNTYPES: pattern_type = observable_type elif observable_type not in OPENCTISTIX2: printer.info("Not in stix2 dict") else: if 'transform' in OPENCTISTIX2[ observable_type]: if OPENCTISTIX2[observable_type][ 'transform'][ 'operation'] == 'remove_string': observable_value = observable_value.replace( OPENCTISTIX2[observable_type] ['transform']['value'], '') lhs = stix2.ObjectPath( OPENCTISTIX2[observable_type]['type'], OPENCTISTIX2[observable_type]['path']) observable_value = stix2.ObservationExpression( stix2.EqualityComparisonExpression( lhs, observable_value)) except Exception as e: printer.error(e) printer.info( "Could not determine suitable pattern") try: indicator_obj = stix2.Indicator( name=indicator["indicator"], description=indicator["description"], pattern=str(observable_value), valid_from=indicator["created"], labels=['malicious-activity'], created_by_ref=author, object_marking_refs=[tlp_id], custom_properties={ 'x_opencti_observable_type': resolved_type, 'x_opencti_observable_value': indicator["indicator"], 'x_opencti_pattern_type': pattern_type }) stix2_object_refs.append(indicator_obj) stix2_objects.append(indicator_obj) except Exception as e: printer.error(e) printer.info("Couldn't fetch indicator") else: printer.error("No indicators") report = stix2.Report(name=name, description=description, created_by_ref=author, labels=['threat-report'], published=report['created'], created=report['created'], modified=report['modified'], object_refs=stix2_object_refs, object_marking_refs=[tlp_id], external_references=external_refs) stix2_objects.append(report) bundle = stix2.Bundle(stix2_objects).serialize() if not self.dryrun: self.opencti_connector_helper.send_stix2_bundle( bundle, None, True, False) printer.info("Sending to OpenCTI") #printer.debug(str(bundle)) else: printer.debug(f"No sectors, disregarding '{name}'")