Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cybereason] Initial Release for Cybereason #9595

Merged
merged 7 commits into from
May 7, 2024

Conversation

muskan-agarwal26
Copy link
Contributor

@muskan-agarwal26 muskan-agarwal26 commented Apr 15, 2024

What does this PR do?

  • Generated the skeleton of the Cybereason integration package.
  • Added data stream.
  • Added data collection logic for the data stream.
  • Added the ingest pipeline for the data stream.
  • Mapped fields according to the ECS schema and added Fields metadata in the appropriate yml files.
  • Added test for pipeline for the data stream.

Integration release checklist

This checklist is intended for integrations maintainers to ensure consistency when creating or updating a Package, Module or Dataset for an Integration.

All changes

  • Change follows the contributing guidelines
  • Supported versions of the monitoring target is documented
  • Supported operating systems are documented (if applicable)
  • Integration or System tests exist
  • Documentation exists
  • Fields follow ECS and naming conventions
  • At least a manual test with ES / Kibana / Agent has been performed.
  • Required Kibana version set to: ^8.12.0

New Package

  • Screenshot of the "Add Integration" page on Fleet added

Log dataset changes

  • Pipeline tests exist (if applicable)
  • Generated output for at least 1 log file exists
  • Sample event (sample_event.json) exists

How to test this PR locally

  • Clone integrations repo.
  • Install the elastic package locally.
  • Start the elastic stack using the elastic package.
  • Move to integrations/packages/cybereason directory.
  • Run the following command to run tests.

elastic-package test -v

Automated Test

test-log-cybereason.log

Screenshots

integration
cybereason

@muskan-agarwal26 muskan-agarwal26 changed the title Add support of Cybereason [cybereason] Initial Release for Cybereason Apr 15, 2024
@jamiehynds jamiehynds requested a review from a team April 16, 2024 15:19
@jamiehynds jamiehynds added the Team:Security-Service Integrations Security Service Integrations Team label Apr 16, 2024
@elasticmachine
Copy link

Pinging @elastic/security-service-integrations (Team:Security-Service Integrations)

Copy link
Contributor

@efd6 efd6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial review only.

