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

Support for advanced rulesets, including evaluation of flags, via Common Expression Language #1200

Open
keeleysam opened this issue Oct 13, 2023 · 1 comment

Comments

@keeleysam
Copy link

(discussed IRL with @mlw, opening an issue for discussion)

Santa's process based rulesets currently apply either as an allow or block to all invocations of a executable. This works well for many use cases, but there are times when one may wish to have a rule which allows execution only under certain conditions.

This to mind because of a presentation by @r3ggi about using previously granted permissions of Electron applications though injection of JavaScript, which is possible on many applications though the use of --inspect flags. https://www.electronjs.org/docs/latest/api/command-line-switches#--inspecthostport

In this case, most organizations will allow the use of Electron apps, but to mitigate this issue may want to block execution if a --inspect flag is in the parameters upon launch.

Likewise, organizations might look to use Santa to mitigate vulnerabilities similar to cURL's CVE-2023-38545, which might be exploitable on macOS versions older than 14.0.1 if /usr/bin/curl is launched with a –-socks5-hostname flag or socks5h:// specified for a -x, --proxy, or --preproxy flag.

There are other cases where this functionality could be useful. For example, organizations may want to be sure that Chromium based browsers aren't able to be launched with flags like --force-devtools-available, --remote-allow-origins *, --remote-debugging-address 0.0.0.0 or --unsafely-treat-insecure-origin-as-secure.

These are just examples of where being able to write an advanced rule would help with flags on a process, but there are potentially many other use cases where being able to craft an advanced rule would help.

I propose that https://github.com/google/cel-spec be utilized for these types of rules, and there is a C++ library available as well at https://github.com/google/cel-cpp. CEL is very performant and flexible, and utilized heavily in public-facing Google products such as Conditional IAM in GCP.

For how the rulesets might be structured, this should be a new ruletype, and probably be more specific than the current BINARY type. All existing rules should be able to be implemented as advanced rules, though there should be no need to do so.

As an example, where a current rule looks like this:

  {
    "identifier": "EQHXZ8M8AV",
    "rule_type": "TEAMID",
    "policy": "ALLOWLIST",
    "custom_msg": "Allow Software Google's Team ID",
    "creation_time": 1576623399.151607
  }

an advanced version of the same rule could look like:

  {
    "rule_text": "process.team_id == \"EQHXZ8M8AV\"",
    "rule_type": "ADVANCED",
    "policy": "ALLOWLIST",
    "custom_msg": "Allow Software Google's Team ID",
    "creation_time": 1576623399.151607
  }

Where rule_text evaluates to true, the policy applies.

For our use case of evaluating flags, an example might look like this:

  {
    "rule_text": "process.bundle_id == \"com.microsoft.VSCode\" && \"--inspect\" in process.flags",
    "rule_type": "ADVANCED",
    "policy": "BLOCKLIST",
    "custom_msg": "Block vscode from being launched with --inspect flag",
    "creation_time": 1576623399.151607
  }

Other information could be passed to the CEL library to help with evaluation - imagine having semantic versions compared within a rule.

Of course, these rules could become quite complicated, and use would probably be encouraged to be used when needed to reduce complexity.

@pmarkowsky
Copy link
Contributor

It's not a bad idea. But definitely will require a lot of design.

Thinking about it quickly using this approach for Advanced rules gets complicated when we have to figure out how we retrieve exactly the right rule for evaluation. Especially in the case when we'd have multiple advanced rules. One way to solve the precedence issue would be to take the number of times an event's attribute is evaluated but I think that'll also be complicated as we have to parse the AST to figure out which rules are the most specific.

Another thought off the cuff here is what if instead of adding a new rule type we instead extend rules to have an optional CEL program as an extension to an existing rule (in our case as yet another column in the rule column). Since you're almost always looking for additional dimensions of to match on this feels more like an operation that's anchored around the binary (e.g. I want to allow VSCode but not if it's using the inspect argument).

The idea would be that we'd start with our existing rule types and evaluation but would then run a CEL program as an extension of the rule that matches essentially something like.

This would make evaluation flow look like the following:

graph TD
A[event is received] --> B{event identifier matches a rule in DB?}
B --> |No| X[Use default rule for execution mode]
B --> |Yes| C{Does the rule have an associated CEL program?}
C --> |Yes| D{Eval Event Against CEL Program}
C --> |No| G[Return normal rule evaluation]
D --> |CEL program matches|E[Return Policy]
D --> |CEL program does not match|F[Return Opposite of Policy]

Both approaches would also require us to extend our caching to take fields passed to CEL engine like the command args into account.

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

No branches or pull requests

2 participants