...

Falco Default Rule Bypass

11 September 2020

Recently, when doing a bit of research of how Falco rules work, we discovered a default rule that alerts when privileged containers or containers that mount sensitive file paths are run inside a Kubernetes cluster could be “bypassed” if the image name was cleverly formatted.

What is Falco?

If you haven’t heard of Falco and how it monitors for malicious activity on a Linux host and inside Kubernetes clusters, head on over to the documentation. It’s a powerful, rules-based engine created by Sysdig and now a CNCF incubating project that can help you gain insight into actions that an attacker in a containerized environment will take, among a host (pun intended) of other useful things.

The Specific Rules

Launch Privileged Container

Prior to v0.25.0, the Launch Privileged Container rule looked like this:

- rule: Launch Privileged Container
  desc: Detect the initial process started in a privileged container. Exceptions are made for known trusted images.
  condition: >
    container_started and container     # Macros that identify this event as a newly started container
    and container.privileged=true       # This container has the privileged flag
    and not falco_privileged_containers # Not in the falco_privileged_containers allow-list
    and not user_privileged_containers  # Not in the user_privileged_containers allow-list
  output: Privileged container started (user=%user.name command=%proc.cmdline %container.info image=%container.image.repository:%container.image.tag)
  priority: INFO
  tags: [container, cis, mitre_privilege_escalation, mitre_lateral_movement]

Looking at the falco_privileged_containers macro which defines what is considered “allowed”:

- macro: falco_privileged_containers
  condition: (openshift_image or
              user_trusted_containers or
              container.image.repository in (trusted_images) or
              container.image.repository in (falco_privileged_images) or
              container.image.repository startswith istio/proxy_ or
              container.image.repository startswith quay.io/sysdig)

and looking at the user_privileged_containers macro:

- macro: user_privileged_containers
  condition: (container.image.repository endswith sysdig/agent)

We can see the potential loopholes for being able to bypass this rule are in the last lines of each macro:

...
              container.image.repository startswith quay.io/sysdig)

and

...
  condition: (container.image.repository endswith sysdig/agent)

Creating an organization with DockerHub and Quay or GCP Project Names (which become the GCR repository name) are free or very low cost. So, if one were to place images in one of these locations and ran it inside a cluster with the privileged: true set, they would avoid generating a Falco alert assuming they violated no other rules:

In the latest version, the macros are now:

- macro: falco_privileged_containers
  condition: (openshift_image or
              user_trusted_containers or
              container.image.repository in (trusted_images) or
              container.image.repository in (falco_privileged_images) or
              container.image.repository startswith istio/proxy_ or
              container.image.repository startswith quay.io/sysdig/) # <- Notice the trailing slash

and

- macro: user_privileged_containers
  condition: (never_true)

thereby closing the loophole.

Launch Sensitive Mount Container

Following a similar pattern, containers with crafted names could also be allowed to mount sensitive paths on the underlying host without triggering a Falco alert. Here’s the prior rule:

- rule: Launch Sensitive Mount Container
  desc: >
    Detect the initial process started by a container that has a mount from a sensitive host directory
    (i.e. /proc). Exceptions are made for known trusted images.
  condition: >
    container_started and container          # Macros that identify this event as a newly started container
    and sensitive_mount                      # Has a mount path like /, /etc, /var/run/docker.sock, etc
    and not falco_sensitive_mount_containers # A list of allowed containers for Falco-owned images
    and not user_sensitive_mount_containers  # A list for users to place their allow-list
  output: Container with sensitive mount started (user=%user.name command=%proc.cmdline %container.info image=%container.image.repository:%container.image.tag mounts=%container.mounts)
  priority: INFO
  tags: [container, cis, mitre_lateral_movement]

The sensitive_mount macro lists the most critical paths to be aware of in a containerized environment:

- macro: sensitive_mount
  condition: (container.mount.dest[/proc*] != "N/A" or
              container.mount.dest[/var/run/docker.sock] != "N/A" or
              container.mount.dest[/var/run/crio/crio.sock] != "N/A" or
              container.mount.dest[/var/lib/kubelet] != "N/A" or
              container.mount.dest[/var/lib/kubelet/pki] != "N/A" or
              container.mount.dest[/] != "N/A" or
              container.mount.dest[/home/admin] != "N/A" or
              container.mount.dest[/etc] != "N/A" or
              container.mount.dest[/etc/kubernetes] != "N/A" or
              container.mount.dest[/etc/kubernetes/manifests] != "N/A" or
              container.mount.dest[/root*] != "N/A")

Here’s the previous falco_sensitive_mount_containers macro that allows the same image name-based bypass:

- macro: falco_sensitive_mount_containers
  condition: (user_trusted_containers or
              container.image.repository in (trusted_images) or
              container.image.repository in (falco_sensitive_mount_images) or
              container.image.repository startswith quay.io/sysdig)

And the newest version includes the trailing / like so:

- macro: falco_sensitive_mount_containers
  condition: (user_trusted_containers or
              container.image.repository in (trusted_images) or
              container.image.repository in (falco_sensitive_mount_images) or
              container.image.repository startswith quay.io/sysdig/)

What Does This Mean?

If an attacker or malicious developer has the RBAC permissions to launch a pod and the cluster does not have Admission Control implemented to prevent arbitrary images from running in a cluster running Falco with unmodified rules, they could create a carefully named image repository, push an image of their choosing there, and run it inside the cluster without Falco generating an alert.

It’s important to understand that there is no vulnerability in Falco’s “engine” here. It’s a sneaky logic bug in the declaration of two default rules/macros that allow undesired image paths to also match. Upgrading Falco’s agent (although recommended for other reasons) isn’t required to address this issue. Instead, an update to the latest falco_rules.yaml is the relatively painless solution.

Conclusion

After upgrading your rules, check out the Falco Community repo and get involved. Thanks to the Falco team for all the hard work making such a useful piece of software, for making the responsible disclosure process straightforward to follow, and for the quick turn-around on the updated rules.