TcM1911 / stix2

A pure Go library for working with Structured Threat Information Expression (STIX™) version 2.x data
BSD 2-Clause "Simplified" License
22 stars 6 forks source link

Add MITRE Objects that deviate from spec #50

Closed shellcromancer closed 2 years ago

shellcromancer commented 2 years ago

Thanks for the very complete and nicely documented library!

For my use-case I was hoping to use this to read the response from the MITRE TAXII server such that I can have an always up to date version of the latest ATT&CK Techniques/Tactics. Reading their response with this library works for getting the Tactics (from AttackPattern object's ExternalReferences) but there isn't a way to read the tactics which use a custom first party objects that got deprecated in STIX 2.1.

Is there a way of doing this today that I'm not seeing? If not I'd be happy to work on a PR adding support if you'd accept that (it seems in the vein of #14) by having a either a generic way to grab these or first class support for them.

TcM1911 commented 2 years ago

The library has support for custom objects and to access top level extension attributes. The docs have examples on how to use them https://pkg.go.dev/github.com/TcM1911/stix2#CustomObject. Check and see if this solves your problem.

shellcromancer commented 2 years ago

Thanks for the reply and linking the docs/example.

Here's an example of what I'm trying and how my current usage doesn't feel/work correct: https://go.dev/play/p/uq6W9IzvV8b

Using the Collection.GetAll() method with the custom type "x-mitre-tactic" I get a slice of generic StixObject's which I for any call of GetExtendedTopLevelProperties() towards a relevant field will be nil which made integration hard. I have a commented this part out in my example and have added hacky method to get to the data which is embedded in the initial object. Easier than the series of JSON unmarshal/marshal I could write a type composed of types from the library but it seemed from the docs that something similar to the commented out section should work if it was casted to the non-generic StixObject type?

TcM1911 commented 2 years ago

STIXObject is an interface because the method can return any type. You can type assert it to a CustomObject. If you do that, does that work?

I noticed that most method are uses the object as the receiver. I should change it to be the pointer to be consistent with the other types.

TcM1911 commented 2 years ago

I have a proposal for a solution. The solution adds a CollectionOption to hand over the parsing of a type to a provided handle function. This allows for parsing into your own created type. For example:

type mitreTactic struct {
    Domains            []string             `json:"x_mitre_domains"`
    ObjectMarkingRefs  []Identifier         `json:"object_marking_refs"`
    ID                 Identifier           `json:"id"`
    Type               STIXType             `json:"type"`
    Created            Timestamp            `json:"created"`
    CreatedBy          Identifier           `json:"created_by_ref"`
    ExternalReferences []*ExternalReference `json:"external_references"`
    Modified           Timestamp            `json:"modified"`
    Name               string               `json:"name"`
    Description        string               `json:"description"`
    Version            string               `json:"x_mitre_version"`
    AttackSpecVersion  string               `json:"x_mitre_attack_spec_version"`
    ModifiedBy         Identifier           `json:"x_mitre_modified_by_ref"`
    ShortName          string               `json:"x_mitre_shortname"`
}

// GetID returns the identifier for the object.
func (m mitreTactic) GetID() Identifier {
    return m.ID
}

// GetType returns the object's type.
func (m mitreTactic) GetType() STIXType {
    return m.Type
}

// GetCreated returns the created time for the STIX object. If the object
// does not have a time defined, nil is returned.
func (m mitreTactic) GetCreated() *time.Time {
    return &m.Created.Time
}

// GetModified returns the modified time for the STIX object. If the object
// does not have a time defined, nil is returned.
func (m mitreTactic) GetModified() *time.Time {
    return &m.Modified.Time
}

// GetExtendedTopLevelProperties returns the extra top level properties or
// nil for the object.
func (m mitreTactic) GetExtendedTopLevelProperties() *CustomObject {
    return nil
}

To use it:

    data := []byte(`{
        "type": "bundle",
        "id": "bundle--099f4d3b-9c94-4472-a5b9-b26186b786b0",
        "spec_version": "2.0",
        "objects": [
            {
                "x_mitre_domains": [
                    "enterprise-attack"
                ],
                "object_marking_refs": [
                    "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
                ],
                "id": "x-mitre-tactic--2558fd61-8c75-4730-94c4-11926db2a263",
                "type": "x-mitre-tactic",
                "created": "2018-10-17T00:14:20.652Z",
                "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5",
                "external_references": [
                    {
                        "external_id": "TA0006",
                        "url": "https://attack.mitre.org/tactics/TA0006",
                        "source_name": "mitre-attack"
                    }
                ],
                "modified": "2019-07-19T17:43:41.967Z",
                "name": "Credential Access",
                "description": "The adversary is trying to steal account names and passwords.\n\nCredential Access consists of techniques for stealing credentials like account names and passwords. Techniques used to get credentials include keylogging or credential dumping. Using legitimate credentials can give adversaries access to systems, make them harder to detect, and provide the opportunity to create more accounts to help achieve their goals.",
                "x_mitre_version": "1.0",
                "x_mitre_attack_spec_version": "2.1.0",
                "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5",
                "x_mitre_shortname": "credential-access"
            }
        ]
    }`)

    col, err := FromJSON(data, UseCustomParser("x-mitre-tactic", func(data []byte) (STIXObject, error) {
        var tactic mitreTactic
        err := json.Unmarshal(data, &tactic)
        if err != nil {
            return nil, err
        }
        return &tactic, nil
    }))

    objs := col.GetAll("x-mitre-tactic")
    obj := objs[0].(*mitreTactic)
    fmt.Println(obj.ExternalReferences[0].ExternalID)
}

Let me know if you think this solution would work.

TcM1911 commented 2 years ago

If you want to try it out, you can checkout the branch in PR #51.

shellcromancer commented 2 years ago

Thanks for writing up this proposal and an implementation - I tried this for my use case and it worked out great! :)

TcM1911 commented 2 years ago

Fixed in v0.9.0