falco hips规则引擎——仅仅支持单一事件匹配,算子比较少,亮点是事件上报里包含取证。
我看了其所有的规则,都是单事件类的规则匹配,不含多事件的关联。官方文档里说了:Condition Syntax A condition is a boolean expression related to a single event ...单一事件。。。
规则清单:https://github.com/falcosecurity/falco/blob/master/rules/falco_rules.yaml
看几个典型的:
宏
- macro: open_write condition: evt.type in (open,openat,openat2) and evt.is_open_write=true and fd.typechar='f' and fd.num>=0 - macro: open_read condition: evt.type in (open,openat,openat2) and evt.is_open_read=true and fd.typechar='f' and fd.num>=0 - macro: open_directory condition: evt.type in (open,openat,openat2) and evt.is_open_read=true and fd.typechar='d' and fd.num>=0 - macro: never_true condition: (evt.num=0) - macro: always_true condition: (evt.num>=0)
- macro: rename condition: evt.type in (rename, renameat, renameat2) - macro: mkdir condition: evt.type in (mkdir, mkdirat) - macro: remove condition: evt.type in (rmdir, unlink, unlinkat)
list:
- list: shell_binaries items: [ash, bash, csh, ksh, sh, tcsh, zsh, dash] - list: ssh_binaries items: [ sshd, sftp-server, ssh-agent, ssh, scp, sftp, ssh-keygen, ssh-keysign, ssh-keyscan, ssh-add ] - list: shell_mgmt_binaries items: [add-shell, remove-shell] - macro: shell_procs condition: proc.name in (shell_binaries)
看一个规则:
- rule: Unexpected inbound connection source desc: Detect any inbound connection from a source outside of an allowed set of ips, networks, or domain names condition: > consider_all_inbound_conns and inbound and not ((fd.cip in (allowed_inbound_source_ipaddrs)) or (fd.cnet in (allowed_inbound_source_networks)) or (fd.cip.name in (allowed_inbound_source_domains))) output: Disallowed inbound connection source (command=%proc.cmdline connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id image=%container.image.repository) priority: NOTICE tags: [network]
参考:https://falco.org/docs/rules/
Rules
A Falco rules file is a YAML file containing three types of elements:
Element | Description |
---|---|
Rules | Conditions under which an alert should be generated. A rule is accompanied by a descriptive output string that is sent with the alert. |
Macros | Rule condition snippets that can be re-used inside rules and even other macros. Macros provide a way to name common patterns and factor out redundancies in rules.==》宏替换,本质上和C里同 |
Lists | Collections of items that can be included in rules, macros, or other lists. Unlike rules and macros, lists cannot be parsed as filtering expressions. |
宏替换这个说下例子:
- macro: rename condition: evt.type in (rename, renameat, renameat2) - macro: mkdir condition: evt.type in (mkdir, mkdirat) - macro: remove condition: evt.type in (rmdir, unlink, unlinkat) - macro: modify condition: rename or remove
看下规则里的:
- macro: ssh_port condition: fd.sport=22 # In a local/user rules file, you could override this macro to # enumerate the servers for which ssh connections are allowed. For # example, you might have a ssh gateway host for which ssh connections # are allowed. # # In the main falco rules file, there isn't any way to know the # specific hosts for which ssh access is allowed, so this macro just # repeats ssh_port, which effectively allows ssh from all hosts. In # the overridden macro, the condition would look something like # "fd.sip="a.b.c.d" or fd.sip="e.f.g.h" or ..." - macro: allowed_ssh_hosts condition: ssh_port - rule: Disallowed SSH Connection desc: Detect any new ssh connection to a host other than those in an allowed group of hosts condition: (inbound_outbound) and ssh_port and not allowed_ssh_hosts output: Disallowed SSH Connection (command=%proc.cmdline connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id image=%container.image.repository) priority: NOTICE tags: [network, mitre_remote_service]
- macro: inbound_outbound condition: > ((((evt.type in (accept,listen,connect) and evt.dir=<)) and (fd.typechar = 4 or fd.typechar = 6)) and (fd.ip != "0.0.0.0" and fd.net != "127.0.0.0/8") and (evt.rawres >= 0 or evt.res = EINPROGRESS))
可读性还是很高的:
- list: allowed_inbound_source_ipaddrs items: ['"127.0.0.1"'] - list: allowed_inbound_source_networks items: ['"127.0.0.1/8"', '"10.0.0.0/8"'] - list: allowed_inbound_source_domains items: [google.com] - rule: Unexpected inbound connection source desc: Detect any inbound connection from a source outside of an allowed set of ips, networks, or domain names condition: > consider_all_inbound_conns and inbound and not ((fd.cip in (allowed_inbound_source_ipaddrs)) or (fd.cnet in (allowed_inbound_source_networks)) or (fd.cip.name in (allowed_inbound_source_domains))) output: Disallowed inbound connection source (command=%proc.cmdline connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id image=%container.image.repository) priority: NOTICE tags: [network]
也会配合白名单进行误报过滤:
- rule: Schedule Cron Jobs desc: Detect cron jobs scheduled condition: > ((open_write and fd.name startswith /etc/cron) or (spawned_process and proc.name = "crontab")) and consider_all_cron_jobs and not user_known_cron_jobs output: > Cron jobs were scheduled to run (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline file=%fd.name container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag) priority: NOTICE tags: [file, mitre_persistence]
利用DB的无文件攻击:
- macro: user_known_db_spawned_processes condition: (never_true) - rule: DB program spawned process desc: > a database-server related program spawned a new process other than itself. This shouldn\'t occur and is a follow on from some SQL injection attacks. condition: > proc.pname in (db_server_binaries) and spawned_process and not proc.name in (db_server_binaries) and not postgres_running_wal_e and not user_known_db_spawned_processes output: > Database-related program spawned process other than itself (user=%user.name user_loginuid=%user.loginuid program=%proc.cmdline parent=%proc.pname container_id=%container.id image=%container.image.repository) priority: NOTICE tags: [process, database, mitre_execution]
非常规shell——无文件攻击:
- macro: parent_java_running_zookeeper condition: (proc.pname=java and proc.pcmdline contains org.apache.zookeeper.server) - macro: parent_java_running_kafka condition: (proc.pname=java and proc.pcmdline contains kafka.Kafka) - macro: parent_java_running_elasticsearch condition: (proc.pname=java and proc.pcmdline contains org.elasticsearch.bootstrap.Elasticsearch) - macro: parent_java_running_activemq condition: (proc.pname=java and proc.pcmdline contains activemq.jar) - macro: parent_java_running_cassandra condition: (proc.pname=java and (proc.pcmdline contains "-Dcassandra.config.loader" or proc.pcmdline contains org.apache.cassandra.service.CassandraDaemon)) - macro: parent_java_running_jboss_wildfly condition: (proc.pname=java and proc.pcmdline contains org.jboss) - macro: parent_java_running_glassfish condition: (proc.pname=java and proc.pcmdline contains com.sun.enterprise.glassfish) - macro: parent_java_running_hadoop condition: (proc.pname=java and proc.pcmdline contains org.apache.hadoop) - macro: parent_java_running_datastax condition: (proc.pname=java and proc.pcmdline contains com.datastax) - macro: nginx_starting_nginx condition: (proc.pname=nginx and proc.cmdline contains "/usr/sbin/nginx -c /etc/nginx/nginx.conf") - macro: nginx_running_aws_s3_cp condition: (proc.pname=nginx and proc.cmdline startswith "sh -c /usr/local/bin/aws s3 cp") - macro: consul_running_net_scripts condition: (proc.pname=consul and (proc.cmdline startswith "sh -c curl" or proc.cmdline startswith "sh -c nc")) - macro: consul_running_alert_checks condition: (proc.pname=consul and proc.cmdline startswith "sh -c /bin/consul-alerts") - macro: serf_script condition: (proc.cmdline startswith "sh -c serf") - macro: check_process_status condition: (proc.cmdline startswith "sh -c kill -0 ")
# We have to choose one of these cases, so we consider node processes # as unprotected by default. If you want to consider any node process # run in a container as a protected shell spawner, override the below # macro to remove the "never_true" clause, which allows it to take effect. - macro: possibly_node_in_container condition: (never_true and (proc.pname=node and proc.aname[3]=docker-containe)) # Similarly, you may want to consider any shell spawned by apache # tomcat as suspect. The famous apache struts attack (CVE-2017-5638) # could be exploited to do things like spawn shells. # # However, many applications *do* use tomcat to run arbitrary shells, # as a part of build pipelines, etc. # # Like for node, we make this case opt-in. - macro: possibly_parent_java_running_tomcat condition: (never_true and proc.pname=java and proc.pcmdline contains org.apache.catalina.startup.Bootstrap) - macro: protected_shell_spawner condition: > (proc.aname in (protected_shell_spawning_binaries) or parent_java_running_zookeeper or parent_java_running_kafka or parent_java_running_elasticsearch or parent_java_running_activemq or parent_java_running_cassandra or parent_java_running_jboss_wildfly or parent_java_running_glassfish or parent_java_running_hadoop or parent_java_running_datastax or possibly_parent_java_running_tomcat or possibly_node_in_container) - list: mesos_shell_binaries items: [mesos-docker-ex, mesos-slave, mesos-health-ch] # Note that runsv is both in protected_shell_spawner and the # exclusions by pname. This means that runsv can itself spawn shells # (the ./run and ./finish scripts), but the processes runsv can not # spawn shells. - rule: Run shell untrusted desc: an attempt to spawn a shell below a non-shell application. Specific applications are monitored. condition: > spawned_process and shell_procs and proc.pname exists and protected_shell_spawner and not proc.pname in (shell_binaries, gitlab_binaries, cron_binaries, user_known_shell_spawn_binaries, needrestart_binaries, mesos_shell_binaries, erl_child_setup, exechealthz, PM2, PassengerWatchd, c_rehash, svlogd, logrotate, hhvm, serf, lb-controller, nvidia-installe, runsv, statsite, erlexec, calico-node, "puma reactor") and not proc.cmdline in (known_shell_spawn_cmdlines) and not proc.aname in (unicorn_launche) and not consul_running_net_scripts and not consul_running_alert_checks and not nginx_starting_nginx and not nginx_running_aws_s3_cp and not run_by_package_mgmt_binaries and not serf_script and not check_process_status and not run_by_foreman and not python_mesos_marathon_scripting and not splunk_running_forwarder and not postgres_running_wal_e and not redis_running_prepost_scripts and not rabbitmq_running_scripts and not rabbitmqctl_running_scripts and not run_by_appdynamics and not user_shell_container_exclusions output: > Shell spawned by untrusted binary (user=%user.name user_loginuid=%user.loginuid shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline pcmdline=%proc.pcmdline gparent=%proc.aname[2] ggparent=%proc.aname[3] aname[4]=%proc.aname[4] aname[5]=%proc.aname[5] aname[6]=%proc.aname[6] aname[7]=%proc.aname[7] container_id=%container.id image=%container.image.repository) priority: DEBUG tags: [shell, mitre_execution]
端侧挖矿的检测,包括加密的:
- list: miner_ports items: [ 25, 3333, 3334, 3335, 3336, 3357, 4444, 5555, 5556, 5588, 5730, 6099, 6666, 7777, 7778, 8000, 8001, 8008, 8080, 8118, 8333, 8888, 8899, 9332, 9999, 14433, 14444, 45560, 45700 ] - list: miner_domains items: [ "asia1.ethpool.org","ca.minexmr.com", "cn.stratum.slushpool.com","de.minexmr.com", "eth-ar.dwarfpool.com","eth-asia.dwarfpool.com", "eth-asia1.nanopool.org","eth-au.dwarfpool.com", "eth-au1.nanopool.org","eth-br.dwarfpool.com", "eth-cn.dwarfpool.com","eth-cn2.dwarfpool.com", "eth-eu.dwarfpool.com","eth-eu1.nanopool.org", "eth-eu2.nanopool.org","eth-hk.dwarfpool.com", "eth-jp1.nanopool.org","eth-ru.dwarfpool.com", "eth-ru2.dwarfpool.com","eth-sg.dwarfpool.com", "eth-us-east1.nanopool.org","eth-us-west1.nanopool.org", "eth-us.dwarfpool.com","eth-us2.dwarfpool.com", "eu.stratum.slushpool.com","eu1.ethermine.org", "eu1.ethpool.org","fr.minexmr.com", "mine.moneropool.com","mine.xmrpool.net", "pool.minexmr.com","pool.monero.hashvault.pro", "pool.supportxmr.com","sg.minexmr.com", "sg.stratum.slushpool.com","stratum-eth.antpool.com", "stratum-ltc.antpool.com","stratum-zec.antpool.com", "stratum.antpool.com","us-east.stratum.slushpool.com", "us1.ethermine.org","us1.ethpool.org", "us2.ethermine.org","us2.ethpool.org", "xmr-asia1.nanopool.org","xmr-au1.nanopool.org", "xmr-eu1.nanopool.org","xmr-eu2.nanopool.org", "xmr-jp1.nanopool.org","xmr-us-east1.nanopool.org", "xmr-us-west1.nanopool.org","xmr.crypto-pool.fr", "xmr.pool.minergate.com", "rx.unmineable.com", "ss.antpool.com","dash.antpool.com", "eth.antpool.com","zec.antpool.com", "xmc.antpool.com","btm.antpool.com", "stratum-dash.antpool.com","stratum-xmc.antpool.com", "stratum-btm.antpool.com" ] - list: https_miner_domains items: [ "ca.minexmr.com", "cn.stratum.slushpool.com", "de.minexmr.com", "fr.minexmr.com", "mine.moneropool.com", "mine.xmrpool.net", "pool.minexmr.com", "sg.minexmr.com", "stratum-eth.antpool.com", "stratum-ltc.antpool.com", "stratum-zec.antpool.com", "stratum.antpool.com", "xmr.crypto-pool.fr", "ss.antpool.com", "stratum-dash.antpool.com", "stratum-xmc.antpool.com", "stratum-btm.antpool.com", "btm.antpool.com" ] - list: http_miner_domains items: [ "ca.minexmr.com", "de.minexmr.com", "fr.minexmr.com", "mine.moneropool.com", "mine.xmrpool.net", "pool.minexmr.com", "sg.minexmr.com", "xmr.crypto-pool.fr" ] # Add rule based on crypto mining IOCs - macro: minerpool_https condition: (fd.sport="443" and fd.sip.name in (https_miner_domains)) - macro: minerpool_http condition: (fd.sport="80" and fd.sip.name in (http_miner_domains)) - macro: minerpool_other condition: (fd.sport in (miner_ports) and fd.sip.name in (miner_domains)) - macro: net_miner_pool condition: (evt.type in (sendto, sendmsg, connect) and evt.dir=< and (fd.net != "127.0.0.0/8" and not fd.snet in (rfc_1918_addresses)) and ((minerpool_http) or (minerpool_https) or (minerpool_other))) - macro: trusted_images_query_miner_domain_dns condition: (container.image.repository in (docker.io/falcosecurity/falco, falcosecurity/falco, public.ecr.aws/falcosecurity/falco)) # The rule is disabled by default. # Note: falco will send DNS request to resolve miner pool domain which may trigger alerts in your environment. - rule: Detect outbound connections to common miner pool ports desc: Miners typically connect to miner pools on common ports. condition: net_miner_pool and not trusted_images_query_miner_domain_dns enabled: false output: Outbound connection to IP/Port flagged by https://cryptoioc.ch (command=%proc.cmdline port=%fd.rport ip=%fd.rip container=%container.info image=%container.image.repository) priority: CRITICAL tags: [network, mitre_execution] - rule: Detect crypto miners using the Stratum protocol desc: Miners typically specify the mining pool to connect to with a URI that begins with 'stratum+tcp' condition: spawned_process and (proc.cmdline contains "stratum+tcp" or proc.cmdline contains "stratum2+tcp" or proc.cmdline contains "stratum+ssl" or proc.cmdline contains "stratum2+ssl") output: Possible miner running (command=%proc.cmdline container=%container.info image=%container.image.repository) priority: CRITICAL tags: [process, mitre_execution]
内核注入检测:
- list: white_listed_modules items: [] - rule: Linux Kernel Module Injection Detected desc: Detect kernel module was injected (from container). condition: spawned_process and container and proc.name=insmod and not proc.args in (white_listed_modules) output: Linux Kernel Module injection using insmod detected (user=%user.name user_loginuid=%user.loginuid parent_process=%proc.pname module=%proc.args %container.info image=%container.image.repository:%container.image.tag) priority: WARNING tags: [process]
Rules
A Falco rule is a node containing the following keys:
Key | Required | Description | Default |
---|---|---|---|
rule |
yes | A short, unique name for the rule. | |
condition |
yes | A filtering expression that is applied against events to check whether they match the rule. | |
desc |
yes | A longer description of what the rule detects. | |
output |
yes | Specifies the message that should be output if a matching event occurs. See output. | |
priority |
yes | A case-insensitive representation of the severity of the event. Should be one of the following: emergency , alert , critical , error , warning , notice , informational , debug . |
|
exceptions |
no | A set of exceptions that cause the rule to not generate an alert. | |
enabled |
no | If set to false , a rule is neither loaded nor matched against any events. |
true |
tags |
no | A list of tags applied to the rule (more on this below). | |
warn_evttypes |
no | If set to false , Falco suppresses warnings related to a rule not having an event type (more on this below). |
true |
skip-if-unknown-filter |
no | If set to true , if a rule conditions contains a filtercheck, e.g. fd.some_new_field , that is not known to this version of Falco, Falco silently accepts the rule but does not execute it; if set to false , Falco repots an error and exists when finding an unknown filtercheck. |
false |
source |
no | The event source for which this rule should be evaluated. Typical values are syscall , k8s_audit , or the source advertised by a source plugin. |
syscall |
Conditions
The key part of a rule is the condition field. A condition is a Boolean predicate expressed using the condition syntax. It is possible to express conditions on all supported events using their respective supported fields.
Here's an example of a condition that alerts whenever a bash shell is run inside a container:
container.id != host and proc.name = bash
The first clause checks that the event happened in a container (where container.id
is equal to "host"
if the event happened on a regular host). The second clause checks that the process name is bash
. Since this condition does not include a clause with a system call it will only check event metadata. Because of that, if a bash shell does start up in a container, Falco outputs events for every syscall that is performed by that shell.
If you want to be alerted only for each successful spawn of a shell in a container, add the appropriate event type and direction to the condition:
evt.type = execve and evt.dir=< and container.id != host and proc.name = bash
Therefore, a complete rule using the above condition might be:
Conditions allow you to check for many aspects of each supported event. To learn more, see the condition language.
我们重点看下其条件语法的表达:
----------------------------------------------
Condition Syntax
A condition is a boolean expression related to a single event that has been detected by Falco. You can use fields related to every supported event, but this document focuses on syscalls as they're currently the most common. The language supports boolean operators and parentheses as you'd expect. For example a condition like:
evt.type = execve and evt.dir = < and (proc.name = cat or proc.name = grep)
This will trigger for each execution of cat
or grep
. Below you can take a closer look at what is the meaning of those fields such as evt.dir
, how to discover the types of events available, and which fields are present with which event type.
Syscall event types, direction and args
Every syscall event will present the evt
field class. Each condition that you write for those events will normally start with a evt.type
expression or macro; this makes a lot of sense since security rules normally consider one syscall type at a time. For instance, you may want to consider open
or openat
to catch suspicious activity when opening files, execve
to inspect spawned processes, and so forth. You don't have to guess the syscall name, as you can see the complete list of supported system calls events and understand which ones you can use.
Every syscall has an entry event and an exit event which is shown in the evt.dir
field, also known as the "direction" of the system call. A value of >
indicates entry, which fires when the syscall is invoked, while <
marks exit, meaning that the call has returned. In fact, by looking at the supported system call list you can see that our events have both entries. For example:
> setuid(UID uid)
< setuid(ERRNO res)
Most of the time the engine informs you about exit events as you want to understand what happened after the event execution is complete. You can see by using the events associated with opening files.
> open()
< open(FD fd, FSPATH name, FLAGS32 flags, UINT32 mode, UINT32 dev)
> openat()
< openat(FD fd, FD dirfd, FSRELPATH name, FLAGS32 flags, UINT32 mode, UINT32 dev)
As you can see, each event has a list of arguments associated with it that you can access by using evt.arg.<argname>
. For example, to identify when a process opens a file to overwrite it, you need to check if the list of flags contains O_TRUNC
. You can use the evt.arg.flags
field of the open
and openat
exit events shown above and the rule will then look like this:
evt.type in (open, openat) and evt.dir = < and evt.arg.flags contains O_TRUNC
Note that the arguments do not necessarily match the raw parameters that are used in the Linux kernel, but are parsed by Falco to make it easier to write rules. By using the evt
fields we can inspect many more aspects that are common across events.
Syscall event context and metadata
While the evt
fields allow you to write pretty expressive conditions, arguments and common fields are usually not enough to write full security rules. Many times you want to add conditions based on the process context the event happens in, or whether or not something is happening inside a container or even correlate each event with the relevant Kubernetes metadata for the cluster, pods, and more. For this reason, Falco enriches many events with other field classes. Not all the classes are available for all the events and the list can grow. The documentation for each clarifies when those are expected to be available, but some are so common that you often rely on them.
The proc
field class gives you the context about the process and thread that is generating a specific syscall. This information is usually very important. The most basic piece of information you can get out of it is proc.name
and proc.pid
, but you can even traverse the process hierarchy by using proc.aname[<n>]
and proc.apid[<n>]
. Likewise, you may be interested in which user performs a specific action via the user
field class.
The documentation gives you an example of how to catch executions of bash
within containers:
if evt.type = execve and evt.dir = < and container.id != host and proc.name = bash
Note that you don't even have to look at the execve
args. That is because once execve
has returned the process context recorded by Falco is updated, meaning that the proc.
fields will already refer to all information, including the command line, executable, arguments, related to the new process that was just spawned.
Operators
You can use the following operators in conditions:
Operators | Description |
---|---|
= , != |
Equality and inequality operators. |
<= , < , >= , > |
Comparison operators for numeric values. |
contains , icontains |
For strings will evaluate to true if a string contains another, and icontains is the case insensitive version. For flags it will evaluate to true if the flag is set. Examples: proc.cmdline contains "-jar" , evt.arg.flags contains O_TRUNC . |
startswith , endswith |
Check prefix or suffix of strings. |
glob |
Evaluate standard glob patterns. Example: fd.name glob "/home/*/.ssh/*" . |
in , intersects |
Set operations. |
pmatch |
Compare a file path against a set of file or directory prefixes. Example: fd.name pmatch (/tmp/hello) will evaluate to true against /tmp/hello , /tmp/hello/world but not /tmp/hello_world . |
exists |
Check if a field is set. Example: k8s.pod.name exists . |
看其支持的算子也是比较单一。
----------------------------------------------
Macros
As noted above, macros provide a way to define common sub-portions of rules in a reusable way. By looking at the condition above it looks like both evt.type = execve and evt.dir=<
and container.id != host
would be used many by other rules, so to make our job easier we can easily define macros for both:
With this macro defined, we can then rewrite the above rule's condition as spawned_process and container and proc.name = bash
.
For many more examples of rules and macros, take a look the documentation on default macros and the rules/falco_rules.yaml
file. In fact, both the macros above are part of the default list!
Lists
Lists are named collections of items that you can include in rules, macros, or even other lists. Please note that lists cannot be parsed as filtering expressions. Each list node has the following keys:
Key | Description |
---|---|
list |
The unique name for the list (as a slug) |
items |
The list of values |
Here are some example lists as well as a macro that uses them:
Referring to a list inserts the list items in the macro, rule, or list.
Lists can contain other lists.
Appending to Lists, Rules, and Macros
If you use multiple Falco rules files, you might want to append new items to an existing list, rule, or macro. To do that, define an item with the same name as an existing item and add an append: true
attribute to the list. When appending lists, items are added to the end of the list. When appending rules/macros, the additional text is appended to the condition: field of the rule/macro.
Note that when appending to lists, rules or macros, the order of the rule configuration files matters! For example if you append to an existing default rule (e.g. Terminal shell in container
), you must ensure your custom configuration file (e.g. /etc/falco/rules.d/custom-rules.yaml
) is loaded after the default configuration file (/etc/falco/falco_rules.yaml
). This can be configured with multiple -r
parameters in the right order, directly inside the falco configuration file (falco.yaml
) via rules_file
or if you use the official Helm chart, via the falco.rulesFile
value.
Examples
In all of the examples below, it's assumed one is running Falco via falco -r /etc/falco/falco_rules.yaml -r /etc/falco/falco_rules.local.yaml
, or has the default entries for rules_file
in falco.yaml, which has /etc/falco/falco.yaml
first and /etc/falco/falco_rules.local.yaml
second.
Appending to lists
Here's an example of appending to lists:
/etc/falco/falco_rules.yaml
/etc/falco/falco_rules.local.yaml
The rule my_programs_opened_file
would trigger whenever any of ls
, cat
, pwd
, or cp
opened a file.
Appending to Macros
Here's an example of appending to macros:
/etc/falco/falco_rules.yaml
/etc/falco/falco_rules.local.yaml
The rule program_accesses_file
would trigger when ls
/cat
either used open
/openat
on a file.
Appending to Rules
Here's an example of appending to rules:
/etc/falco/falco_rules.yaml
/etc/falco/falco_rules.local.yaml
The rule program_accesses_file
would trigger when ls
/cat
either used open
on a file, but not if the user was root.
Gotchas with rule/macro append and logical operators
Remember that when appending rules and macros, the text of the second rule/macro is simply added to the condition of the first rule/macro. This can result in unintended results if the original rule/macro has potentially ambiguous logical operators. Here's an example:
Should proc.name=nginx
be interpreted as relative to the and proc.name=apache
, that is to allow either apache/nginx to open files, or relative to the evt.type=open
, that is to allow apache to open files or to allow nginx to do anything?
In cases like this, be sure to scope the logical operators of the original condition with parentheses when possible, or avoid appending conditions when not possible.
Disable Default Rules
Even though Falco provides a quite powerful default ruleset, you sometimes need to disable some of these default rules since they do not work properly in your environment. Luckily Falco offers you multiple possibilities to do so.
Via existing Macros
Most of the default rules offer some kind of consider_*
macros which are already part of the rule conditions. These consider_*
macros are usually set to (never_true)
or (always_true)
which basically enables or disabled the regarding rule. Now if you want to enable a by default disabled rule (e.g. Unexpected outbound connection destination
), you just have to override the rule's consider_*
macro (consider_all_outbound_conns
in this case) inside your custom Falco configuration.
Example for your custom Falco configuration (note the (always_true)
condition):
Please note again that the order of the specified configuration file matters! The last defined macro with the same name wins.
Via Falco Parameters
Falco offers the following parameters to limit which default rules should be enabled/used and which not:
These parameters can also be specified as Helm chart value (extraArgs
) if you are deploying Falco via the official Helm chart.
Via Custom Rule Definition
Last but not the least, you can just disable a rule that is enabled by default using the enabled: false
rule property. This is especially useful for rules which do not provide a consider_*
macro in the default condition.
Ensure that the custom configuration file loads after the default configuration file. You can configure the right order using multiple -r
parameters, directly inside the falco configuration file falco.yaml
through rules_file
. If you are using the official Helm chart, then configure the order with the falco.rulesFile
value.
For example to disable the User mgmt binaries
default rule in /etc/falco/falco_rules.yaml
define a custom rule in /etc/falco/rules.d/custom-rules.yaml
:
At the same time, disabled rules can be re-enabled by using the enabled: true
rule property. For instance, the Change thread namespace
rule in /etc/falco/falco_rules.yaml
that is disabled by default, can be manually enabled with:
Output
A rule output is a string that can use the same fields that conditions can use prepended by %
to perform interpolation, akin to printf
. For example:
Disallowed SSH Connection (command=%proc.cmdline connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id image=%container.image.repository)
could output:
Disallowed SSH Connection (command=sshd connection=127.0.0.1:34705->10.0.0.120:22 user=root user_loginuid=-1 container_id=host image=<NA>)
Note that it's not necessary that all fields are set in the specific event. As you can see in the example above if the connection happens outside a container the field %container.image.repository
would not be set and <NA>
is displayed instead.
Rule Priorities
Every Falco rule has a priority which indicates how serious a violation of the rule is. The priority is included in the message/JSON output/etc. Here are the available priorities:
EMERGENCY
ALERT
CRITICAL
ERROR
WARNING
NOTICE
INFORMATIONAL
DEBUG
The general guidelines used to assign priorities to rules are the following:
- If a rule is related to writing state (i.e. filesystem, etc.), its priority is
ERROR
. - If a rule is related to an unauthorized read of state (i.e. reading sensitive files, etc.), its priority is
WARNING
. - If a rule is related to unexpected behavior (spawning an unexpected shell in a container, opening an unexpected network connection, etc.), its priority is
NOTICE
. - If a rule is related to behaving against good practices (unexpected privileged containers, containers with sensitive mounts, running interactive commands as root), its priority is
INFO
.
One exception is that the rule "Run shell untrusted", which is fairly FP-prone, has a priority of DEBUG
.
Rule Tags
As of 0.6.0, rules have an optional set of tags that are used to categorize the ruleset into groups of related rules. Here's an example:
In this case, the rule "File Open by Privileged Container" has been given the tags "container" and "cis". If the tags key is not present for a given rule or the list is empty, a rule has no tags.
Here's how you can use tags:
- You can use the
-T <tag>
argument to disable rules having a given tag.-T
can be specified multiple times. For example, to skip all rules with the "filesystem" and "cis" tags you would run falco withfalco -T filesystem -T cis ...
.-T
can not be specified with-t
. - You can use the
-t <tag>
argument to only run those rules having a given tag.-t
can be specified multiple times. For example, to only run those rules with the "filesystem" and "cis" tags, you would run falco withfalco -t filesystem -t cis ...
.-t
can not be specified with-T
or-D <pattern>
(disable rules by rule name regex).
Tags for Current Falco Ruleset
We've also gone through the default ruleset and tagged all the rules with an initial set of tags. Here are the tags we've used:
Tag | Description |
---|---|
filesystem |
The rule relates to reading/writing files |
software_mgmt |
The rule relates to any software/package management tool like rpm, dpkg, etc. |
process |
The rule relates to starting a new process or changing the state of a current process |
database |
The rule relates to databases |
host |
The rule only works outside of containers |
shell |
The rule specifically relates to starting shells |
container |
The rule only works inside containers |
cis |
The rule is related to the CIS Docker benchmark |
users |
The rule relates to management of users or changing the identity of a running process |
network |
The rule relates to network activity |
Rules can have multiple tags if they relate to multiple of the above. Every rule in the falco ruleset currently has at least one tag.
Rule Condition Best Practices
To allow for grouping rules by event type, which improves performance, Falco prefers rule conditions that have at least one evt.type=
operator, at the beginning of the condition, before any negative operators (i.e. not
or !=
). If a condition does not have any evt.type=
operator, Falco logs a warning like:
Rule no_evttype: warning (no-evttype):
proc.name=foo
did not contain any evt.type restriction, meaning that it will run for all event types. ==》最好按照事件类型过滤匹配
This has a significant performance penalty. Consider adding an evt.type restriction if possible.
If a rule has an evt.type
operator in the latter portion of the condition, Falco logs a warning like this:
Rule evttype_not_equals: warning (trailing-evttype):
evt.type!=execve
does not have all evt.type restrictions at the beginning of the condition,
or uses a negative match (i.e. "not"/"!=") for some evt.type restriction.
This has a performance penalty, as the rule can not be limited to specific event types.
Consider moving all evt.type restrictions to the beginning of the rule and/or
replacing negative matches with positive matches if possible.
Escaping Special Characters
In some cases, rules may need to contain special characters like (
, spaces, etc. For example, you may need to look for a proc.name
of (systemd)
, including the surrounding parentheses.
You can use "
to capture these special characters. Here's an example:
When including items in lists, ensure that the double quotes are not interpreted from your YAML file by surrounding the quoted string with single quotes. Here's an example: