From 502941b804ae724dc05635f0850d2c079a9e5e64 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Fri, 8 Jul 2016 09:31:17 -0700 Subject: [PATCH] Add list support to rules file. Once sysdig adds support for handling "in (...)" filter expressions as set membership tests, it will be advantageous to combine lists of items together into a single list so they can all be checked in a single set membership test. This commit adds support for a new yaml item type "list" containing a field "name" and field "items" containing a list of items. These are represented as a yaml list, which allows yaml to handle some of the initial parsing with the list items maintained natively in lua. Allow lists to contain list references by expanding any references to the items in the list, before storing the list items in state.lists. When parsing macro or rule conditions, replace all references to a list name with the list items as a comma separated string. Modify the falco rules to switch to lists whenever possible. The new convention is to use the suffix _binaries for lists of program names and _procs for macros that define a filter expression using the list. --- rules/falco_rules.yaml | 122 ++++++++++++++-------------- userspace/falco/lua/compiler.lua | 14 +++- userspace/falco/lua/rule_loader.lua | 25 +++++- 3 files changed, 94 insertions(+), 67 deletions(-) diff --git a/rules/falco_rules.yaml b/rules/falco_rules.yaml index 038c6a42..9ceb318a 100644 --- a/rules/falco_rules.yaml +++ b/rules/falco_rules.yaml @@ -68,76 +68,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 @@ -172,10 +172,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. @@ -189,32 +187,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 @@ -227,19 +225,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 @@ -267,7 +265,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 @@ -289,9 +287,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 @@ -307,13 +305,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 @@ -361,7 +359,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 @@ -530,6 +528,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 0e809dd6..df88e7fb 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 8bb55edf..f668de60 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