From 6e1f23b9a53798de3c89a64ec8a300a0027520e4 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Thu, 11 Aug 2016 16:37:07 -0700 Subject: [PATCH 1/2] Program/docker image that performs bad activities. C++ program that performs bad activities related to the current falco ruleset. There are configurable actions for almost all of the current ruleset, via the --action argument. By default runs in a loop forever. Can be overridden via --once. Also add a Dockerfile that compiles event_generator.cpp within an alpine linux image and copies it to /usr/local/bin. This image has been pushed to docker hub as "sysdig/falco-event-generator:latest". Add a Makefile that runs the right docker build command. --- .gitignore | 6 + docker/event-generator/Dockerfile | 6 + docker/event-generator/Makefile | 2 + docker/event-generator/event_generator.cpp | 402 +++++++++++++++++++++ 4 files changed, 416 insertions(+) create mode 100644 docker/event-generator/Dockerfile create mode 100644 docker/event-generator/Makefile create mode 100644 docker/event-generator/event_generator.cpp diff --git a/.gitignore b/.gitignore index d1217019bb5..b3abef09078 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,9 @@ test/results*.json.* userspace/falco/lua/re.lua userspace/falco/lua/lpeg.so + +docker/event-generator/event-generator +docker/event-generator/mysqld +docker/event-generator/httpd +docker/event-generator/sha1sum +docker/event-generator/vipw diff --git a/docker/event-generator/Dockerfile b/docker/event-generator/Dockerfile new file mode 100644 index 00000000000..84539a950ac --- /dev/null +++ b/docker/event-generator/Dockerfile @@ -0,0 +1,6 @@ +FROM alpine:latest +RUN apk add --no-cache bash g++ +COPY ./event_generator.cpp /usr/local/bin +RUN mkdir -p /var/lib/rpm +RUN g++ --std=c++0x /usr/local/bin/event_generator.cpp -o /usr/local/bin/event_generator +CMD ["/usr/local/bin/event_generator"] diff --git a/docker/event-generator/Makefile b/docker/event-generator/Makefile new file mode 100644 index 00000000000..09a9c8f9351 --- /dev/null +++ b/docker/event-generator/Makefile @@ -0,0 +1,2 @@ +image: + docker build -t sysdig/falco-event-generator:latest . diff --git a/docker/event-generator/event_generator.cpp b/docker/event-generator/event_generator.cpp new file mode 100644 index 00000000000..266eeaebaa8 --- /dev/null +++ b/docker/event-generator/event_generator.cpp @@ -0,0 +1,402 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +void usage(char *program) +{ + printf("Usage %s [options]\n\n", program); + printf("Options:\n"); + printf(" -h/--help: show this help\n"); + printf(" -a/--action: actions to perform. Can be one of the following:\n"); + printf(" write_binary_dir Write to files below /bin\n"); + printf(" write_etc Write to files below /etc\n"); + printf(" read_sensitive_file Read a sensitive file\n"); + printf(" read_sensitive_file_after_startup As a trusted program, wait a while,\n"); + printf(" then read a sensitive file\n"); + printf(" write_rpm_database Write to files below /var/lib/rpm\n"); + printf(" spawn_shell Run a shell (bash)\n"); + printf(" db_program_spawn_process As a database program, try to spawn\n"); + printf(" another program\n"); + printf(" modify_binary_dirs Modify a file below /bin\n"); + printf(" mkdir_binary_dirs Create a directory below /bin\n"); + printf(" change_thread_namespace Change namespace\n"); + printf(" system_user_interactive Change to a system user and try to\n"); + printf(" run an interactive command\n"); + printf(" network_activity Open network connections\n"); + printf(" (used by system_procs_network_activity below)\n"); + printf(" system_procs_network_activity Open network connections as a program\n"); + printf(" that should not perform network actions\n"); + printf(" non_sudo_setuid Setuid as a non-root user\n"); + printf(" create_files_below_dev Create files below /dev\n"); + printf(" exec_ls execve() the program ls\n"); + printf(" (used by user_mgmt_binaries below)\n"); + printf(" user_mgmt_binaries Become the program \"vipw\", which triggers\n"); + printf(" rules related to user management programs\n"); + printf(" all All of the above\n"); + printf(" -i/--interval: Number of seconds between actions\n"); + printf(" -o/--once: Perform actions once and exit\n"); +} + +void open_file(const char *filename, const char *flags) +{ + FILE *f = fopen(filename, flags); + if(f) + { + fclose(f); + } + else + { + fprintf(stderr, "Could not open %s for writing: %s\n", filename, strerror(errno)); + } + +} + +void touch(const char *filename) +{ + open_file(filename, "w"); +} + +void read(const char *filename) +{ + open_file(filename, "r"); +} + +uid_t become_user(const char *user) +{ + struct passwd *pw; + pw = getpwnam(user); + if(pw == NULL) + { + fprintf(stderr, "Could not find user information for \"%s\" user: %s\n", user, strerror(errno)); + exit(1); + } + + int rc = setuid(pw->pw_uid); + + if(rc != 0) + { + fprintf(stderr, "Could not change user to \"%s\" (uid %u): %s\n", user, pw->pw_uid, strerror(errno)); + exit(1); + } +} + +void spawn(const char *cmd, char **argv, char **env) +{ + pid_t child; + + // Fork a process, that way proc.duration is reset + if ((child = fork()) == 0) + { + execve(cmd, argv, env); + fprintf(stderr, "Could not exec to spawn %s: %s\n", cmd, strerror(errno)); + } + else + { + int status; + waitpid(child, &status, 0); + } +} + +void respawn(const char *cmd, const char *action, const char *interval) +{ + char *argv[] = {(char *) cmd, + (char *) "--action", (char *) action, + (char *) "--interval", (char *) interval, + (char *) "--once", NULL}; + + char *env[] = {NULL}; + + spawn(cmd, argv, env); +} + +void write_binary_dir() { + printf("Writing to /bin/created-by-event-generator-sh...\n"); + touch("/bin/created-by-event-generator-sh"); +} + +void write_etc() { + printf("Writing to /etc/created-by-event-generator-sh...\n"); + touch("/etc/created-by-event-generator-sh"); +} + +void read_sensitive_file() { + printf("Reading /etc/shadow...\n"); + read("/etc/shadow"); +} + +void read_sensitive_file_after_startup() { + printf("Becoming the program \"httpd\", sleeping 6 seconds and reading /etc/shadow...\n"); + respawn("./httpd", "read_sensitive_file", "6"); +} + +void write_rpm_database() { + printf("Writing to /var/lib/rpm/created-by-event-generator-sh...\n"); + touch("/var/lib/rpm/created-by-event-generator-sh"); +} + +void spawn_shell() { + printf("Spawning a shell using system()...\n"); + int rc; + + if ((rc = system("ls > /dev/null")) != 0) + { + fprintf(stderr, "Could not run ls > /dev/null in a shell: %s\n", strerror(errno)); + } +} + +void db_program_spawn_process() { + printf("Becoming the program \"mysql\" and then spawning a shell\n"); + respawn("./mysqld", "spawn_shell", "0"); +} + +void modify_binary_dirs() { + printf("Moving /bin/true to /bin/true.event-generator-sh and back...\n"); + + if (rename("/bin/true", "/bin/true.event-generator-sh") != 0) + { + fprintf(stderr, "Could not rename \"/bin/true\" to \"/bin/true.event-generator-sh\": %s\n", strerror(errno)); + } + else + { + if (rename("/bin/true.event-generator-sh", "/bin/true") != 0) + { + fprintf(stderr, "Could not rename \"/bin/true.event-generator-sh\" to \"/bin/true\": %s\n", strerror(errno)); + } + } +} + +void mkdir_binary_dirs() { + printf("Creating directory /bin/directory-created-by-event-generator-sh...\n"); + if (mkdir("/bin/directory-created-by-event-generator-sh", 0644) != 0) + { + fprintf(stderr, "Could not create directory \"/bin/directory-created-by-event-generator-sh\": %s\n", strerror(errno)); + } +} + +void change_thread_namespace() { + printf("Calling setns() to change namespaces...\n"); + // It doesn't matter that the arguments to setns are + // bogus. It's the attempt to call it that will trigger the + // rule. + setns(0, 0); +} + +void system_user_interactive() { + pid_t child; + + // Fork a child and do everything in the child. + if ((child = fork()) == 0) + { + become_user("daemon"); + char *argv[] = {(char *)"/bin/login", NULL}; + char *env[] = {NULL}; + spawn("/bin/login", argv, env); + exit(0); + } + else + { + int status; + waitpid(child, &status, 0); + } +} + +void network_activity() { + printf("Opening a listening socket on port 8192...\n"); + int rc; + int sock = socket(PF_INET, SOCK_DGRAM, 0); + struct sockaddr_in localhost; + + localhost.sin_family = AF_INET; + localhost.sin_port = htons(8192); + inet_aton("127.0.0.1", &(localhost.sin_addr)); + + if((rc = bind(sock, (struct sockaddr *) &localhost, sizeof(localhost))) != 0) + { + fprintf(stderr, "Could not bind listening socket to localhost: %s\n", strerror(errno)); + return; + } + + listen(sock, 1); + + close(sock); +} + +void system_procs_network_activity() { + printf("Becoming the program \"sha1sum\" and then performing network activity\n"); + respawn("./sha1sum", "network_activity", "0"); +} + +void non_sudo_setuid() { + pid_t child; + + // Fork a child and do everything in the child. + if ((child = fork()) == 0) + { + // First setuid to something non-root. Then try to setuid back to root. + become_user("daemon"); + become_user("root"); + exit(0); + } + else + { + int status; + waitpid(child, &status, 0); + } +} + +void create_files_below_dev() { + printf("Creating /dev/created-by-event-generator-sh...\n"); + touch("/dev/created-by-event-generator-sh"); +} + +void exec_ls() +{ + char *argv[] = {(char *)"/bin/ls", NULL}; + char *env[] = {NULL}; + spawn("/bin/ls", argv, env); +} + +void user_mgmt_binaries() { + printf("Becoming the program \"vipw\" and then running the program /bin/ls\n"); + printf("NOTE: does not result in a falco notification in containers\n"); + respawn("./vipw", "exec_ls", "0"); +} + +typedef void (*action_t)(); + +map defined_actions = {{"write_binary_dir", write_binary_dir}, + {"write_etc", write_etc}, + {"read_sensitive_file", read_sensitive_file}, + {"read_sensitive_file_after_startup", read_sensitive_file_after_startup}, + {"write_rpm_database", write_rpm_database}, + {"spawn_shell", spawn_shell}, + {"db_program_spawn_process", db_program_spawn_process}, + {"modify_binary_dirs", modify_binary_dirs}, + {"mkdir_binary_dirs", mkdir_binary_dirs}, + {"change_thread_namespace", change_thread_namespace}, + {"system_user_interactive", system_user_interactive}, + {"network_activity", network_activity}, + {"system_procs_network_activity", system_procs_network_activity}, + {"non_sudo_setuid", non_sudo_setuid}, + {"create_files_below_dev", create_files_below_dev}, + {"exec_ls", exec_ls}, + {"user_mgmt_binaries", user_mgmt_binaries}}; + + +void create_symlinks(const char *program) +{ + int rc; + + // Some actions depend on this program being re-run as + // different program names like 'mysqld', 'httpd', etc. This + // sets up all the required symlinks. + const char *progs[] = {"./httpd", "./mysqld", "./sha1sum", "./vipw", NULL}; + + for (unsigned int i=0; progs[i] != NULL; i++) + { + unlink(progs[i]); + + if ((rc = symlink(program, progs[i])) != 0) + { + fprintf(stderr, "Could not link \"./event_generator\" to \"%s\": %s\n", progs[i], strerror(errno)); + } + } +} + +void run_actions(map &actions, int interval, bool once) +{ + while (true) + { + for (auto action : actions) + { + sleep(interval); + printf("***Action %s\n", action.first.c_str()); + action.second(); + } + if(once) + { + break; + } + } +} + +int main(int argc, char **argv) +{ + map actions; + int op; + int long_index = 0; + int interval = 1; + bool once = false; + map::iterator it; + + static struct option long_options[] = + { + {"help", no_argument, 0, 'h' }, + {"action", required_argument, 0, 'a' }, + {"interval", required_argument, 0, 'i' }, + {"once", no_argument, 0, 'o' }, + + {0, 0} + }; + + // + // Parse the args + // + while((op = getopt_long(argc, argv, + "ha:i:l:", + long_options, &long_index)) != -1) + { + switch(op) + { + case 'h': + usage(argv[0]); + exit(1); + case 'a': + 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); + break; + case 'i': + interval = atoi(optarg); + break; + case 'o': + once = true; + break; + default: + usage(argv[0]); + exit(1); + } + } + + if(actions.size() == 0) + { + actions = defined_actions; + } + + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + // Only create symlinks when running as the program event_generator + if (strstr(argv[0], "generator")) + { + create_symlinks(argv[0]); + } + + run_actions(actions, interval, once); +} From 65f3725e76897d07a179eafbe6b715b58e260de0 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Fri, 12 Aug 2016 14:17:13 -0700 Subject: [PATCH 2/2] Improve ruleset based on falco event-generator. Improve ruleset after using with falco event_generator: - Instead of assuming all shells are bash, add a list shell_binaries and macro shell_procs, and replace references to bash with shell_procs. This revealed some other programs that can spawn shells. - Add "login" as an interactive command. systemd-login isn't in alpine linux, which is the linux distro used for the container. - Move read_sensitive_file_untrusted before read_sensitive_file_trusted_after_startup, so it can hit first. --- rules/falco_rules.yaml | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/rules/falco_rules.yaml b/rules/falco_rules.yaml index e576fb943ab..ea0f941f35c 100644 --- a/rules/falco_rules.yaml +++ b/rules/falco_rules.yaml @@ -54,6 +54,12 @@ - macro: linux_so_dirs condition: ubuntu_so_dirs or centos_so_dirs or fd.name=/etc/ld.so.cache +- list: shell_binaries + items: [bash, csh, ksh, sh, tcsh, zsh, dash] + +- macro: shell_procs + condition: proc.name in (shell_binaries) + - list: coreutils_binaries items: [ truncate, sha1sum, numfmt, fmt, fold, uniq, cut, who, @@ -161,7 +167,7 @@ - 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 or proc.name=login) - macro: syslog condition: fd.name in (/dev/log, /run/systemd/journal/syslog) - list: cron_binaries @@ -203,18 +209,18 @@ 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: sensitive_files and open_read and not proc.name in (user_mgmt_binaries, userexec_binaries, package_mgmt_binaries, cron_binaries, iptables, ps, lsb_release, check-new-relea, dumpe2fs, accounts-daemon, bash, sshd) and not proc.cmdline contains /usr/bin/mandb - 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: sensitive_files and open_read and server_procs and not proc_is_new 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: 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: sensitive_files and open_read and not proc.name in (user_mgmt_binaries, userexec_binaries, package_mgmt_binaries, cron_binaries, iptables, ps, lsb_release, check-new-relea, dumpe2fs, accounts-daemon, shell_binaries, sshd) and not proc.cmdline contains /usr/bin/mandb + output: "Sensitive file opened for reading by non-trusted program (user=%user.name name=%proc.name command=%proc.cmdline file=%fd.name)" + priority: WARNING + # Only let rpm-related programs write to the rpm database - rule: write_rpm_database desc: an attempt to write to the rpm database by any non-rpm related program @@ -264,7 +270,7 @@ - rule: run_shell_untrusted desc: an attempt to spawn a shell by a non-shell program. Exceptions are made for trusted binaries. - condition: spawned_process and not container and proc.name = bash and proc.pname exists and not proc.pname in (cron_binaries, bash, sshd, sudo, docker_binaries, su, tmux, screen, emacs, systemd, login, flock, fbash, nginx, monit, supervisord, dragent) + condition: spawned_process and not container and shell_procs and proc.pname exists and not proc.pname in (cron_binaries, shell_binaries, sshd, sudo, docker_binaries, su, tmux, screen, emacs, systemd, login, flock, fbash, nginx, monit, supervisord, dragent, aws, initdb, docker-compose) output: "Shell spawned by untrusted binary (user=%user.name shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)" priority: WARNING @@ -281,7 +287,7 @@ - rule: run_shell_in_container desc: a shell was spawned by a non-shell program in a container. Container entrypoints are excluded. - condition: spawned_process and container and proc.name = bash and proc.pname exists and not proc.pname in (sh, bash, docker_binaries) + condition: spawned_process and container and shell_procs and proc.pname exists and not proc.pname in (shell_binaries, docker_binaries, initdb, pg_ctl) 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