diff --git a/rules/falco_rules.yaml b/rules/falco_rules.yaml index 9d954e33d83..77654eb5893 100644 --- a/rules/falco_rules.yaml +++ b/rules/falco_rules.yaml @@ -63,76 +63,76 @@ - macro: linux_so_dirs condition: ubuntu_so_dirs or centos_so_dirs or fd.name=/etc/ld.so.cache -- macro: coreutils_binaries - condition: > - proc.name in (truncate, sha1sum, numfmt, fmt, fold, uniq, cut, who, +- list: coreutils_binaries + items: [ + truncate, sha1sum, numfmt, fmt, fold, uniq, cut, who, groups, csplit, sort, expand, printf, printenv, unlink, tee, chcon, stat, - basename, split, nice, yes, whoami, sha224sum, hostid, users, stdbuf, + basename, split, nice, "yes", whoami, sha224sum, hostid, users, stdbuf, base64, unexpand, cksum, od, paste, nproc, pathchk, sha256sum, wc, test, comm, arch, du, factor, sha512sum, md5sum, tr, runcon, env, dirname, tsort, join, shuf, install, logname, pinky, nohup, expr, pr, tty, timeout, - tail, [, seq, sha384sum, nl, head, id, mkfifo, sum, dircolors, ptx, shred, - tac, link, chroot, vdir, chown, touch, ls, dd, uname, true, pwd, date, - chgrp, chmod, mktemp, cat, mknod, sync, ln, false, rm, mv, cp, echo, - readlink, sleep, stty, mkdir, df, dir, rmdir, touch) + tail, "[", seq, sha384sum, nl, head, id, mkfifo, sum, dircolors, ptx, shred, + tac, link, chroot, vdir, chown, touch, ls, dd, uname, "true", pwd, date, + chgrp, chmod, mktemp, cat, mknod, sync, ln, "false", rm, mv, cp, echo, + readlink, sleep, stty, mkdir, df, dir, rmdir, touch + ] # dpkg -L login | grep bin | xargs ls -ld | grep -v '^d' | awk '{print $9}' | xargs -L 1 basename | tr "\\n" "," -- macro: login_binaries - condition: proc.name in (login, systemd-logind, su, nologin, faillog, lastlog, newgrp, sg) +- list: login_binaries + items: [login, systemd-logind, su, nologin, faillog, lastlog, newgrp, sg] # dpkg -L passwd | grep bin | xargs ls -ld | grep -v '^d' | awk '{print $9}' | xargs -L 1 basename | tr "\\n" "," -- macro: passwd_binaries - condition: > - proc.name in (shadowconfig, grpck, pwunconv, grpconv, pwck, +- list: passwd_binaries + items: [ + shadowconfig, grpck, pwunconv, grpconv, pwck, groupmod, vipw, pwconv, useradd, newusers, cppw, chpasswd, usermod, groupadd, groupdel, grpunconv, chgpasswd, userdel, chage, chsh, - gpasswd, chfn, expiry, passwd, vigr, cpgr) + gpasswd, chfn, expiry, passwd, vigr, cpgr + ] # repoquery -l shadow-utils | grep bin | xargs ls -ld | grep -v '^d' | awk '{print $9}' | xargs -L 1 basename | tr "\\n" "," -- macro: shadowutils_binaries - condition: > - proc.name in (chage, gpasswd, lastlog, newgrp, sg, adduser, deluser, chpasswd, +- list: shadowutils_binaries + items: [ + chage, gpasswd, lastlog, newgrp, sg, adduser, deluser, chpasswd, groupadd, groupdel, addgroup, delgroup, groupmems, groupmod, grpck, grpconv, grpunconv, - newusers, pwck, pwconv, pwunconv, useradd, userdel, usermod, vigr, vipw, unix_chkpwd) + newusers, pwck, pwconv, pwunconv, useradd, userdel, usermod, vigr, vipw, unix_chkpwd + ] -- macro: sysdigcloud_binaries - condition: proc.name in (setup-backend, dragent) +- list: sysdigcloud_binaries + items: [setup-backend, dragent] -- macro: sysdigcloud_binaries_parent - condition: proc.pname in (setup-backend, dragent) +- list: docker_binaries + items: [docker, exe] -- macro: docker_binaries - condition: proc.name in (docker, exe) +- list: http_server_binaries + items: [nginx, httpd, httpd-foregroun, lighttpd] -- macro: http_server_binaries - condition: proc.name in (nginx, httpd, httpd-foregroun, lighttpd) +- list: db_server_binaries + items: [mysqld] -- macro: db_server_binaries - condition: proc.name in (mysqld) - -- macro: db_server_binaries_parent - condition: proc.pname in (mysqld) - -- macro: server_binaries - condition: (http_server_binaries or db_server_binaries or docker_binaries or proc.name in (sshd)) +- macro: server_procs + condition: proc.name in (http_server_binaries, db_server_binaries, docker_binaries, sshd) # The truncated dpkg-preconfigu is intentional, process names are # truncated at the sysdig level. -- macro: package_mgmt_binaries - condition: proc.name in (dpkg, dpkg-preconfigu, rpm, rpmkey, yum) +- list: package_mgmt_binaries + items: [dpkg, dpkg-preconfigu, rpm, rpmkey, yum] + +- macro: package_mgmt_procs + condition: proc.name in (package_mgmt_binaries) # A canonical set of processes that run other programs with different # privileges or as a different user. -- macro: userexec_binaries - condition: proc.name in (sudo, su) +- list: userexec_binaries + items: [sudo, su] -- macro: user_mgmt_binaries - condition: (login_binaries or passwd_binaries or shadowutils_binaries) +- list: user_mgmt_binaries + items: [login_binaries, passwd_binaries, shadowutils_binaries] -- macro: system_binaries - condition: (coreutils_binaries or user_mgmt_binaries) +- macro: system_procs + condition: proc.name in (coreutils_binaries, user_mgmt_binaries) -- macro: mail_binaries +- macro: mail_procs condition: proc.name in (sendmail, sendmail-msp, postfix, procmail) - macro: sensitive_files @@ -167,10 +167,8 @@ condition: ((proc.aname=sshd and proc.name != sshd) or proc.name=systemd-logind) - macro: syslog condition: fd.name in (/dev/log, /run/systemd/journal/syslog) -- macro: cron - condition: proc.name in (cron, crond) -- macro: parent_cron - condition: proc.pname in (cron, crond) +- list: cron_binaries + items: [cron, crond] # System users that should never log into a system. Consider adding your own # service users (e.g. 'apache' or 'mysqld') here. @@ -184,32 +182,32 @@ - rule: write_binary_dir desc: an attempt to write to any file below a set of binary directories - condition: evt.dir = < and open_write and not package_mgmt_binaries and bin_dir + condition: evt.dir = < and open_write and not package_mgmt_procs and bin_dir output: "File below a known binary directory opened for writing (user=%user.name command=%proc.cmdline file=%fd.name)" priority: WARNING - rule: write_etc desc: an attempt to write to any file below /etc, not in a pipe installer session - condition: evt.dir = < and open_write and not shadowutils_binaries and not sysdigcloud_binaries_parent and not package_mgmt_binaries and etc_dir and not proc.sname=fbash + condition: evt.dir = < and open_write and not proc.name in (shadowutils_binaries, sysdigcloud_binaries, package_mgmt_binaries) and etc_dir and not proc.pname in (sysdigcloud_binaries) and not proc.sname=fbash output: "File below /etc opened for writing (user=%user.name command=%proc.cmdline file=%fd.name)" priority: WARNING # Within a fbash session, the severity is lowered to INFO - rule: write_etc_installer desc: an attempt to write to any file below /etc, in a pipe installer session - condition: evt.dir = < and open_write and not shadowutils_binaries and not sysdigcloud_binaries_parent and not package_mgmt_binaries and etc_dir and proc.sname=fbash + condition: evt.dir = < and open_write and not proc.name in (shadowutils_binaries, sysdigcloud_binaries, package_mgmt_binaries) and etc_dir and not proc.pname in (sysdigcloud_binaries) and proc.sname=fbash output: "File below /etc opened for writing (user=%user.name command=%proc.cmdline file=%fd.name) within pipe installer session" priority: INFO - rule: read_sensitive_file_untrusted desc: an attempt to read any sensitive file (e.g. files containing user/password/authentication information). Exceptions are made for known trusted programs. - condition: open_read and not user_mgmt_binaries and not userexec_binaries and not proc.name in (iptables, ps, lsb_release, check-new-relea, dumpe2fs, accounts-daemon, bash, sshd) and not cron and sensitive_files + condition: open_read and not proc.name in (user_mgmt_binaries, userexec_binaries, cron_binaries, iptables, ps, lsb_release, check-new-relea, dumpe2fs, accounts-daemon, bash, sshd) and sensitive_files output: "Sensitive file opened for reading by non-trusted program (user=%user.name command=%proc.cmdline file=%fd.name)" priority: WARNING - 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. - condition: open_read and server_binaries and not proc_is_new and sensitive_files and proc.name!="sshd" + condition: open_read and server_procs and not proc_is_new and sensitive_files and proc.name!="sshd" output: "Sensitive file opened for reading by trusted program after startup (user=%user.name command=%proc.cmdline file=%fd.name)" priority: WARNING @@ -222,19 +220,19 @@ - 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: db_server_binaries_parent and not db_server_binaries and spawned_process + condition: proc.pname in (db_server_binaries) and not proc.name in (db_server_binaries) and spawned_process output: "Database-related program spawned process other than itself (user=%user.name program=%proc.cmdline parent=%proc.pname)" priority: WARNING - rule: modify_binary_dirs desc: an attempt to modify any file below a set of binary directories. - condition: modify and bin_dir_rename and not package_mgmt_binaries + condition: modify and bin_dir_rename and not package_mgmt_procs output: "File below known binary directory renamed/removed (user=%user.name command=%proc.cmdline operation=%evt.type file=%fd.name %evt.args)" priority: WARNING - rule: mkdir_binary_dirs desc: an attempt to create a directory below a set of binary directories. - condition: mkdir and bin_dir_mkdir and not package_mgmt_binaries + condition: mkdir and bin_dir_mkdir and not package_mgmt_procs output: "Directory below known binary directory created (user=%user.name command=%proc.cmdline directory=%evt.arg.path)" priority: WARNING @@ -262,7 +260,7 @@ - rule: run_shell_untrusted desc: an attempt to spawn a shell by a non-shell program. Exceptions are made for trusted binaries. - condition: not container and proc.name = bash and spawned_process and proc.pname exists and not parent_cron and not proc.pname in (bash, sshd, sudo, docker, su, tmux, screen, emacs, systemd, login, flock, fbash, nginx, monit, supervisord, dragent) + condition: not container and proc.name = bash and spawned_process and proc.pname exists and not proc.pname in (cron_binaries, bash, sshd, sudo, docker, su, tmux, screen, emacs, systemd, login, flock, fbash, nginx, monit, supervisord, dragent) output: "Shell spawned by untrusted binary (user=%user.name shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)" priority: WARNING @@ -284,9 +282,9 @@ priority: WARNING # sockfamily ip is to exclude certain processes (like 'groups') that communicate on unix-domain sockets -- rule: system_binaries_network_activity +- rule: system_procs_network_activity desc: any network activity performed by system binaries that are not expected to send or receive any network traffic - condition: (inbound or outbound) and (fd.sockfamily = ip and system_binaries) + condition: (inbound or outbound) and (fd.sockfamily = ip and system_procs) output: "Known system binary sent/received network traffic (user=%user.name command=%proc.cmdline connection=%fd.name)" priority: WARNING @@ -302,13 +300,13 @@ # sshd, sendmail-msp, sendmail attempt to setuid to root even when running as non-root. Excluding here to avoid meaningless FPs - rule: non_sudo_setuid desc: an attempt to change users by calling setuid. sudo/su are excluded. user "root" is also excluded, as setuid calls typically involve dropping privileges. - condition: evt.type=setuid and evt.dir=> and not user.name=root and not userexec_binaries and not proc.name in (sshd, sendmail-msp, sendmail) + condition: evt.type=setuid and evt.dir=> and not user.name=root and not proc.name in (userexec_binaries, sshd, sendmail-msp, sendmail) output: "Unexpected setuid call by non-sudo, non-root program (user=%user.name command=%proc.cmdline uid=%evt.arg.uid)" priority: WARNING - rule: user_mgmt_binaries desc: activity by any programs that can manage users, passwords, or permissions. sudo and su are excluded. Activity in containers is also excluded--some containers create custom users on top of a base linux distribution at startup. - condition: spawned_process and not proc.name in (su, sudo) and not container and user_mgmt_binaries and not parent_cron and not proc.pname in (systemd, run-parts) + condition: spawned_process and not proc.name in (su, sudo) and not container and proc.name in (user_mgmt_binaries) and not proc.pname in (cron_binaries, systemd, run-parts) output: "User management binary command run outside of container (user=%user.name command=%proc.cmdline parent=%proc.pname)" priority: WARNING @@ -356,7 +354,7 @@ # as a part of doing the installation - rule: installer_bash_runs_pkgmgmt desc: an attempt by a program in a pipe installer session to run a package management binary - condition: evt.type=execve and package_mgmt_binaries and proc.sname=fbash + condition: evt.type=execve and package_mgmt_procs and proc.sname=fbash output: "Package management program run by process in a fbash session (command=%proc.cmdline)" priority: INFO @@ -525,6 +523,6 @@ # - rule: http_server_unexpected_network_inbound # desc: inbound network traffic to a http server program on a port other than the standard ports -# condition: http_server_binaries and inbound and fd.sport != 80 and fd.sport != 443 +# condition: proc.name in (http_server_binaries) and inbound and fd.sport != 80 and fd.sport != 443 # output: "Inbound network traffic to HTTP Server on unexpected port (connection=%fd.name)" # priority: WARNING diff --git a/userspace/falco/lua/compiler.lua b/userspace/falco/lua/compiler.lua index 0e809dd66cd..df88e7fb9b3 100644 --- a/userspace/falco/lua/compiler.lua +++ b/userspace/falco/lua/compiler.lua @@ -156,7 +156,12 @@ function check_for_ignored_syscalls_events(ast, filter_type, source) parser.traverse_ast(ast, "BinaryRelOp", cb) end -function compiler.compile_macro(line) +function compiler.compile_macro(line, list_defs) + + for name, items in pairs(list_defs) do + line = string.gsub(line, name, table.concat(items, ", ")) + end + local ast, error_msg = parser.parse_filter(line) if (error_msg) then @@ -174,7 +179,12 @@ end --[[ Parses a single filter, then expands macros using passed-in table of definitions. Returns resulting AST. --]] -function compiler.compile_filter(source, macro_defs) +function compiler.compile_filter(source, macro_defs, list_defs) + + for name, items in pairs(list_defs) do + source = string.gsub(source, name, table.concat(items, ", ")) + end + local ast, error_msg = parser.parse_filter(source) if (error_msg) then diff --git a/userspace/falco/lua/rule_loader.lua b/userspace/falco/lua/rule_loader.lua index 8bb55edf864..f668de6040d 100644 --- a/userspace/falco/lua/rule_loader.lua +++ b/userspace/falco/lua/rule_loader.lua @@ -115,7 +115,7 @@ end -- object. The by_name index is used for things like describing rules, -- and the by_idx index is used to map the relational node index back -- to a rule. -local state = {macros={}, filter_ast=nil, rules_by_name={}, n_rules=0, rules_by_idx={}} +local state = {macros={}, lists={}, filter_ast=nil, rules_by_name={}, n_rules=0, rules_by_idx={}} function load_rules(filename) @@ -131,9 +131,28 @@ function load_rules(filename) end if (v['macro']) then - local ast = compiler.compile_macro(v['condition']) + local ast = compiler.compile_macro(v['condition'], state.lists) state.macros[v['macro']] = ast.filter.value + elseif (v['list']) then + -- list items are represented in yaml as a native list, so no + -- parsing necessary + local items = {} + + -- List items may be references to other lists, so go through + -- the items and expand any references to the items in the list + for i, item in ipairs(v['items']) do + if (state.lists[item] == nil) then + items[#items+1] = item + else + for i, exp_item in ipairs(state.lists[item]) do + items[#items+1] = exp_item + end + end + end + + state.lists[v['list']] = items + else -- rule if (v['rule'] == nil) then @@ -150,7 +169,7 @@ function load_rules(filename) v['level'] = priority(v['priority']) state.rules_by_name[v['rule']] = v - local filter_ast = compiler.compile_filter(v['condition'], state.macros) + local filter_ast = compiler.compile_filter(v['condition'], state.macros, state.lists) if (filter_ast.type == "Rule") then state.n_rules = state.n_rules + 1