예제 #1
0
def test_register_custom_object_with_version():
    custom_obj_1 = {
        "type": "x-new-type-2",
        "id": "x-new-type-2--00000000-0000-4000-8000-000000000007",
    }

    cust_obj_1 = parsing.dict_to_stix2(custom_obj_1, version='2.0')
    v = 'v20'

    assert cust_obj_1.type in parsing.STIX2_OBJ_MAPS[v]['objects']
    # spec_version is not in STIX 2.0, and is required in 2.1, so this
    # suffices as a test for a STIX 2.0 object.
    assert "spec_version" not in cust_obj_1
예제 #2
0
파일: data.py 프로젝트: tenzir/threatbus
 def decode_hook(self, dct: dict):
     type_ = dct.get("type", None)
     if not type_:
         return dct
     if type_ != SnapshotEnvelope.__name__.lower():
         # decoders walk through nested objects first (bottom up).
         # try to parse nested stix2 objs per best-effort, else bubble up
         try:
             return dict_to_stix2(dct, allow_custom=True)
         except Exception:
             return dct
     return SnapshotEnvelope(
         MessageType(int(dct["snapshot_type"])),
         dct["snapshot_id"],
         parse(dct["body"], allow_custom=True),
     )
예제 #3
0
def test_dict_to_stix2_bundle_with_version():
    with pytest.raises(exceptions.InvalidValueError) as excinfo:
        parsing.dict_to_stix2(BUNDLE, version='2.0')

    msg = "Invalid value for Bundle 'objects': Spec version 2.0 bundles don't yet support containing objects of a different spec version."
    assert str(excinfo.value) == msg
예제 #4
0
def test_dict_to_stix2_bundle_with_version():
    with pytest.raises(exceptions.ExtraPropertiesError) as excinfo:
        parsing.dict_to_stix2(BUNDLE, version='2.1')

    assert str(
        excinfo.value) == "Unexpected properties for Bundle: (spec_version)."
