Skip to content

RF Bundle

psengine.stix2.rf_bundle.RFBundle

Class for creating STIX2 bundles from Recorded Future objects.

from_analyst_note classmethod

from_analyst_note(
    note: AnalystNote,
    attachment: bytes = None,
    split_snort: bool = False,
    identity: Identity = None,
) -> Bundle

Creates a STIX2 bundle from a Recorded Future analyst note.

PARAMETER DESCRIPTION
note

A Recorded Future analyst note.

TYPE: AnalystNote

attachment

A note attachment.

TYPE: bytes DEFAULT: None

split_snort

Whether to split Snort rules into separate DetectionRule objects.

TYPE: bool DEFAULT: False

identity

An author identity. Defaults to Recorded Future.

TYPE: Identity DEFAULT: None

RETURNS DESCRIPTION
Bundle

A STIX2 bundle.

Source code in psengine/stix2/rf_bundle.py
@classmethod
def from_analyst_note(
    cls,
    note: Annotated[AnalystNote, Doc('A Recorded Future analyst note.')],
    attachment: Annotated[bytes, Doc('A note attachment.')] = None,
    split_snort: Annotated[
        bool, Doc('Whether to split Snort rules into separate DetectionRule objects.')
    ] = False,
    identity: Annotated[
        Identity, Doc('An author identity. Defaults to Recorded Future.')
    ] = None,
) -> Annotated[Bundle, Doc('A STIX2 bundle.')]:
    """Creates a STIX2 bundle from a Recorded Future analyst note."""
    LOG.info(f'Creating STIX2 bundle from analyst note {note.id_}')

    if not identity:
        identity = create_rf_author()
    objects = [identity]
    topics = [topic.name for topic in note.attributes.topic]
    report_types = _create_report_types(topics)
    for entity in note.attributes.note_entities:
        try:
            stix_entity = convert_entity(entity.name, entity.type_)
            if isinstance(stix_entity, IndicatorEntity):
                objects.extend(stix_entity.stix_objects)
            else:
                objects.append(stix_entity.stix_obj)

        except UnsupportedConversionTypeError as err:  # noqa: PERF203
            LOG.warning(str(err) + '. Skipping...')
            continue

    if attachment and note.detection_rule_type in SUPPORTED_HUNTING_RULES:
        # This is a workaround for OpenCTI
        # OpenCTI does not support multiple Snort rules within a single DetectionRule object
        # so we split them into separate objects (split_snort = True)
        if note.detection_rule_type == 'snort' and split_snort is True:
            objects.extend(_split_snort_rules(note, str(attachment, 'UTF-8')))
        else:
            rule = DetectionRuleEntity(
                name=note.attributes.title,
                type_=note.detection_rule_type,
                content=str(attachment, 'UTF-8'),
            )
            objects.append(rule.stix_obj)

    external_references = _generate_external_references(note.attributes.validation_urls)
    external_references.append(
        {
            'source_name': 'Recorded Future',
            'url': note.portal_url,
        },
    )

    report = Report(
        name=note.attributes.title,
        created_by_ref=identity.id,
        description=note.attributes.text,
        published=note.attributes.published,
        labels=topics,
        report_types=report_types,
        object_refs=[obj.id for obj in objects],
        object_marking_refs=TLP_MAP['amber'],
        external_references=external_references,
    )
    objects.append(report)

    return Bundle(objects=objects, allow_custom=True)

from_default_risklist classmethod

from_default_risklist(
    risklist: list[DefaultRiskList],
    entity_type: str,
    identity: Identity = None,
) -> Bundle

Creates STIX2 bundle from a Recorded Future default risklist.

PARAMETER DESCRIPTION
risklist

A Recorded Future default risklist (contains the standard 5 columns).

TYPE: list[DefaultRiskList]

entity_type

An entity type.

TYPE: str

identity

An author identity. Defaults to Recorded Future.

TYPE: Identity DEFAULT: None

RAISES DESCRIPTION
STIX2TransformError

If the risklist is not valid.

STIX2TransformError

If EvidenceDetails is not valid JSON.

STIX2TransformError

If the bundle cannot be created.

RETURNS DESCRIPTION
Bundle

A STIX2 bundle.

Source code in psengine/stix2/rf_bundle.py
@classmethod
def from_default_risklist(
    cls,
    risklist: Annotated[
        list[DefaultRiskList],
        Doc('A Recorded Future default risklist (contains the standard 5 columns).'),
    ],
    entity_type: Annotated[str, Doc('An entity type.')],
    identity: Annotated[
        Identity, Doc('An author identity. Defaults to Recorded Future.')
    ] = None,
) -> Annotated[Bundle, Doc('A STIX2 bundle.')]:
    """Creates STIX2 bundle from a Recorded Future default risklist.

    Raises:
        STIX2TransformError: If the risklist is not valid.
        STIX2TransformError: If EvidenceDetails is not valid JSON.
        STIX2TransformError: If the bundle cannot be created.
    """
    if not identity:
        identity = create_rf_author()
    objects = [identity]
    LOG.info(f'Creating STIX2 bundle from {entity_type} risklist')

    try:
        enriched_indicators = []
        for ioc in risklist:
            indicator = EnrichedIndicator(
                name=ioc.ioc,
                type_=ENTITY_TYPE_MAP[entity_type],
                confidence=ioc.risk_score,
                evidence_details=ioc.evidence_details,
            )
            enriched_indicators.append(indicator)

    except KeyError as ke:
        raise STIX2TransformError(f'Risklist missing header: {ke}') from ke

    except json.JSONDecodeError as jse:
        raise STIX2TransformError(f'EvidenceDetails is not valid JSON: {jse}') from jse

    for i in enriched_indicators:
        objects.extend(i.stix_objects)

    try:
        bundle = Bundle(objects=objects, allow_custom=True)
    except (
        ValueError,
        ExtraPropertiesError,
        InvalidValueError,
        MissingPropertiesError,
        STIXError,
    ) as e:
        raise STIX2TransformError(f'Failed to create STIX2 bundle from risklist. {e}') from e

    return bundle