Exemple #1
0
def is_point_equality_ioc(pattern_str: str) -> bool:
    """
    Predicate to check if a STIX-2 pattern is a point-IoC, i.e., if the pattern
    only consists of a single EqualityComparisonExpression
    @param pattern_str The STIX-2 pattern string to inspect
    """
    try:
        pattern = Pattern(pattern_str)
        # InspectionListener https://github.com/oasis-open/cti-pattern-validator/blob/e926d0a14adf88de08acb908a51db1f453c13647/stix2patterns/v21/inspector.py#L5
        # E.g.,   pattern = "[domain-name:value = 'evil.com']"
        # =>           il = pattern_data(comparisons={'domain-name': [(['value'], '=', "'evil.com'")]}, observation_ops=set(), qualifiers=set())
        # =>  cybox_types = ['domain-name']
        il = pattern.inspect()
        cybox_types = list(il.comparisons.keys())
        return (
            len(il.observation_ops) == 0 and len(il.qualifiers) == 0
            and len(il.comparisons) == 1 and
            len(cybox_types) == 1  # must be point-indicator (one field only)
            and len(il.comparisons[cybox_types[0]][0]) ==
            3  # ('value', '=', 'evil.com')
            and
            il.comparisons[cybox_types[0]][0][1] == "="  # equality comparison
        )
    except Exception:
        return False
def test_config_continue_path_through_ref_always(num_trials,
                                                 default_object_generator):
    pattern_config = Config(
        min_pattern_size=5,
        max_pattern_size=10,
        probability_continue_path_through_ref=1,
    )

    generator = PatternGenerator(default_object_generator, "2.1",
                                 pattern_config)

    for _ in range(num_trials):
        pattern_str = generator.generate()
        pattern_obj = Pattern(pattern_str)
        pattern_data = pattern_obj.inspect()

        for comparisons in pattern_data.comparisons.values():
            for path, _, _ in comparisons:
                # If all paths continue through refs, a ref prop can't be last,
                # and a refs prop can't be second-to-last.
                path_len = len(path)
                for i, path_elt in enumerate(path):
                    if isinstance(path_elt, str):
                        assert not path_elt.endswith(
                            "_ref") or i < path_len - 1
                        assert not path_elt.endswith(
                            "_refs") or i < path_len - 2
def test_config_repeat_count(num_trials, default_object_generator):
    pattern_config = Config(
        min_repeat_count=3,
        max_repeat_count=7,
        # Ensure there are lots of qualifiers, to increase the probability we
        # will actually see some REPEATS qualifiers to test.  There's not
        # actually any guarantee any will show up in the pattern!
        # ("probability_qualifier=1" below guarantees some qualifiers will be
        # included, but not which type of qualifiers they are.)
        min_pattern_size=5,
        max_pattern_size=10,
        probability_qualifier=1,
    )
    generator = PatternGenerator(default_object_generator, "2.1",
                                 pattern_config)

    for _ in range(num_trials):
        pattern_str = generator.generate()
        pattern_obj = Pattern(pattern_str)
        pattern_data = pattern_obj.inspect()

        for qualifier in pattern_data.qualifiers:
            m = _REPEATS_RE.match(qualifier)
            if m:
                repeat_count = int(m.group(1))
                assert 3 <= repeat_count <= 7
Exemple #4
0
def test_config_probability_index_star_step_never(
        num_trials, default_object_generator
):
    pattern_config = Config(
        min_pattern_size=5,
        max_pattern_size=10,
        probability_index_star_step=0,
    )

    generator = PatternGenerator(
        default_object_generator, "2.1", pattern_config
    )

    for _ in range(num_trials):
        pattern_str = generator.generate()
        pattern_obj = Pattern(pattern_str)
        pattern_data = pattern_obj.inspect()

        for comparisons in pattern_data.comparisons.values():
            for path, _, _ in comparisons:
                # If always using integer steps into lists, there should never
                # be any star steps.
                assert not any(
                    path_elt is stix2patterns.inspector.INDEX_STAR
                    for path_elt in path
                )
def test_config_pattern_size(num_trials, default_object_generator):
    pattern_config = Config(min_pattern_size=3, max_pattern_size=7)
    generator = PatternGenerator(default_object_generator, "2.1",
                                 pattern_config)

    for _ in range(num_trials):
        pattern_str = generator.generate()
        pattern_obj = Pattern(pattern_str)
        pattern_data = pattern_obj.inspect()

        comparison_data = pattern_data.comparisons
        pattern_size = sum(
            len(comparisons) for comparisons in comparison_data.values())

        assert 3 <= pattern_size <= 7