예제 #5
0
def maltego_to_stix2(
        entity: MaltegoMsg,
        transform: MaltegoTransform = None,
        allow_custom_types=True,
        allow_custom_fields=True,
        allow_skipping_stix2_coercion=False
) -> Optional[Union[_STIXBase, dict]]:
    """
    Try to convert any incoming Maltego entity to the closest corresponding STIX2 entity.

    If the input is a "proper" STIX2 entity (i.e. it is in the "maltego.STIX2." namespace), the recovered entity will be
    (at least nearly) equivalent to the STIX2 JSON object that would have been used to generate the Maltego entity.

    If the input is any other Maltego entity, a best guess is applied to generate an equivalent STIX2 object. If no
    matching STIX2 type is found, None is returned unless allow_custom_types is set to True. If allow_custom_types is
    used, this function will generate arbitrary STIX2-like objects will be generated (e.g. a maltego.Person entity will
    result in a 'maltego-person' STIX2 object.

    Properties will be translated according to an internally defined mapping. For instance, in Maltego, the STIX field
    "value" of "domain-name" is instead called "fqdn" on the "STIX2.domain-name" Entity. This mapping will be undone by
    this function, to translate the "fqdn" field back to "value". If allow_custom_fields is True, *all* fields will be
    added to the output object,

    :param entity: an incoming MaltegoMsg object (see maltego-trx library)
    :param transform: reference to the transform object (pass in 'response' from the calling transform). If missing, no
        logs will be sent to the client.
    :param allow_custom_types: if True, arbitrary new types may be returned regardless of input type, if False (default)
        only return a result where an "official" STIX2 types can be found.
    :param allow_custom_fields: if True, *all* properties on the Maltego entitiy will be added to the output STIX
        object, no matter if a mapping exists for it or not. If False (default), only properties that can be explicitly
        mapped are kept on the output STIX object.
    :param allow_skipping_stix2_coercion: if True, the final dict is directly returned without being converted to a
        _STIXBase object first. By default, this is turned off.
    :return: The resulting _STIXBase object, or the equivalent dictionary if skip_stix2_coercion is True (or None).
    """
    is_proper_stix2 = False
    stix2_type = None
    for type_dict in entity.Genealogy:
        type_ = type_dict["Name"]
        if type_.startswith("maltego.STIX2."):
            is_proper_stix2 = True
            stix2_type = entity.Properties["type"]
            break
        elif type_ in _partial_reverse_type_map:
            stix2_type = _partial_reverse_type_map[type_]
            break
    if stix2_type is None:
        if allow_custom_types:
            stix2_type = (entity.Genealogy[0]["Name"].lower().replace(
                ".", "-"))
        else:
            return None

    res_dict = convert_maltego_property_bag_to_stix2(
        entity.Properties,
        stix2_type,
        allow_custom_fields=allow_custom_fields,
        assume_stix_input=is_proper_stix2)

    obj_type: Type[_STIXBase] = get_stix_type(res_dict)
    for prop_name, prop_def in obj_type._properties.items():
        prop_value = res_dict.get(prop_name)
        if not prop_value:
            continue

        # TODO try to validate whether stix will accept the property.
        #  If not, try some recovery, if it fails, just delete the prop
        valid = False
        try:
            prop_def.clean(res_dict[prop_name])
            valid = True
        except:
            # TODO add more normalizers to recover
            normalizer = normalizers.get(prop_def.__class__)
            if normalizer:
                try:
                    res_dict[prop_name] = normalizer(prop_value)
                    valid = True
                except:
                    pass

        if not valid:
            if allow_custom_fields:
                new_name = f"x_{prop_name}_unparseable"
                if transform is not None:
                    transform.addUIMessage(
                        f"Warning: STIX2 conversion of property {prop_name} failed, it was added as {new_name} instead",
                        "PartialError")
                res_dict[new_name] = prop_value
            else:
                if transform is not None:
                    transform.addUIMessage(
                        f"Warning: STIX2 conversion of property {prop_name} failed and it was removed from the output",
                        "PartialError")
                del res_dict[prop_name]

    # Handle default values
    mapping = _heritage_config.get(stix2_type)
    if mapping is not None and mapping.default_values is not None:
        for k, v in mapping.default_values.items():
            if k not in res_dict:
                res_dict.update({k: v})

    failed = True
    reason = None
    extra_reasons = []
    stix2_object = res_dict
    try:
        stix2_object = dict_to_stix2(res_dict,
                                     allow_custom=allow_custom_types
                                     or allow_custom_fields)
        failed = False
    except MissingPropertiesError as e:
        reason = f"Missing properties error: {', '.join(e.args)}"
        for missing_prop_name in e.properties:
            if mapping is not None and missing_prop_name in mapping.property_map:
                maps_to = mapping.property_map[missing_prop_name]
                extra_reasons.append(
                    f"Note: the property '{missing_prop_name}' is likely called '{maps_to}' on your input Entity "
                    f"(and appears to be empty there).")

    except PropertyPresenceError as e:
        reason = f"Property presence error ({e.__class__}): {', '.join(e.args)}"
    except ObjectConfigurationError as e:
        if hasattr(e, "reason"):
            reason = f"Object configuration error: {e.reason}"
        else:
            reason = f"Unknown object configuration error"
    except Exception as e:
        reason = f"Unknown exception occurred."

    if failed:
        if allow_skipping_stix2_coercion:
            if transform is not None:
                transform.addUIMessage(
                    f"Warning: Strict STIX2 conversion of object failed, "
                    f"object will be returned as-is and may not be fully STIX2 compliant. "
                    f"Reason of conversion failure: {reason}", "PartialError")
        else:
            if transform is not None:
                transform.addUIMessage(
                    f"Error: Strict STIX2 conversion of object failed, no output will be returned. "
                    f"Reason of failure: {reason}", "PartialError")
            stix2_object = None

    for r in extra_reasons:
        if transform is not None:
            transform.addUIMessage(r, "PartialError")

    return stix2_object