post_request(
state.url + "/login.html",
"application/x-www-form-urlencoded",
"username="+ state.username.replace("@", "%40", -1) + "&password=" + state.password.replace("@", "%40", -1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"username="+ state.username.replace("@", "%40", -1) + "&password=" + state.password.replace("@", "%40", -1)
"username":[state.username],"password":[state.password]}.format_query()

Comment on lines 107 to 109
(
[{}]
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(
[{}]
)
[{}]

(similar throughout)

Comment on lines 37 to 39
resp.Request.Response.Request.Response.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID")).size() > 0 ?
(resp.Request.Response.Request.Response.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID"))[0].split(";").filter(e, e.contains("JSESSIONID")).size() > 0 ?
resp.Request.Response.Request.Response.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID"))[0].split(";").filter(e, e.contains("JSESSIONID"))[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the intention here? This looks to me like it is trying to

            resp.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID")).as(sess, sess.size() > 0 ?
                sess[0].split(";").as(sub, sub.filter(e, e.contains("JSESSIONID")).size() > 0 ?
                    sub[0]
                  :
                    ""
                )

but it is extremely unclear, so I am not sure.

Note that resp.Request.Response.Request.Response = resp since the response hold a copy of the request and vice versa.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kcreddy Has explained what this field walk is for. This makes sense, though it looks like there are two steps too many. The reason should be explained in a comment in the code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the comments explaining this (and in the other cases where this is done).

)).as(body, {
"cookie": body,
})
).as(cookie_obj, (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why you are doing this; you have state decorated to include the request object, but it's called cookie_obj and you continue to use the original state object. What is the intention? I don't think that you need to do the initial state.with since AFAICS you are just POSTing to get the sess cookie, so ISTM that this whole preamble should be something like:

      post_request(
        state.url + "/login.html",
        "application/x-www-form-urlencoded",
        "username":[state.username],"password":[state.password]}.format_query()
      ).do_request().as(resp, resp.Request.URL.contains("error") ? "" :
          resp.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID")).as(sess, sess.size() > 0 ?
              sess[0].split(";").as(sub, sub.filter(e, e.contains("JSESSIONID")).size() > 0 ?
                  sub[0]
                :
                  ""
              )
            :
              ""
          )

fields:
- username
- password
program: |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think probably this should look something like this, though there are still outstanding questions (see in code comments)

program: |
  post_request(
    state.url + "/login.html",
    "application/x-www-form-urlencoded",
    "username":[state.username],"password":[state.password]}.format_query()
  ).do_request().as(resp, resp.Request.URL.contains("error") ? "" :
      resp.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID")).as(sess, sess.size() > 0 ?
          sess[0].split(";").as(sub, sub.filter(e, e.contains("JSESSIONID")).size() > 0 ?
              sub[0]
            :
              ""
          )
        :
          ""
      )
  ).as(cookie, cookie == "" ? {"events": []} :
    post_request(
      state.url + "/rest/visualsearch/query/simple",
      "application/json",
        {
          "queryPath": [
            {
              "requestedType": "LogonSession",
              "filters": [
                {
                  "facetName": "creationTime",
                  "filterType": "Between",
                  "values": [
                    (
                      state.?cursor.last_timestamp.orValue(null) != null ?
                        state.cursor.last_timestamp
                      :
                        int(now - duration(state.initial_interval)) * 1000
                    ),
                    int(now) * 1000,
                  ]
                }
              ],
              "isResult": "true"
            }
          ],
          "totalResultLimit": state.batch,
          "perGroupLimit": 100,
          "perFeatureLimit": 100,
          "templateContext": "SPECIFIC",
          "queryTimeout": 120000,
          "customFields": [
            "processes",
            "ownerMachine",
            "user",
            "remoteMachine",
            "logonType",
            "creationTime",
            "endTime",
            "elementDisplayName",
          ]
        }.encode_json(),
    ).with({
      "Header": {
        "Cookie": [cookie], 
        "Content-Type": ["application/json"],
      },
    }).do_request().as(resp,
      resp.StatusCode == 200 ?
        bytes(resp.Body).decode_json().as(body, {
          "events": !has(body.?data.resultIdToElementDataMap) ? [{}] : // Why are we returning a single empty event in this case?
          (
            body.data.resultIdToElementDataMap.size() == 0 ? [{}] : // Why are we returning a single empty event in this case?
            (
              body.data.resultIdToElementDataMap.map(e, {
                "message": [
                  body.data.resultIdToElementDataMap[e].encode_json(),
                  body.data.suspicionsMap.encode_json(),
                  body.data.evidenceMap.encode_json(),
                ],
              })
            )
          )
          "cursor": {
            "last_timestamp": int(now) * 1000,
          },
          "username": state.username,
          "password": state.password,
          "initial_interval": state.initial_interval,
          "batch": state.batch,
        })
      :
        {
          "events": [{
            "error": {
              "code": string(resp.StatusCode),
              "id": string(resp.Status),
              "message": string(resp.Body),
            }
          }],
          "username": state.username,
          "password": state.password,
          "initial_interval": state.initial_interval,
          "batch": state.batch,
        }
      )
  )

