mirror of
https://github.com/falcosecurity/falco.git
synced 2025-06-30 16:42:14 +00:00
Add additional rules/tests for pipe installers.
Add additional rules related to using pipe installers within a fbash session: - Modify write_etc to only trigger if *not* in a fbash session. There's a new rule write_etc_installer which has the same conditions when in a fbash session, logging at INFO severity. - A new rule write_rpm_database warns if any non package management program tries to write below /var/lib/rpm. - Add a new warning if any program below a fbash session tries to open an outbound network connection on ports other than http(s) and dns. - Add INFO level messages when programs in a fbash session try to run package management binaries (rpm,yum,etc) or service management (systemctl,chkconfig,etc) binaries. In order to test these new INFO level rules, make up a third class of trace files traces-info.zip containing trace files that should result in info-level messages. To differentiate warning and info level detection, add an attribute to the multiplex file "detect_level", which is "Warning" for the files in traces-positive and "Info" for the files in traces-info. Modify falco_test.py to look specifically for a non-zero count for the given detect_level. Doing this exposed a bug in the way the level-specific counts were being recorded--they were keeping counts by level name, not number. Fix that.
This commit is contained in:
parent
31c87c295a
commit
fc6d775e5b
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,6 +4,8 @@ test/falco_test.pyc
|
|||||||
test/falco_tests.yaml
|
test/falco_tests.yaml
|
||||||
test/traces-negative
|
test/traces-negative
|
||||||
test/traces-positive
|
test/traces-positive
|
||||||
|
test/traces-info
|
||||||
|
test/job-results
|
||||||
|
|
||||||
userspace/falco/lua/re.lua
|
userspace/falco/lua/re.lua
|
||||||
userspace/falco/lua/lpeg.so
|
userspace/falco/lua/lpeg.so
|
||||||
|
@ -124,7 +124,7 @@
|
|||||||
# The truncated dpkg-preconfigu is intentional, process names are
|
# The truncated dpkg-preconfigu is intentional, process names are
|
||||||
# truncated at the sysdig level.
|
# truncated at the sysdig level.
|
||||||
- macro: package_mgmt_binaries
|
- macro: package_mgmt_binaries
|
||||||
condition: proc.name in (dpkg, dpkg-preconfigu, rpm, yum)
|
condition: proc.name in (dpkg, dpkg-preconfigu, rpm, rpmkey, yum)
|
||||||
|
|
||||||
# A canonical set of processes that run other programs with different
|
# A canonical set of processes that run other programs with different
|
||||||
# privileges or as a different user.
|
# privileges or as a different user.
|
||||||
@ -141,7 +141,7 @@
|
|||||||
condition: proc.name in (sendmail, sendmail-msp, postfix, procmail)
|
condition: proc.name in (sendmail, sendmail-msp, postfix, procmail)
|
||||||
|
|
||||||
- macro: sensitive_files
|
- 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 in (/etc/sudoers.d, /etc/pam.d) or fd.name = /etc/pam.conf)
|
||||||
|
|
||||||
# Indicates that the process is new. Currently detected using time
|
# Indicates that the process is new. Currently detected using time
|
||||||
# since process was started, using a threshold of 5 seconds.
|
# since process was started, using a threshold of 5 seconds.
|
||||||
@ -194,11 +194,18 @@
|
|||||||
priority: WARNING
|
priority: WARNING
|
||||||
|
|
||||||
- rule: write_etc
|
- rule: write_etc
|
||||||
desc: an attempt to write to any file below /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
|
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
|
||||||
output: "File below /etc opened for writing (user=%user.name command=%proc.cmdline file=%fd.name)"
|
output: "File below /etc opened for writing (user=%user.name command=%proc.cmdline file=%fd.name)"
|
||||||
priority: WARNING
|
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
|
||||||
|
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
|
- 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.
|
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 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
|
||||||
@ -211,6 +218,13 @@
|
|||||||
output: "Sensitive file opened for reading by trusted program after startup (user=%user.name command=%proc.cmdline file=%fd.name)"
|
output: "Sensitive file opened for reading by trusted program after startup (user=%user.name command=%proc.cmdline file=%fd.name)"
|
||||||
priority: WARNING
|
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
|
||||||
|
condition: open_write and not proc.name in (rpm,rpmkey,yum) and fd.directory=/var/lib/rpm
|
||||||
|
output: "Rpm database opened for writing by a non-rpm program (command=%proc.cmdline file=%fd.name)"
|
||||||
|
priority: WARNING
|
||||||
|
|
||||||
- rule: db_program_spawned_process
|
- 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.
|
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: db_server_binaries_parent and not db_server_binaries and spawned_process
|
||||||
@ -312,17 +326,45 @@
|
|||||||
|
|
||||||
# fbash is a small shell script that runs bash, and is suitable for use in curl <curl> | fbash installers.
|
# fbash is a small shell script that runs bash, and is suitable for use in curl <curl> | fbash installers.
|
||||||
- rule: installer_bash_starts_network_server
|
- rule: installer_bash_starts_network_server
|
||||||
desc: an attempt by any program that is in a session led by fbash to start listening for network connections
|
desc: an attempt by a program in a pipe installer session to start listening for network connections
|
||||||
condition: evt.type=listen and proc.sname=fbash
|
condition: evt.type=listen and proc.sname=fbash
|
||||||
output: "Unexpected listen call by a process in a fbash session (command=%proc.cmdline)"
|
output: "Unexpected listen call by a process in a fbash session (command=%proc.cmdline)"
|
||||||
priority: WARNING
|
priority: WARNING
|
||||||
|
|
||||||
- rule: installer_bash_starts_session
|
- rule: installer_bash_starts_session
|
||||||
desc: an attempt by any program that is in a session led by fbash to start a new session
|
desc: an attempt by a program in a pipe installer session to start a new session
|
||||||
condition: evt.type=setsid and proc.sname=fbash
|
condition: evt.type=setsid and proc.sname=fbash
|
||||||
output: "Unexpected setsid call by a process in fbash session (command=%proc.cmdline)"
|
output: "Unexpected setsid call by a process in fbash session (command=%proc.cmdline)"
|
||||||
priority: WARNING
|
priority: WARNING
|
||||||
|
|
||||||
|
- rule: installer_bash_non_https_connection
|
||||||
|
desc: an attempt by a program in a pipe installer session to make an outgoing connection on a non-http(s) port
|
||||||
|
condition: outbound and not fd.sport in (80, 443, 53) and proc.sname=fbash
|
||||||
|
output: "Outbound connection on non-http(s) port by a process in a fbash session (command=%proc.cmdline connection=%fd.name)"
|
||||||
|
priority: WARNING
|
||||||
|
|
||||||
|
# It'd be nice if we could warn when processes in a fbash session try
|
||||||
|
# to download from any nonstandard location? This is probably blocked
|
||||||
|
# on https://github.com/draios/falco/issues/88 though.
|
||||||
|
|
||||||
|
# Notice when processes try to run chkconfig/systemctl.... to install a service.
|
||||||
|
# Note: this is not a WARNING, as you'd expect some service management
|
||||||
|
# as a part of doing the installation.
|
||||||
|
- rule: installer_bash_manages_service
|
||||||
|
desc: an attempt by a program in a pipe installer session to manage a system service (systemd/chkconfig)
|
||||||
|
condition: evt.type=execve and proc.name in (chkconfig, systemctl) and proc.sname=fbash
|
||||||
|
output: "Service management program run by process in a fbash session (command=%proc.cmdline)"
|
||||||
|
priority: INFO
|
||||||
|
|
||||||
|
# Notice when processes try to run any package management binary within a fbash session.
|
||||||
|
# Note: this is not a WARNING, as you'd expect some package management
|
||||||
|
# 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
|
||||||
|
output: "Package management program run by process in a fbash session (command=%proc.cmdline)"
|
||||||
|
priority: INFO
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# Application-Related Rules
|
# Application-Related Rules
|
||||||
###########################
|
###########################
|
||||||
|
@ -18,6 +18,9 @@ class FalcoTest(Test):
|
|||||||
self.should_detect = self.params.get('detect', '*')
|
self.should_detect = self.params.get('detect', '*')
|
||||||
self.trace_file = self.params.get('trace_file', '*')
|
self.trace_file = self.params.get('trace_file', '*')
|
||||||
|
|
||||||
|
if self.should_detect:
|
||||||
|
self.detect_level = self.params.get('detect_level', '*')
|
||||||
|
|
||||||
# Doing this in 2 steps instead of simply using
|
# Doing this in 2 steps instead of simply using
|
||||||
# module_is_loaded to avoid logging lsmod output to the log.
|
# module_is_loaded to avoid logging lsmod output to the log.
|
||||||
lsmod_output = process.system_output("lsmod", verbose=False)
|
lsmod_output = process.system_output("lsmod", verbose=False)
|
||||||
@ -44,17 +47,29 @@ class FalcoTest(Test):
|
|||||||
cmd, res.exit_status))
|
cmd, res.exit_status))
|
||||||
|
|
||||||
# Get the number of events detected.
|
# Get the number of events detected.
|
||||||
res = re.search('Events detected: (\d+)', res.stdout)
|
match = re.search('Events detected: (\d+)', res.stdout)
|
||||||
if res is None:
|
if match is None:
|
||||||
self.fail("Could not find a line 'Events detected: <count>' in falco output")
|
self.fail("Could not find a line 'Events detected: <count>' in falco output")
|
||||||
|
|
||||||
events_detected = int(res.group(1))
|
events_detected = int(match.group(1))
|
||||||
|
|
||||||
if not self.should_detect and events_detected > 0:
|
if not self.should_detect and events_detected > 0:
|
||||||
self.fail("Detected {} events when should have detected none".format(events_detected))
|
self.fail("Detected {} events when should have detected none".format(events_detected))
|
||||||
|
|
||||||
if self.should_detect and events_detected == 0:
|
if self.should_detect:
|
||||||
self.fail("Detected {} events when should have detected > 0".format(events_detected))
|
if events_detected == 0:
|
||||||
|
self.fail("Detected {} events when should have detected > 0".format(events_detected))
|
||||||
|
|
||||||
|
level_line = '{}: (\d+)'.format(self.detect_level)
|
||||||
|
match = re.search(level_line, res.stdout)
|
||||||
|
|
||||||
|
if match is None:
|
||||||
|
self.fail("Could not find a line '{}: <count>' in falco output".format(self.detect_level))
|
||||||
|
|
||||||
|
events_detected = int(match.group(1))
|
||||||
|
|
||||||
|
if not events_detected > 0:
|
||||||
|
self.fail("Detected {} events at level {} when should have detected > 0".format(events_detected, self.detect_level))
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -5,7 +5,8 @@ SCRIPTDIR=$(dirname $SCRIPT)
|
|||||||
MULT_FILE=$SCRIPTDIR/falco_tests.yaml
|
MULT_FILE=$SCRIPTDIR/falco_tests.yaml
|
||||||
|
|
||||||
function download_trace_files() {
|
function download_trace_files() {
|
||||||
for TRACE in traces-positive traces-negative ; do
|
for TRACE in traces-positive traces-negative traces-info ; do
|
||||||
|
rm -rf $SCRIPTDIR/$TRACE
|
||||||
curl -so $SCRIPTDIR/$TRACE.zip https://s3.amazonaws.com/download.draios.com/falco-tests/$TRACE.zip &&
|
curl -so $SCRIPTDIR/$TRACE.zip https://s3.amazonaws.com/download.draios.com/falco-tests/$TRACE.zip &&
|
||||||
unzip -d $SCRIPTDIR $SCRIPTDIR/$TRACE.zip &&
|
unzip -d $SCRIPTDIR $SCRIPTDIR/$TRACE.zip &&
|
||||||
rm -rf $SCRIPTDIR/$TRACE.zip
|
rm -rf $SCRIPTDIR/$TRACE.zip
|
||||||
@ -21,6 +22,7 @@ function prepare_multiplex_file() {
|
|||||||
cat << EOF >> $MULT_FILE
|
cat << EOF >> $MULT_FILE
|
||||||
$NAME:
|
$NAME:
|
||||||
detect: True
|
detect: True
|
||||||
|
detect_level: Warning
|
||||||
trace_file: $trace
|
trace_file: $trace
|
||||||
EOF
|
EOF
|
||||||
done
|
done
|
||||||
@ -35,6 +37,17 @@ EOF
|
|||||||
EOF
|
EOF
|
||||||
done
|
done
|
||||||
|
|
||||||
|
for trace in $SCRIPTDIR/traces-info/*.scap ; do
|
||||||
|
[ -e "$trace" ] || continue
|
||||||
|
NAME=`basename $trace .scap`
|
||||||
|
cat << EOF >> $MULT_FILE
|
||||||
|
$NAME:
|
||||||
|
detect: True
|
||||||
|
detect_level: Informational
|
||||||
|
trace_file: $trace
|
||||||
|
EOF
|
||||||
|
done
|
||||||
|
|
||||||
echo "Contents of $MULT_FILE:"
|
echo "Contents of $MULT_FILE:"
|
||||||
cat $MULT_FILE
|
cat $MULT_FILE
|
||||||
}
|
}
|
||||||
|
@ -102,14 +102,13 @@ function set_output(output_format, state)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function priority(s)
|
local function priority(s)
|
||||||
valid_levels = {"emergency", "alert", "critical", "error", "warning", "notice", "informational", "debug"}
|
|
||||||
s = string.lower(s)
|
s = string.lower(s)
|
||||||
for i,v in ipairs(valid_levels) do
|
for i,v in ipairs(output.levels) do
|
||||||
if (string.find(v, "^"..s)) then
|
if (string.find(string.lower(v), "^"..s)) then
|
||||||
return i - 1 -- (syslog levels start at 0, lua indices start at 1)
|
return i - 1 -- (syslog levels start at 0, lua indices start at 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
error("Invalid severity level: "..level)
|
error("Invalid severity level: "..s)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Note that the rules_by_name and rules_by_idx refer to the same rule
|
-- Note that the rules_by_name and rules_by_idx refer to the same rule
|
||||||
@ -232,8 +231,8 @@ end
|
|||||||
|
|
||||||
local rule_output_counts = {total=0, by_level={}, by_name={}}
|
local rule_output_counts = {total=0, by_level={}, by_name={}}
|
||||||
|
|
||||||
for idx, level in ipairs(output.levels) do
|
for idx=0,table.getn(output.levels)-1,1 do
|
||||||
rule_output_counts[level] = 0
|
rule_output_counts.by_level[idx] = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
function on_event(evt_, rule_id)
|
function on_event(evt_, rule_id)
|
||||||
@ -265,8 +264,8 @@ function print_stats()
|
|||||||
print("Rule counts by severity:")
|
print("Rule counts by severity:")
|
||||||
for idx, level in ipairs(output.levels) do
|
for idx, level in ipairs(output.levels) do
|
||||||
-- To keep the output concise, we only print 0 counts for error, warning, and info levels
|
-- 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
|
if rule_output_counts.by_level[idx-1] > 0 or level == "Error" or level == "Warning" or level == "Informational" then
|
||||||
print (" "..level..": "..rule_output_counts[level])
|
print (" "..level..": "..rule_output_counts.by_level[idx-1])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user