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_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_boolean_expression(): exp1 = stix2.MatchesComparisonExpression("email-message:from_ref.value", stix2.StringConstant(".+\\@example\\.com$")) exp2 = stix2.MatchesComparisonExpression("email-message:body_multipart[*].body_raw_ref.name", stix2.StringConstant("^Final Report.+\\.exe$")) exp = stix2.AndBooleanExpression([exp1, exp2]) assert str(exp) == "email-message:from_ref.value MATCHES '.+\\\\@example\\\\.com$' AND email-message:body_multipart[*].body_raw_ref.name MATCHES '^Final Report.+\\\\.exe$'" # noqa
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_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_boolean_expression_with_parentheses(): exp1 = stix2.MatchesComparisonExpression(stix2.ObjectPath("email-message", [stix2.ReferenceObjectPathComponent("from_ref"), stix2.BasicObjectPathComponent("value")]), stix2.StringConstant(".+\\@example\\.com$")) exp2 = stix2.MatchesComparisonExpression("email-message:body_multipart[*].body_raw_ref.name", stix2.StringConstant("^Final Report.+\\.exe$")) exp = stix2.ParentheticalExpression(stix2.AndBooleanExpression([exp1, exp2])) assert str(exp) == "(email-message:from_ref.value MATCHES '.+\\\\@example\\\\.com$' AND email-message:body_multipart[*].body_raw_ref.name MATCHES '^Final Report.+\\\\.exe$')" # 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_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 __generate_complex_comparison_expression(self, size, type_constraint=None): """ Generates a "complex" comparison expression, i.e. one which may consist of sub-expressions connected via AND or OR. If a type constraint is given, the resulting expression will honor that constraint. :param size: The size of the desired complex comparison expression, in terms of the number of simple comparison expressions it must contain :param type_constraint: An SCO type, or None :return: """ assert size > 0 # This complex expression must be composed of N simple expressions. # This implementation builds the overall expression in two parts: a # left and right side. The location of the split between left and # right is random. A side is randomly chosen to just contain a series # of simple expressions, and the other side will have a nested # subexpression. # # One goal of the strategy is to avoid excessive nested parentheses. # Too many parentheses results in ugly crazy-looking patterns. This # algorithm still can generate some silly patterns, but I hope it helps # a little. if size == 1: expr = self.__generate_simple_comparison_expression_list( 1, type_constraint, False)[0] else: # Choose whether top-level operator will be AND or OR. # This will also determine how we handle the type constraint. is_and = random.random() < 0.5 # If AND, all operands *must* be type-constrained. if is_and and not type_constraint: type_constraint = self.__random_sco_type() # In the following, if type_constraint is None, both left and right # constraints will be None. No need for a special case. If we # have a type constraint, for 'AND', the constraint must be # enforced on both sides. For 'OR', we need only enforce it on one # side. if is_and: left_constraint = right_constraint = type_constraint else: left_constraint, right_constraint = type_constraint, None if random.random() < 0.5: left_constraint, right_constraint = \ right_constraint, left_constraint # Don't let either side be zero size here. Avoids the case where # we have an OR, and randomly choose to enforce the type constraint # on the zero-length side. That can result in an invalid pattern. lsize = random.randint(1, size - 1) rsize = size - lsize if random.random() < 0.5: # Parenthesize right case operands = self.__generate_simple_comparison_expression_list( lsize, left_constraint, is_and) operands.append( stix2.ParentheticalExpression( self.__generate_complex_comparison_expression( rsize, right_constraint))) else: # Parenthesize left case operands = [ stix2.ParentheticalExpression( self.__generate_complex_comparison_expression( lsize, left_constraint)) ] operands.extend( self.__generate_simple_comparison_expression_list( rsize, right_constraint, is_and)) if is_and: expr = stix2.AndBooleanExpression(operands) else: expr = stix2.OrBooleanExpression(operands) return expr