General comments:

  • avoid re-evals by binding results so that they can be reused
  • please ensure that indentation is correctly used; opening an closing parens, braces and brackets should be on the same column as the first non-whitespace character of the opening, and the column depth increased by the indent width for each line that increases the depth of the syntax
  • please use commas on all array and object lines (this simplifies code mutation later and we don't have JSON rules)
  • avoid unnecessary parens; only use these when the compiler cannot disambiguate from the existing syntax

The programs below look similar and will need equivalent treatment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reply to the questions in the code comments:
As every input requires a corresponding output, in the case of null data in the response, we return empty events.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As every input requires a corresponding output, in the case of null data in the response, we return empty events.

What do you mean by this? If you mean that an expression evaluates to a value and the value has to go somewhere and if there is a place to go there must be a value, this is not quite true since we have optional types now. If you eval an optional type, either by a .? operator, or by an explicit pair of optional.of(x)/opional.none(), then you can define the field that it's going into with ? prefix, {?"field": state.?maybe_exists} will eval to either {} or {"field":<whatever was in state.maybe_exists>} depending on whether state has a field maybe_exists.

@narph narph added the Crest label Apr 26, 2024
1. Added comments in cel code in malware datastream regarding the cookie.
2. Resolved the general comments
packages/cybereason/_dev/build/docs/README.md Outdated Show resolved Hide resolved

## Compatibility

This module has been tested against the latest Cybereason On-Prem version **23.2**.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This module has been tested against the latest Cybereason On-Prem version **23.2**.
This module has been tested against the latest Cybereason On-Premises version **23.2**.

packages/cybereason/_dev/build/docs/README.md Outdated Show resolved Hide resolved
{
"startTime":
(
state.?cursor.last_timestamp.orValue(null) != null ?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is cursor.last_timestamp ever set to null? I can't see this happen anywhere.

1. Suggested changes in readme.
2. Made suggested changes in the cel code.
@efd6
Copy link
Contributor

efd6 commented May 1, 2024

/test

Copy link
Contributor

@efd6 efd6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some remaining issues.

Comment on lines 32 to 40
resp.Request.Response.Request.Response.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID")).as(sess, sess.size() > 0 ?
sess[0].split(";").as(sub, sub.filter(e, e.contains("JSESSIONID")).size() > 0 ?
sub[0]
:
""
)
:
""
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This in incorrectly indented; inconsistent with the other code, which is at 2.

""
)
).as(cookie, cookie == "" ? {"events": []} :
post_request(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

"application/x-www-form-urlencoded",
{"username":[state.username],"password":[state.password]}.format_query()
).do_request().as(resp, resp.Request.URL.contains("error") ? "" :
resp.Request.Response.Request.Response.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID")).as(sess, sess.size() > 0 ?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here and below.

"application/x-www-form-urlencoded",
{"username":[state.username],"password":[state.password]}.format_query()
).do_request().as(resp, resp.Request.URL.contains("error") ? "" :
resp.Request.Response.Request.Response.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID")).as(sess, sess.size() > 0 ?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation here and below.

- username
- password
program: |
(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't wrap the complete program in parens; it's not necessary.

Comment on lines 30 to 31
(!state.?want_more.orValue(false)
?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(!state.?want_more.orValue(false)
?
!state.?want_more.orValue(false) ?

along with associated remove of its partner.

:
""
).as(cookie, cookie == "" && !has(state.cookie) ? {"events": []} :
post_request(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation.

Comment on lines 37 to 39
resp.Request.Response.Request.Response.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID")).size() > 0 ?
(resp.Request.Response.Request.Response.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID"))[0].split(";").filter(e, e.contains("JSESSIONID")).size() > 0 ?
resp.Request.Response.Request.Response.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID"))[0].split(";").filter(e, e.contains("JSESSIONID"))[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the comments explaining this (and in the other cases where this is done).

fields:
- username
- password
program: |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As every input requires a corresponding output, in the case of null data in the response, we return empty events.

What do you mean by this? If you mean that an expression evaluates to a value and the value has to go somewhere and if there is a place to go there must be a value, this is not quite true since we have optional types now. If you eval an optional type, either by a .? operator, or by an explicit pair of optional.of(x)/opional.none(), then you can define the field that it's going into with ? prefix, {?"field": state.?maybe_exists} will eval to either {} or {"field":<whatever was in state.maybe_exists>} depending on whether state has a field maybe_exists.

@efd6
Copy link
Contributor

efd6 commented May 1, 2024

/test

1. Fixed indentation in cel code files.
2. Removed unnecessary line of code in construction of events.
@muskan-agarwal26 muskan-agarwal26 requested a review from efd6 May 4, 2024 05:57
@efd6
Copy link
Contributor

efd6 commented May 5, 2024

/test

Comment on lines 71 to 90
"events": !has(body.?data.resultIdToElementDataMap) ? [{}] :
body.data.resultIdToElementDataMap.map(e, {
"message": [
body.data.resultIdToElementDataMap[e].encode_json(),
body.data.suspicionsMap.encode_json(),
body.data.evidenceMap.encode_json(),
],
}),
"cursor": {
"last_timestamp": (
has(body.?data.resultIdToElementDataMap) && body.data.resultIdToElementDataMap.size() > 0 ?
int(body.data.resultIdToElementDataMap.map(e, int(body.data.resultIdToElementDataMap[e].simpleValues.malopLastUpdateTime.values[0])).max())
:
state.?cursor.last_timestamp.orValue(int(now - duration(state.initial_interval)) * 1000)
)
},
"username": state.username,
"password": state.password,
"initial_interval": state.initial_interval,
"batch": state.batch,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent this block.

{
"queryPath": [
).as(cookie, cookie == "" ? {"events": []} :
post_request(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole block is indented 4, but the rest of the file is indented 2. This is the case for the block under do_request as well.

{
"queryPath": [
).as(cookie, cookie == "" ? {"events": []} :
post_request(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indented 4 instead of 2.

"Content-Type": ["application/json"],
},
}).do_request().as(resp,
resp.StatusCode == 200 ?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indented 4 instead of 2.

Comment on lines 89 to 108
"events": !has(body.?data.resultIdToElementDataMap) ? [{}] :
body.data.resultIdToElementDataMap.map(e, {
"message": [
body.data.resultIdToElementDataMap[e].encode_json(),
body.data.suspicionsMap.encode_json(),
body.data.evidenceMap.encode_json(),
],
}),
"cursor": {
"last_timestamp": (
has(body.?data.resultIdToElementDataMap) && body.data.resultIdToElementDataMap.size() > 0 ?
int(now) * 1000
:
state.?cursor.last_timestamp.orValue(int(now) * 1000)
),
},
"username": state.username,
"password": state.password,
"initial_interval": state.initial_interval,
"batch": state.batch,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be indented.

Comment on lines 86 to 105
"events": !has(body.?data.resultIdToElementDataMap) ? [{}] :
body.data.resultIdToElementDataMap.map(e, {
"message": [
body.data.resultIdToElementDataMap[e].encode_json(),
body.data.suspicionsMap.encode_json(),
body.data.evidenceMap.encode_json(),
],
}),
"cursor": {
"last_timestamp": (
has(body.?data.resultIdToElementDataMap) && body.data.resultIdToElementDataMap.size() > 0 ?
int(now) * 1000
:
state.?cursor.last_timestamp.orValue(int(now) * 1000)
),
},
"username": state.username,
"password": state.password,
"initial_interval": state.initial_interval,
"batch": state.batch,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent this block.

"application/x-www-form-urlencoded",
{"username":[state.username],"password":[state.password]}.format_query()
).do_request().as(resp, resp.Request.URL.contains("error") ? "" : // When the base request redirects to a new request,
resp.Request.Response.Request.Response.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID")).as(sess, sess.size() > 0 ? // we obtain the request object for the initial request's response.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indented 4 instead of 2.

"application/x-www-form-urlencoded",
{"username":[state.username],"password":[state.password]}.format_query()
).do_request().as(resp, resp.Request.URL.contains("error") ? "" : // When the base request redirects to a new request,
resp.Request.Response.Request.Response.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID")).as(sess, sess.size() > 0 ? // we obtain the request object for the initial request's response.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indented 4 instead of 2.

Comment on lines 29 to 46
(!state.?want_more.orValue(false) ?
post_request(
state.url.trim_right("/") + "/login.html",
"application/x-www-form-urlencoded",
{"username":[state.username],"password":[state.password]}.format_query()
).do_request().as(resp, resp.Request.URL.contains("error") ? "" : // When the base request redirects to a new request,
resp.Request.Response.Request.Response.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID")).as(sess, sess.size() > 0 ? // we obtain the request object for the initial request's response.
sess[0].split(";").as(sub, sub.filter(e, e.contains("JSESSIONID")).size() > 0 ? // To retrieve the appropriate header based on the redirect,
sub[0] // we've implemented the following method.
:
""
)
:
""
)
)
:
""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(!state.?want_more.orValue(false) ?
post_request(
state.url.trim_right("/") + "/login.html",
"application/x-www-form-urlencoded",
{"username":[state.username],"password":[state.password]}.format_query()
).do_request().as(resp, resp.Request.URL.contains("error") ? "" : // When the base request redirects to a new request,
resp.Request.Response.Request.Response.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID")).as(sess, sess.size() > 0 ? // we obtain the request object for the initial request's response.
sess[0].split(";").as(sub, sub.filter(e, e.contains("JSESSIONID")).size() > 0 ? // To retrieve the appropriate header based on the redirect,
sub[0] // we've implemented the following method.
:
""
)
:
""
)
)
:
""
(
state.?want_more.orValue(false) ? "" : post_request(
state.url.trim_right("/") + "/login.html",
"application/x-www-form-urlencoded",
{"username":[state.username],"password":[state.password]}.format_query()
).do_request().as(resp, resp.Request.URL.contains("error") ? "" : // When the base request redirects to a new request,
resp.Request.Response.Request.Response.Header["Set-Cookie"].filter(e, e.contains("JSESSIONID")).as(sess, sess.size() > 0 ? // we obtain the request object for the initial request's response.
sess[0].split(";").as(sub, sub.filter(e, e.contains("JSESSIONID")).size() > 0 ? // To retrieve the appropriate header based on the redirect,
sub[0] // we've implemented the following method.
:
""
)
:
""
)
)

Comment on lines 95 to 137
"events": has(body.?data.malwares) ? body.data.malwares.map(e, { "message": e.encode_json() }) : [{}],
"want_more": body.?data.hasMoreResults.orValue(false),
"batch": state.batch,
"initial_interval": state.initial_interval,
"page_no": body.?data.hasMoreResults.orValue(false) ? int(state.page_no) + 1 : 0,
"username": state.username,
"password": state.password,
"cookie": state.?cookie.orValue(cookie),
"cursor": {
"last_timestamp": (
has(body.?data.malwares) && body.data.malwares.size() > 0 ?
(
has(state.?cursor.last_timestamp) && body.data.malwares.map(e, e.timestamp).max() < state.cursor.last_timestamp ?
state.cursor.last_timestamp
:
int(body.data.malwares.map(e, e.timestamp).max())
)
:
state.?cursor.last_timestamp.orValue(int(now - duration(state.initial_interval)) * 1000)
),
"first_timestamp": (
has(state.?cursor.first_timestamp) && has(body.?data.malwares) ?
(
body.?data.hasMoreResults.orValue(false) ?
state.cursor.first_timestamp
:
state.cursor.last_timestamp
)
:
int(now - duration(state.initial_interval)) * 1000
),
"less_than_timestamp": (
has(state.?cursor.less_than_timestamp) && has(body.?data.malwares) ?
(
body.?data.hasMoreResults.orValue(false) ?
state.cursor.less_than_timestamp
:
int(now) * 1000
)
:
int(now) * 1000
),
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent this block.

@elasticmachine
Copy link

🚀 Benchmarks report

To see the full report comment with /test benchmark fullreport

1. Indentation corrected in all cel files.
@muskan-agarwal26 muskan-agarwal26 requested a review from efd6 May 6, 2024 13:06
@efd6
Copy link
Contributor

efd6 commented May 7, 2024

/test

@elasticmachine
Copy link

💚 Build Succeeded

History

Copy link
Contributor

@efd6 efd6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@efd6 efd6 merged commit 8fad2d6 into elastic:main May 7, 2024
5 checks passed
@elasticmachine
Copy link

Package cybereason - 0.1.0 containing this change is available at https://epr.elastic.co/search?package=cybereason

bmorelli25 pushed a commit to bmorelli25/integrations that referenced this pull request Jun 3, 2024
@nuno-andre
Copy link
Contributor

IMO element_values, single_values, total_values and values are meaningless fields when translated to Elasticsearch.

Also, it would desirable to use ECS where possible.

You may want to take a look at STIX-shifter for a more compact and semantic translation of the data model: https://github.com/opencybersecurityalliance/stix-shifter/tree/develop/stix_shifter_modules/cybereason

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants