From 1a2719437f56f0eb3489027a32a123b0fb85c7d5 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Thu, 19 May 2016 16:17:27 -0700 Subject: [PATCH 1/5] Add graceful shutdown on SIGINT/SIGTERM. Add signal handlers for SIGINT/SIGTERM that set a shutdown flag. Initialize the live inspector with a timeout so the main loop can watch the flag set by the signal handlers. --- userspace/falco/falco.cpp | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index 6b92058f..4cc8bae8 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -28,6 +28,14 @@ extern "C" { #include "utils.h" #include +bool g_terminate = false; +// +// Helper functions +// +static void signal_callback(int signal) +{ + g_terminate = true; +} // // Program help @@ -90,7 +98,11 @@ void do_inspect(sinsp* inspector, res = inspector->next(&ev); - if(res == SCAP_TIMEOUT) + if (g_terminate) + { + break; + } + else if(res == SCAP_TIMEOUT) { continue; } @@ -398,6 +410,20 @@ int falco_init(int argc, char **argv) add_output(ls, *it); } + if(signal(SIGINT, signal_callback) == SIG_ERR) + { + fprintf(stderr, "An error occurred while setting SIGINT signal handler.\n"); + result = EXIT_FAILURE; + goto exit; + } + + if(signal(SIGTERM, signal_callback) == SIG_ERR) + { + fprintf(stderr, "An error occurred while setting SIGTERM signal handler.\n"); + result = EXIT_FAILURE; + goto exit; + } + if (scap_filename.size()) { inspector->open(scap_filename); @@ -406,7 +432,7 @@ int falco_init(int argc, char **argv) { try { - inspector->open(); + inspector->open(200); } catch(sinsp_exception e) { From a41bb0dac06e2f1db7e004ee072986d0d63b1321 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Thu, 19 May 2016 16:19:45 -0700 Subject: [PATCH 2/5] Print stats when shutting down. At shutdown, print stats on the number of rules triggered by severity and rule name. This is done by a lua function print_stats and the associated table rule_output_counts. When passing rules to outputs, update the counts in rule_output_counts. --- userspace/falco/falco.cpp | 23 +++++++++++++++++ userspace/falco/lua/output.lua | 2 ++ userspace/falco/lua/rule_loader.lua | 39 ++++++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index 4cc8bae8..01b3019c 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -75,6 +75,7 @@ static void display_fatal_err(const string &msg, bool daemon) string lua_on_event = "on_event"; string lua_add_output = "add_output"; +string lua_print_stats = "print_stats"; // Splitting into key=value or key.subkey=value will be handled by configuration class. std::list cmdline_options; @@ -211,6 +212,26 @@ void add_output(lua_State *ls, output_config oc) } +// Print statistics on the the rules that triggered +void print_stats(lua_State *ls) +{ + lua_getglobal(ls, lua_print_stats.c_str()); + + if(lua_isfunction(ls, -1)) + { + if(lua_pcall(ls, 0, 0, 0) != 0) + { + const char* lerr = lua_tostring(ls, -1); + string err = "Error invoking function print_stats: " + string(lerr); + throw sinsp_exception(err); + } + } + else + { + throw sinsp_exception("No function " + lua_print_stats + " found in lua rule loader module"); + } + +} // // ARGUMENT PARSING AND PROGRAM SETUP @@ -504,6 +525,8 @@ int falco_init(int argc, char **argv) ls); inspector->close(); + + print_stats(ls); } catch(sinsp_exception& e) { diff --git a/userspace/falco/lua/output.lua b/userspace/falco/lua/output.lua index 78573b94..0bef1712 100644 --- a/userspace/falco/lua/output.lua +++ b/userspace/falco/lua/output.lua @@ -2,6 +2,8 @@ local mod = {} levels = {"Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Informational", "Debug"} +mod.levels = levels + local outputs = {} function mod.stdout(evt, level, format) diff --git a/userspace/falco/lua/rule_loader.lua b/userspace/falco/lua/rule_loader.lua index f5cc8882..0e041f7e 100644 --- a/userspace/falco/lua/rule_loader.lua +++ b/userspace/falco/lua/rule_loader.lua @@ -230,12 +230,49 @@ function describe_rule(name) end end +local rule_output_counts = {by_level={}, by_name={}} + +for idx, level in ipairs(output.levels) do + rule_output_counts[level] = 0 +end + function on_event(evt_, rule_id) if state.rules_by_idx[rule_id] == nil then error ("rule_loader.on_event(): event with invalid rule_id: ", rule_id) end - output.event(evt_, state.rules_by_idx[rule_id].level, state.rules_by_idx[rule_id].output) + local rule = state.rules_by_idx[rule_id] + + if rule_output_counts.by_level[rule.level] == nil then + rule_output_counts.by_level[rule.level] = 1 + else + rule_output_counts.by_level[rule.level] = rule_output_counts.by_level[rule.level] + 1 + end + + if rule_output_counts.by_name[rule.rule] == nil then + rule_output_counts.by_name[rule.rule] = 1 + else + rule_output_counts.by_name[rule.rule] = rule_output_counts.by_name[rule.rule] + 1 + end + + output.event(evt_, rule.level, rule.output) end +function print_stats() + print("Rule counts by severity:") + for idx, level in ipairs(output.levels) do + -- To keep the output concise, we only print 0 counts for error, warning, and info levels + if rule_output_counts[level] > 0 or level == "Error" or level == "Warning" or level == "Informational" then + print (" "..level..": "..rule_output_counts[level]) + end + end + + print("Triggered rules by rule name:") + for name, count in pairs(rule_output_counts.by_name) do + print (" "..name..": "..count) + end +end + + + From 4751546c033660b35fb174af5399fc7abee8f964 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Wed, 18 May 2016 17:08:01 -0700 Subject: [PATCH 3/5] Add correctness tests using Avocado Start using the Avocado framework for automated regression testing. Create a test FalcoTest in falco_test.py which can run on a collection of trace files. The script test/run_regression_tests.sh is responsible for pulling zip files containing the positive (falco should detect) and negative (falco should not detect) trace files, creating a Avocado multiplex file that defines all the tests (one for each trace file), running avocado on all the trace files, and showing full logs for any test that didn't pass. The old regression script, which simply ran falco, has been removed. Modify falco's stats output to show the total number of events detected for use in the tests. In travis.yml, pull a known stable version of avocado and build it, including installing any dependencies, as a part of the build process. --- .travis.yml | 10 ++++- test/falco_test.py | 63 +++++++++++++++++++++++++++++ test/falco_trace_regression.sh | 30 -------------- test/run_regression_tests.sh | 62 ++++++++++++++++++++++++++++ userspace/falco/lua/rule_loader.lua | 4 +- 5 files changed, 137 insertions(+), 32 deletions(-) create mode 100644 test/falco_test.py delete mode 100755 test/falco_trace_regression.sh create mode 100755 test/run_regression_tests.sh diff --git a/.travis.yml b/.travis.yml index d4953371..fe37c22f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,14 @@ install: - sudo apt-get --force-yes install g++-4.8 - sudo apt-get install rpm linux-headers-$(uname -r) - git clone https://github.com/draios/sysdig.git ../sysdig + - sudo apt-get install -y python-pip libvirt-dev jq + - cd .. + - curl -Lo avocado-36.0-tar.gz https://github.com/avocado-framework/avocado/archive/36.0lts.tar.gz + - tar -zxvf avocado-36.0-tar.gz + - cd avocado-36.0lts + - sudo pip install -r requirements-travis.txt + - sudo python setup.py install + - cd ../falco before_script: - export KERNELDIR=/lib/modules/$(ls /lib/modules | sort | head -1)/build script: @@ -28,7 +36,7 @@ script: - make VERBOSE=1 - make package - cd .. - - sudo test/falco_trace_regression.sh build/userspace/falco/falco + - sudo test/run_regression_tests.sh notifications: webhooks: urls: diff --git a/test/falco_test.py b/test/falco_test.py new file mode 100644 index 00000000..72875c1c --- /dev/null +++ b/test/falco_test.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python + +import os +import re + +from avocado import Test +from avocado.utils import process +from avocado.utils import linux_modules + +class FalcoTest(Test): + + def setUp(self): + """ + Load the sysdig kernel module if not already loaded. + """ + self.falcodir = self.params.get('falcodir', '/', default=os.path.join(self.basedir, '../build')) + + self.should_detect = self.params.get('detect', '*') + self.trace_file = self.params.get('trace_file', '*') + + # Doing this in 2 steps instead of simply using + # module_is_loaded to avoid logging lsmod output to the log. + lsmod_output = process.system_output("lsmod", verbose=False) + + if linux_modules.parse_lsmod_for_module(lsmod_output, 'sysdig_probe') == {}: + self.log.debug("Loading sysdig kernel module") + process.run('sudo insmod {}/driver/sysdig-probe.ko'.format(self.falcodir)) + + self.str_variant = self.trace_file + + def test(self): + self.log.info("Trace file %s", self.trace_file) + + # Run the provided trace file though falco + cmd = '{}/userspace/falco/falco -r {}/../rules/falco_rules.yaml -c {}/../falco.yaml -e {}'.format( + self.falcodir, self.falcodir, self.falcodir, self.trace_file) + + self.falco_proc = process.SubProcess(cmd) + + res = self.falco_proc.run(timeout=60, sig=9) + + if res.exit_status != 0: + self.error("Falco command \"{}\" exited with non-zero return value {}".format( + cmd, res.exit_status)) + + # Get the number of events detected. + res = re.search('Events detected: (\d+)', res.stdout) + if res is None: + self.fail("Could not find a line 'Events detected: ' in falco output") + + events_detected = int(res.group(1)) + + if not self.should_detect and events_detected > 0: + self.fail("Detected {} events when should have detected none".format(events_detected)) + + if self.should_detect and events_detected == 0: + self.fail("Detected {} events when should have detected > 0".format(events_detected)) + + pass + + +if __name__ == "__main__": + main() diff --git a/test/falco_trace_regression.sh b/test/falco_trace_regression.sh deleted file mode 100755 index a4b3498c..00000000 --- a/test/falco_trace_regression.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -set -eu - -SCRIPT=$(readlink -f $0) -BASEDIR=$(dirname $SCRIPT) - -FALCO=$1 -BUILDDIR=$(dirname $FALCO) - -# Load the built kernel module by hand -insmod $BUILDDIR/../../driver/sysdig-probe.ko - -# For now, simply ensure that falco can run without errors. -FALCO_CMDLINE="$FALCO -c $BASEDIR/../falco.yaml -r $BASEDIR/../rules/falco_rules.yaml" -echo "Running falco: $FALCO_CMDLINE" -$FALCO_CMDLINE > $BASEDIR/falco.log 2>&1 & -FALCO_PID=$! -echo "Falco started, pid $FALCO_PID" -sleep 10 -if kill -0 $FALCO_PID > /dev/null 2>&1; then - echo "Falco ran successfully" - kill $FALCO_PID - ret=0 -else - echo "Falco did not start successfully. Full program output:" - cat $BASEDIR/falco.log - ret=1 -fi - -exit $ret diff --git a/test/run_regression_tests.sh b/test/run_regression_tests.sh new file mode 100755 index 00000000..9f6b2a28 --- /dev/null +++ b/test/run_regression_tests.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +SCRIPT=$(readlink -f $0) +SCRIPTDIR=$(dirname $SCRIPT) +MULT_FILE=$SCRIPTDIR/falco_tests.yaml + +function download_trace_files() { + for TRACE in traces-positive traces-negative ; do + curl -so $SCRIPTDIR/$TRACE.zip https://s3.amazonaws.com/download.draios.com/falco-tests/$TRACE.zip && + unzip -d $SCRIPTDIR $SCRIPTDIR/$TRACE.zip && + rm -rf $SCRIPTDIR/$TRACE.zip + done +} + +function prepare_multiplex_file() { + echo "trace_files: !mux" > $MULT_FILE + + for trace in $SCRIPTDIR/traces-positive/*.scap ; do + [ -e "$trace" ] || continue + NAME=`basename $trace .scap` + cat << EOF >> $MULT_FILE + $NAME: + detect: True + trace_file: $trace +EOF + done + + for trace in $SCRIPTDIR/traces-negative/*.scap ; do + [ -e "$trace" ] || continue + NAME=`basename $trace .scap` + cat << EOF >> $MULT_FILE + $NAME: + detect: False + trace_file: $trace +EOF + done + + echo "Contents of $MULT_FILE:" + cat $MULT_FILE +} + +function run_tests() { + CMD="avocado run --multiplex $MULT_FILE --job-results-dir $SCRIPTDIR/job-results -- $SCRIPTDIR/falco_test.py" + echo "Running: $CMD" + $CMD + TEST_RC=$? +} + + +function print_test_failure_details() { + echo "Showing full job logs for any tests that failed:" + jq '.tests[] | select(.status != "PASS") | .logfile' $SCRIPTDIR/job-results/latest/results.json | xargs cat +} + +download_trace_files +prepare_multiplex_file +run_tests +if [ $TEST_RC -ne 0 ]; then + print_test_failure_details +fi + +exit $TEST_RC diff --git a/userspace/falco/lua/rule_loader.lua b/userspace/falco/lua/rule_loader.lua index 0e041f7e..7a9774a7 100644 --- a/userspace/falco/lua/rule_loader.lua +++ b/userspace/falco/lua/rule_loader.lua @@ -230,7 +230,7 @@ function describe_rule(name) end end -local rule_output_counts = {by_level={}, by_name={}} +local rule_output_counts = {total=0, by_level={}, by_name={}} for idx, level in ipairs(output.levels) do rule_output_counts[level] = 0 @@ -242,6 +242,7 @@ function on_event(evt_, rule_id) error ("rule_loader.on_event(): event with invalid rule_id: ", rule_id) end + rule_output_counts.total = rule_output_counts.total + 1 local rule = state.rules_by_idx[rule_id] if rule_output_counts.by_level[rule.level] == nil then @@ -260,6 +261,7 @@ function on_event(evt_, rule_id) end function print_stats() + print("Events detected: "..rule_output_counts.total) print("Rule counts by severity:") for idx, level in ipairs(output.levels) do -- To keep the output concise, we only print 0 counts for error, warning, and info levels From b3ae480facc6eaf6ed4b588e665beafa23b7ea3d Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Mon, 23 May 2016 15:32:12 -0700 Subject: [PATCH 4/5] Another round of rule cleanups. Do another round of rule cleanups now that we have a larger set of positive and negative trace files to work with. Outside of this commit, there are now trace files for all the positive rules, a docker-compose startup and teardown, and some trace files from the sysdig cloud staging environment. Also add a script that runs sysdig with a filter that removes all the syscalls not handled by falco as well as a few other high-volume, low-information syscalls. This script was used to create the staging environment trace files. Notable rule changes: - The direction for write_binary_dir/write_etc needs to be exit instead of enter, as the bin_dir clause works on the file descriptor returned by the open/openat call. - Add login as a trusted binary that can read sensitive files (occurs for direct console logins). - sshd can read sensitive files well after startup, so exclude it from the set of binaries that can trigger read_sensitive_file_trusted_after_startup. - limit run_shell_untrusted to non-containers. - Disable the ssh_error_syslog rule for now. With the current restriction on system calls (no read/write/sendto/recvfrom/etc), you won't see the ssh error messages. Nevertheless, add a string to look for to indicate ssh errors and add systemd's true location for the syslog device. - Sshd attemps to setuid even when it's not running as root, so exclude it from the set of binaries to monitor for now. - Let programs that are direct decendants of systemd spawn user management tasks for now. - Temporarily disable the EACCESS rule. This rule is exposing a bug in sysdig in debug mode, https://github.com/draios/sysdig/issues/598. The rule is also pretty noisy so I'll keep it disabled until the sysdig bug is fixed. - The etc_dir and bin_dir macros both have the problem that they match pathnames with /etc/, /bin/, etc in the middle of the path, as sysdig doesn't have a "begins with" comparison. Add notes for that. - Change spawn_process to spawned_process to indicate that it's for the exit side of the execve. Also use it in a few places that were looking for the same conditions without any macro. - Get rid of adduser_binaries and fold any programs not already present into shadowutils_binaries. - Add new groups sysdigcloud_binaries and sysdigcloud_binaries_parent and add them as exceptions for write_etc/write_binary_dir. - Add yum as a package management binary and add it as an exception to write_etc/write_binary_dir. - Change how db_program_spawned_process works. Since all of the useful information is on the exit side of the event, you can't really add a condition based on the process being new. Isntead, have the rule check for a non-database-related program being spawned by a database-related program. - Allow dragent to run shells. - Add sendmail, sendmail-msp as a program that attempts to setuid. - Some of the *_binaries macros that were based on dpkg -L accidentally contained directories in addition to end files. Trim those. - Add systemd-logind as a login_binary. - Add unix_chkpwd as a shadowutils_binary. - Add parentheses around any macros that group items using or. I found this necessary when the macro is used in the middle of a list of and conditions. - Break out system_binaries into a new subset user_mgmt_binaries containing login_, passwd_, and shadowutils_ binaries. That way you don't have to pull in all of system_binaries when looking for sensisitive files or user management activity. - Rename fs-bash to fbash, thinking ahead to its more likely name. --- rules/falco_rules.yaml | 135 +++++++++++++++++++++++---------------- test/utils/run_sysdig.sh | 9 +++ 2 files changed, 89 insertions(+), 55 deletions(-) create mode 100644 test/utils/run_sysdig.sh diff --git a/rules/falco_rules.yaml b/rules/falco_rules.yaml index b0cdb0ab..1d451af9 100644 --- a/rules/falco_rules.yaml +++ b/rules/falco_rules.yaml @@ -38,12 +38,16 @@ - macro: modify condition: rename or remove -- macro: spawn_process - condition: syscall.type = execve and evt.dir=< +- macro: spawned_process + condition: evt.type = execve and evt.dir=< # File categories - macro: terminal_file_fd condition: fd.name=/dev/ptmx or fd.directory=/dev/pts + +# This really should be testing that the directory begins with these +# prefixes but sysdig's filter doesn't have a "starts with" operator +# (yet). - macro: bin_dir condition: fd.directory in (/bin, /sbin, /usr/bin, /usr/sbin) @@ -52,6 +56,8 @@ - macro: bin_dir_rename condition: evt.arg[1] contains /bin/ or evt.arg[1] contains /sbin/ or evt.arg[1] contains /usr/bin/ or evt.arg[1] contains /usr/sbin/ +# This really should be testing that the directory begins with /etc, +# but sysdig's filter doesn't have a "starts with" operator (yet). - macro: etc_dir condition: fd.directory contains /etc @@ -74,25 +80,31 @@ 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) -- macro: adduser_binaries - condition: proc.name in (adduser, deluser, addgroup, delgroup) -- macro: login_binaries - condition: proc.name in (bin, login, su, sbin, nologin, bin, faillog, lastlog, newgrp, sg) -# dpkg -L passwd | grep bin | xargs -L 1 basename | tr "\\n" "," +# 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) + +# 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 (sbin, shadowconfig, sbin, grpck, pwunconv, grpconv, pwck, + proc.name in (shadowconfig, grpck, pwunconv, grpconv, pwck, groupmod, vipw, pwconv, useradd, newusers, cppw, chpasswd, usermod, - groupadd, groupdel, grpunconv, chgpasswd, userdel, bin, chage, chsh, + groupadd, groupdel, grpunconv, chgpasswd, userdel, chage, chsh, gpasswd, chfn, expiry, passwd, vigr, cpgr) -# repoquery -l shadow-utils | grep bin | xargs -L 1 basename | tr "\\n" "," +# 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, chpasswd, - groupadd, groupdel, groupmems, groupmod, grpck, grpconv, grpunconv, - newusers, pwck, pwconv, pwunconv, useradd, userdel, usermod, vigr, vipw) + proc.name in (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) + +- macro: sysdigcloud_binaries + condition: proc.name in (setup-backend, dragent) + +- macro: sysdigcloud_binaries_parent + condition: proc.pname in (setup-backend, dragent) - macro: docker_binaries condition: proc.name in (docker, exe) @@ -103,25 +115,33 @@ - macro: db_server_binaries condition: proc.name in (mysqld) -- macro: server_binaries - condition: http_server_binaries or db_server_binaries or docker_binaries or proc.name in (sshd) +- 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)) + +# The truncated dpkg-preconfigu is intentional, process names are +# truncated at the sysdig level. - macro: package_mgmt_binaries - condition: proc.name in (dpkg, rpm) + condition: proc.name in (dpkg, dpkg-preconfigu, rpm, yum) # 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) +- macro: user_mgmt_binaries + condition: (login_binaries or passwd_binaries or shadowutils_binaries) + - macro: system_binaries - condition: coreutils_binaries or adduser_binaries or login_binaries or passwd_binaries or shadowutils_binaries + condition: (coreutils_binaries or user_mgmt_binaries) - macro: mail_binaries - condition: proc.name in (sendmail, postfix, procmail) + condition: proc.name in (sendmail, sendmail-msp, postfix, procmail) - macro: sensitive_files - condition: fd.name contains /etc/shadow or fd.name = /etc/sudoers or fd.directory = /etc/sudoers.d or fd.directory = /etc/pam.d or fd.name = /etc/pam.conf + condition: (fd.name contains /etc/shadow or fd.name = /etc/sudoers or fd.directory = /etc/sudoers.d or fd.directory = /etc/pam.d or fd.name = /etc/pam.conf) # Indicates that the process is new. Currently detected using time # since process was started, using a threshold of 5 seconds. @@ -130,7 +150,7 @@ # Network - macro: inbound - condition: (syscall.type=listen and evt.dir=>) or (syscall.type=accept and evt.dir=<) + condition: ((syscall.type=listen and evt.dir=>) or (syscall.type=accept and evt.dir=<)) # Currently sendto is an ignored syscall, otherwise this could also check for (syscall.type=sendto and evt.dir=>) - macro: outbound @@ -141,7 +161,7 @@ # Ssh - macro: ssh_error_message - condition: evt.arg.data contains "Invalid user" or evt.arg.data contains "preauth" + condition: (evt.arg.data contains "Invalid user" or evt.arg.data contains "preauth" or evt.arg.data contains "Failed password") # System - macro: modules @@ -149,9 +169,9 @@ - macro: container condition: container.id != host - macro: interactive - condition: (proc.aname=sshd and proc.name != sshd) or proc.name=systemd-logind + condition: ((proc.aname=sshd and proc.name != sshd) or proc.name=systemd-logind) - macro: syslog - condition: fd.name = /dev/log + condition: fd.name in (/dev/log, /run/systemd/journal/syslog) - macro: cron condition: proc.name in (cron, crond) - macro: parent_cron @@ -169,32 +189,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 bin_dir + condition: evt.dir = < and open_write and not package_mgmt_binaries 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 - condition: evt.dir = > and open_write and etc_dir + condition: evt.dir = < and open_write and not shadowutils_binaries and not sysdigcloud_binaries_parent and not package_mgmt_binaries and etc_dir output: "File below /etc opened for writing (user=%user.name command=%proc.cmdline file=%fd.name)" priority: WARNING - 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 server_binaries and not userexec_binaries and not proc.name in (iptables, ps, systemd-logind, lsb_release, check-new-relea, dumpe2fs, accounts-daemon, bash) and not cron and sensitive_files + 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 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 + condition: open_read and server_binaries 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 -- rule: db_program_spawn_process - desc: a database-server related program spawning a new process after startup. This shouldn\'t occur and is a follow on from some SQL injection attacks. - condition: db_server_binaries and not proc_is_new and spawn_process - output: "Database-related program spawned new process after startup (user=%user.name command=%proc.cmdline)" +- 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 + output: "Database-related program spawned process other than itself (user=%user.name program=%proc.cmdline parent=%proc.pname)" priority: WARNING - rule: modify_binary_dirs @@ -218,11 +238,12 @@ # output: "Loaded .so from unexpected dir (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)" # priority: WARNING -- rule: syscall_returns_eaccess - desc: any system call that returns EACCESS. This is not always a strong indication of a problem, hence the INFO priority. - condition: evt.res = EACCESS - output: "System call returned EACCESS (user=%user.name command=%proc.cmdline syscall=%evt.type args=%evt.args)" - priority: INFO +# Temporarily disabling this rule as it's tripping over https://github.com/draios/sysdig/issues/598 +# - rule: syscall_returns_eaccess +# desc: any system call that returns EACCESS. This is not always a strong indication of a problem, hence the INFO priority. +# condition: evt.res = EACCESS +# output: "System call returned EACCESS (user=%user.name command=%proc.cmdline syscall=%evt.type args=%evt.args)" +# priority: INFO - rule: change_thread_namespace desc: an attempt to change a program/thread\'s namespace (commonly done as a part of creating a container) by calling setns. @@ -232,7 +253,7 @@ - rule: run_shell_untrusted desc: an attempt to spawn a shell by a non-shell program. Exceptions are made for trusted binaries. - condition: proc.name = bash and evt.dir=< and evt.type=execve and proc.pname exists and not parent_cron and not proc.pname in (bash, sshd, sudo, docker, su, tmux, screen, emacs, systemd, flock, fs-bash, nginx, monit, supervisord) + 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) output: "Shell spawned by untrusted binary (user=%user.name shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)" priority: WARNING @@ -243,13 +264,13 @@ - rule: system_user_interactive desc: an attempt to run interactive commands by a system (i.e. non-login) user - condition: spawn_process and system_users and interactive + condition: spawned_process and system_users and interactive output: "System user ran an interactive command (user=%user.name command=%proc.cmdline)" priority: WARNING - rule: run_shell_in_container - desc: an attempt to spawn a shell by a non-shell program in a container. Container entrypoints are excluded. - condition: container and proc.name = bash and evt.dir=< and evt.type=execve and proc.pname exists and not proc.pname in (bash, docker) + desc: a shell was spawned by a non-shell program in a container. Container entrypoints are excluded. + condition: container and proc.name = bash and spawned_process and proc.pname exists and not proc.pname in (bash, docker) output: "Shell spawned in a container other than entrypoint (user=%user.name container_id=%container.id container_name=%container.name shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)" priority: WARNING @@ -260,22 +281,26 @@ output: "Known system binary sent/received network traffic (user=%user.name command=%proc.cmdline connection=%fd.name)" priority: WARNING -- rule: ssh_error_syslog - desc: any ssh errors (failed logins, disconnects, ...) sent to syslog - condition: syslog and ssh_error_message and evt.dir = < - output: "sshd sent error message to syslog (error=%evt.buffer)" - priority: WARNING +# With the current restriction on system calls handled by falco +# (e.g. excluding read/write/sendto/recvfrom/etc, this rule won't +# trigger). +# - rule: ssh_error_syslog +# desc: any ssh errors (failed logins, disconnects, ...) sent to syslog +# condition: syslog and ssh_error_message and evt.dir = < +# output: "sshd sent error message to syslog (error=%evt.buffer)" +# priority: WARNING +# 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 + 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) 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: spawn_process and not proc.name in (su, sudo) and not container and (adduser_binaries or login_binaries or passwd_binaries or shadowutils_binaries) - output: "User management binary command run outside of container (user=%user.name command=%proc.cmdline)" + 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) + output: "User management binary command run outside of container (user=%user.name command=%proc.cmdline parent=%proc.pname)" priority: WARNING # (we may need to add additional checks against false positives, see: https://bugs.launchpad.net/ubuntu/+source/rkhunter/+bug/86153) @@ -285,17 +310,17 @@ output: "File created below /dev by untrusted program (user=%user.name command=%proc.cmdline file=%fd.name)" priority: WARNING -# fs-bash is a restricted version of bash suitable for use in curl | sh installers. +# fbash is a small shell script that runs bash, and is suitable for use in curl | fbash installers. - rule: installer_bash_starts_network_server - desc: an attempt by any program that is a child of fs-bash to start listening for network connections - condition: evt.type=listen and proc.aname=fs-bash - output: "Unexpected listen call by a child process of fs-bash (command=%proc.cmdline)" + desc: an attempt by any program that is a child of fbash to start listening for network connections + condition: evt.type=listen and proc.aname=fbash + output: "Unexpected listen call by a child process of fbash (command=%proc.cmdline)" priority: WARNING - rule: installer_bash_starts_session - desc: an attempt by any program that is a child of fs-bash to start a new session (process group) - condition: evt.type=setsid and proc.aname=fs-bash - output: "Unexpected setsid call by a child process of fs-bash (command=%proc.cmdline)" + desc: an attempt by any program that is a child of fbash to start a new session (process group) + condition: evt.type=setsid and proc.aname=fbash + output: "Unexpected setsid call by a child process of fbash (command=%proc.cmdline)" priority: WARNING ########################### diff --git a/test/utils/run_sysdig.sh b/test/utils/run_sysdig.sh new file mode 100644 index 00000000..9a1a611f --- /dev/null +++ b/test/utils/run_sysdig.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Run sysdig excluding all events that aren't used by falco and also +# excluding other high-volume events that aren't essential. This +# results in smaller trace files. + +# The remaining arguments are taken from the command line. + +exec sudo sysdig not evt.type in '(mprotect,brk,mq_timedreceive,mq_receive,mq_timedsend,mq_send,getrusage,procinfo,rt_sigprocmask,rt_sigaction,ioctl,clock_getres,clock_gettime,clock_nanosleep,clock_settime,close,epoll_create,epoll_create1,epoll_ctl,epoll_pwait,epoll_wait,eventfd,fcntl,fcntl64,fstat,fstat64,fstatat64,fstatfs,fstatfs64,futex,getitimer,gettimeofday,ioprio_get,ioprio_set,llseek,lseek,lstat,lstat64,mmap,mmap2,munmap,nanosleep,poll,ppoll,pread,pread64,preadv,procinfo,pselect6,pwrite,pwrite64,pwritev,read,readv,recv,recvfrom,recvmmsg,recvmsg,sched_yield,select,send,sendfile,sendfile64,sendmmsg,sendmsg,sendto,setitimer,settimeofday,shutdown,splice,stat,stat64,statfs,statfs64,switch,tee,timer_create,timer_delete,timerfd_create,timerfd_gettime,timerfd_settime,timer_getoverrun,timer_gettime,timer_settime,wait4,write,writev) and user.name!=ec2-user' $@ From 0f4b3787754507153213e8614a21071c2196c1db Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Mon, 23 May 2016 16:14:07 -0700 Subject: [PATCH 5/5] Add .gitignore for test directory. Exclude trace directories. --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index f392843b..5b202663 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,9 @@ /build* +*~ +test/falco_test.pyc +test/falco_tests.yaml +test/traces-negative +test/traces-positive userspace/falco/lua/re.lua userspace/falco/lua/lpeg.so