From 6356490b1c6d124f895c8478dab30a1134267bc5 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Wed, 1 Feb 2017 14:51:18 -0800 Subject: [PATCH 1/2] Misc demo improvements. Small changes to improve the use of falco_event_generator with falco: - In event_generator, some actions like exec_ls won't trigger notifications on their own. So exclude them from -a all. - For all actions, print details on what the action will do. - For actions that won't result in a falco notification in containers, note that in the output. - The short version of --once wasn't working, fix the getopt. - Explicitly saying -a all wasn't working, fix. - Don't rely on an external ruleset in the nodejs docker-compose demo--the built in rules are sufficient now. --- docker/event-generator/event_generator.cpp | 35 +++++++++++++++++----- examples/nodejs-bad-rest-api/demo.yml | 1 - 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/docker/event-generator/event_generator.cpp b/docker/event-generator/event_generator.cpp index 5bbe844f..3311b6b0 100644 --- a/docker/event-generator/event_generator.cpp +++ b/docker/event-generator/event_generator.cpp @@ -97,6 +97,8 @@ void exfiltration() shadow.open("/etc/shadow"); + printf("Reading /etc/shadow and sending to 10.5.2.6:8197...\n"); + if(!shadow.is_open()) { fprintf(stderr, "Could not open /etc/shadow for reading: %s", strerror(errno)); @@ -219,7 +221,7 @@ void write_rpm_database() { } void spawn_shell() { - printf("Spawning a shell using system()...\n"); + printf("Spawning a shell to run \"ls > /dev/null\" using system()...\n"); int rc; if ((rc = system("ls > /dev/null")) != 0) @@ -259,6 +261,7 @@ void mkdir_binary_dirs() { void change_thread_namespace() { printf("Calling setns() to change namespaces...\n"); + printf("NOTE: does not result in a falco notification in containers, unless container run with --privileged or --security-opt seccomp=unconfined\n"); // It doesn't matter that the arguments to setns are // bogus. It's the attempt to call it that will trigger the // rule. @@ -268,6 +271,7 @@ void change_thread_namespace() { void system_user_interactive() { pid_t child; + printf("Forking a child that becomes user=daemon and then tries to run /bin/login...\n"); // Fork a child and do everything in the child. if ((child = fork()) == 0) { @@ -313,6 +317,8 @@ void system_procs_network_activity() { void non_sudo_setuid() { pid_t child; + printf("Forking a child that becomes \"daemon\" user and then \"root\"...\n"); + // Fork a child and do everything in the child. if ((child = fork()) == 0) { @@ -367,6 +373,9 @@ map defined_actions = {{"write_binary_dir", write_binary_dir}, {"user_mgmt_binaries", user_mgmt_binaries}, {"exfiltration", exfiltration}}; +// Some actions don't directly result in suspicious behavior. These +// actions are excluded from the ones run with -a all. +set exclude_from_all_actions = {"exec_ls", "network_activity"}; void create_symlinks(const char *program) { @@ -394,9 +403,9 @@ void run_actions(map &actions, int interval, bool once) { for (auto action : actions) { - sleep(interval); printf("***Action %s\n", action.first.c_str()); action.second(); + sleep(interval); } if(once) { @@ -428,7 +437,7 @@ int main(int argc, char **argv) // Parse the args // while((op = getopt_long(argc, argv, - "ha:i:l:", + "ha:i:l:o", long_options, &long_index)) != -1) { switch(op) @@ -437,12 +446,16 @@ int main(int argc, char **argv) usage(argv[0]); exit(1); case 'a': - if((it = defined_actions.find(optarg)) == defined_actions.end()) + // "all" is already implied + if (strcmp(optarg, "all") != 0) { - fprintf(stderr, "No action with name \"%s\" known, exiting.\n", optarg); - exit(1); + if((it = defined_actions.find(optarg)) == defined_actions.end()) + { + fprintf(stderr, "No action with name \"%s\" known, exiting.\n", optarg); + exit(1); + } + actions.insert(*it); } - actions.insert(*it); break; case 'i': interval = atoi(optarg); @@ -482,7 +495,13 @@ int main(int argc, char **argv) if(actions.size() == 0) { - actions = defined_actions; + for(auto &act : defined_actions) + { + if(exclude_from_all_actions.find(act.first) == exclude_from_all_actions.end()) + { + actions.insert(act); + } + } } setvbuf(stdout, NULL, _IONBF, 0); diff --git a/examples/nodejs-bad-rest-api/demo.yml b/examples/nodejs-bad-rest-api/demo.yml index a826ab6e..ab0b4988 100644 --- a/examples/nodejs-bad-rest-api/demo.yml +++ b/examples/nodejs-bad-rest-api/demo.yml @@ -20,5 +20,4 @@ falco: - /boot:/host/boot:ro - /lib/modules:/host/lib/modules:ro - /usr:/host/usr:ro - - ${PWD}/../../rules/falco_rules.yaml:/etc/falco_rules.yaml tty: true From e0a5034a43ebf1c27ac6156d33059f8fe8a66390 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Wed, 1 Feb 2017 14:55:45 -0800 Subject: [PATCH 2/2] Ensure falco-event-generator actions are detected. A new trace file falco-event-generator.scap contains the result of running the falco event generator in docker, via: docker run --security-opt seccomp=unconfined sysdig/falco-event-generator:latest /usr/local/bin/event_generator --once Make sure this trace file detects the exact set of events we expect for each rule. This required adding a new verification method check_detections_by_rule that finds the per-rule counts and compares them to the expected counts, which are included in the test description under the key "detect_counts". This is the first time a trace file for a test is actually in one of the downloaded zip files. This means it will be tested twice (one for simple detect-or-not, once for actual counts). Adding this test showed a problem with Run shell in container rule--since sysdig/falco-event-generator startswith sysdig/falco, it was being treated as a trusted container. Modify the macro trusted_containers to not allow falco-event-generator to be trusted. --- rules/falco_rules.yaml | 3 ++- test/falco_test.py | 29 +++++++++++++++++++++++++++++ test/falco_tests.yaml.in | 19 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/rules/falco_rules.yaml b/rules/falco_rules.yaml index 4e411648..13cb0e68 100644 --- a/rules/falco_rules.yaml +++ b/rules/falco_rules.yaml @@ -345,7 +345,8 @@ - macro: trusted_containers condition: (container.image startswith sysdig/agent or - container.image startswith sysdig/falco or + (container.image startswith sysdig/falco and + not container.image startswith sysdig/falco-event-generator) or container.image startswith sysdig/sysdig or container.image startswith gcr.io/google_containers/hyperkube or container.image startswith gcr.io/google_containers/kube-proxy) diff --git a/test/falco_test.py b/test/falco_test.py index d6ce9b87..afb7c4f8 100644 --- a/test/falco_test.py +++ b/test/falco_test.py @@ -56,6 +56,16 @@ class FalcoTest(Test): for rule in self.disabled_rules: self.disabled_args = self.disabled_args + "-D " + rule + " " + self.detect_counts = self.params.get('detect_counts', '*', default=False) + if self.detect_counts == False: + self.detect_counts = {} + else: + detect_counts = {} + for item in self.detect_counts: + for item2 in item: + detect_counts[item2[0]] = item2[1] + self.detect_counts = detect_counts + self.rules_warning = self.params.get('rules_warning', '*', default=False) if self.rules_warning == False: self.rules_warning = sets.Set() @@ -161,6 +171,23 @@ class FalcoTest(Test): if not events_detected > 0: self.fail("Detected {} events at level {} when should have detected > 0".format(events_detected, level)) + def check_detections_by_rule(self, res): + # Get the number of events detected for each rule. Must match the expected counts. + match = re.search('Triggered rules by rule name:(.*)', res.stdout, re.DOTALL) + if match is None: + self.fail("Could not find a block 'Triggered rules by rule name: ...' in falco output") + + triggered_rules = match.group(1) + + for rule, count in self.detect_counts.iteritems(): + expected_line = '{}: {}'.format(rule, count) + match = re.search(expected_line, triggered_rules) + + if match is None: + self.fail("Could not find a line '{}' in triggered rule counts '{}'".format(expected_line, triggered_rules)) + else: + self.log.debug("Found expected count for {}: {}".format(rule, match.group())) + def check_outputs(self): for output in self.outputs: # Open the provided file and match each line against the @@ -222,6 +249,8 @@ class FalcoTest(Test): if len(self.rules_events) > 0: self.check_rules_events(res) self.check_detections(res) + if len(self.detect_counts) > 0: + self.check_detections_by_rule(res) self.check_json_output(res) self.check_outputs() pass diff --git a/test/falco_tests.yaml.in b/test/falco_tests.yaml.in index a973e8fe..977b7d4c 100644 --- a/test/falco_tests.yaml.in +++ b/test/falco_tests.yaml.in @@ -181,3 +181,22 @@ trace_files: !mux trace_file: trace_files/cat_write.scap outputs: - /tmp/falco_outputs/program_output.txt: Warning An open was seen + + detect_counts: + detect: True + detect_level: WARNING + trace_file: traces-positive/falco-event-generator.scap + detect_counts: + - "Write below binary dir": 1 + - "Read sensitive file untrusted": 3 + - "Run shell in container": 1 + - "Write below rpm database": 1 + - "Write below etc": 1 + - "System procs network activity": 1 + - "Mkdir binary dirs": 1 + - "System user interactive": 1 + - "DB program spawned process": 1 + - "Non sudo setuid": 1 + - "Create files below dev": 1 + - "Modify binary dirs": 2 + - "Change thread namespace": 2