def test_config_within_count(num_trials, default_object_generator):
    pattern_config = Config(
        min_within_count=3,
        max_within_count=7,
        min_pattern_size=5,
        max_pattern_size=10,
        probability_qualifier=1,
    )
    generator = PatternGenerator(default_object_generator, "2.1",
                                 pattern_config)

    for _ in range(num_trials):
        pattern_str = generator.generate()
        pattern_obj = Pattern(pattern_str)
        pattern_data = pattern_obj.inspect()

        for qualifier in pattern_data.qualifiers:
            m = _WITHIN_RE.match(qualifier)
            if m:
                within_count = int(m.group(1))
                assert 3 <= within_count <= 7
def test_comparisons(pattern, expected_comparisons):
    compiled_pattern = Pattern(pattern)
    pattern_data = compiled_pattern.inspect()

    assert pattern_data.comparisons == expected_comparisons
def test_observation_ops(pattern, expected_obs_ops):
    compiled_pattern = Pattern(pattern)
    pattern_data = compiled_pattern.inspect()

    assert pattern_data.observation_ops == expected_obs_ops
def test_qualifiers(pattern, expected_qualifiers):
    compiled_pattern = Pattern(pattern)
    pattern_data = compiled_pattern.inspect()

    assert pattern_data.qualifiers == expected_qualifiers
def patterns(instance, options):
    """Ensure that the syntax of the pattern of an indicator is valid, and that
    objects and properties referenced by the pattern are valid.
    """
    if (instance['type'] != 'indicator' or instance.get('pattern_type', '') != 'stix' or
            isinstance(instance.get('pattern', None), str) is False):
        return

    pattern = instance['pattern']
    if 'pattern_version' in instance:
        pattern_version = instance['pattern_version']
    elif 'spec_version' in instance:
        pattern_version = instance['spec_version']
    else:
        pattern_version = '2.1'
    errors = pattern_validator(pattern, pattern_version)

    # Check pattern syntax
    if errors:
        for e in errors:
            yield PatternError(str(e), instance['id'])
        return

    p = Pattern(pattern)
    inspection = p.inspect().comparisons
    for objtype in inspection:
        # Check observable object types
        if objtype in enums.OBSERVABLE_TYPES:
            pass
        elif (not TYPE_FORMAT_RE.match(objtype) or
              len(objtype) < 3 or len(objtype) > 250):
            yield PatternError("'%s' is not a valid observable type name"
                               % objtype, instance['id'])
        elif (all(x not in options.disabled for x in ['all', 'format-checks', 'custom-prefix']) and
              'extensions-use' in options.disabled and not CUSTOM_TYPE_PREFIX_RE.match(objtype)):
            yield PatternError("Custom Observable Object type '%s' should start "
                               "with 'x-' followed by a source unique identifier "
                               "(like a domain name with dots replaced by "
                               "hyphens), a hyphen and then the name"
                               % objtype, instance['id'])
        elif (all(x not in options.disabled for x in ['all', 'format-checks', 'custom-prefix-lax']) and
              'extensions-use' in options.disabled and not CUSTOM_TYPE_LAX_PREFIX_RE.match(objtype)):
            yield PatternError("Custom Observable Object type '%s' should start "
                               "with 'x-'" % objtype, instance['id'])

        # Check observable object properties
        expression_list = inspection[objtype]
        for exp in expression_list:
            path = exp[0]
            # Get the property name without list index, dictionary key, or referenced object property
            prop = path[0]
            if objtype in enums.OBSERVABLE_PROPERTIES and prop in enums.OBSERVABLE_PROPERTIES[objtype]:
                continue
            elif not PROPERTY_FORMAT_RE.match(prop):
                yield PatternError("'%s' is not a valid observable property name"
                                   % prop, instance['id'])
            elif objtype not in enums.OBSERVABLE_TYPES:
                continue  # custom SCOs aren't required to use x_ prefix on properties
            elif (all(x not in options.disabled for x in ['all', 'format-checks', 'custom-prefix']) and
                  'extensions-use' in options.disabled and not CUSTOM_PROPERTY_PREFIX_RE.match(prop)):
                yield PatternError("Cyber Observable Object custom property '%s' "
                                   "should start with 'x_' followed by a source "
                                   "unique identifier (like a domain name with "
                                   "dots replaced by underscores), an "
                                   "underscore and then the name"
                                   % prop, instance['id'])
            elif (all(x not in options.disabled for x in ['all', 'format-checks', 'custom-prefix-lax']) and
                  'extensions-use' in options.disabled and not CUSTOM_PROPERTY_LAX_PREFIX_RE.match(prop)):
                yield PatternError("Cyber Observable Object custom property '%s' "
                                   "should start with 'x_'" % prop, instance['id'])