diff --git a/rules/falco_rules.yaml b/rules/falco_rules.yaml index c78e62f3d14..bc3c1ced204 100644 --- a/rules/falco_rules.yaml +++ b/rules/falco_rules.yaml @@ -2381,13 +2381,29 @@ WARNING tags: [process, mitre_persistence] -- rule: Delete Bash History - desc: Detect bash history deletion - condition: > - ((spawned_process and proc.name in (shred, rm, mv) and proc.args contains "bash_history") or - (open_write and fd.name contains "bash_history" and evt.arg.flags contains "O_TRUNC")) +- rule: Delete or rename shell history + desc: Detect shell history deletion + condition: > + (modify and ( + evt.arg.name contains "bash_history" or + evt.arg.name contains "zsh_history" or + evt.arg.name contains "fish_read_history" or + evt.arg.name endswith "fish_history" or + evt.arg.oldpath contains "bash_history" or + evt.arg.oldpath contains "zsh_history" or + evt.arg.oldpath contains "fish_read_history" or + evt.arg.oldpath endswith "fish_history" or + evt.arg.path contains "bash_history" or + evt.arg.path contains "zsh_history" or + evt.arg.path contains "fish_read_history" or + evt.arg.path endswith "fish_history")) or + (open_write and ( + fd.name contains "bash_history" or + fd.name contains "zsh_history" or + fd.name contains "fish_read_history" or + fd.name endswith "fish_history") and evt.arg.flags contains "O_TRUNC") output: > - Bash history has been deleted (user=%user.name command=%proc.cmdline file=%fd.name %container.info) + Shell history had been deleted or renamed (user=%user.name type=%evt.type command=%proc.cmdline fd.name=%fd.name name=%evt.arg.name path=%evt.arg.path oldpath=%evt.arg.oldpath %container.info) priority: WARNING tag: [process, mitre_defense_evation] @@ -2453,6 +2469,103 @@ Symlinks created over senstivie files (user=%user.name command=%proc.cmdline target=%evt.arg.target linkpath=%evt.arg.linkpath parent_process=%proc.pname) priority: NOTICE tags: [file, mitre_exfiltration] + +- list: miner_ports + items: [ + 25, 3333, 3334, 3335, 3336, 3357, 4444, + 5555, 5556, 5588, 5730, 6099, 6666, 7777, + 7778, 8000, 8001, 8008, 8080, 8118, 8333, + 8888, 8899, 9332, 9999, 14433, 14444, + 45560, 45700 + ] + +- list: miner_domains + items: [ + "asia1.ethpool.org","ca.minexmr.com", + "cn.stratum.slushpool.com","de.minexmr.com", + "eth-ar.dwarfpool.com","eth-asia.dwarfpool.com", + "eth-asia1.nanopool.org","eth-au.dwarfpool.com", + "eth-au1.nanopool.org","eth-br.dwarfpool.com", + "eth-cn.dwarfpool.com","eth-cn2.dwarfpool.com", + "eth-eu.dwarfpool.com","eth-eu1.nanopool.org", + "eth-eu2.nanopool.org","eth-hk.dwarfpool.com", + "eth-jp1.nanopool.org","eth-ru.dwarfpool.com", + "eth-ru2.dwarfpool.com","eth-sg.dwarfpool.com", + "eth-us-east1.nanopool.org","eth-us-west1.nanopool.org", + "eth-us.dwarfpool.com","eth-us2.dwarfpool.com", + "eu.stratum.slushpool.com","eu1.ethermine.org", + "eu1.ethpool.org","fr.minexmr.com", + "mine.moneropool.com","mine.xmrpool.net", + "pool.minexmr.com","pool.monero.hashvault.pro", + "pool.supportxmr.com","sg.minexmr.com", + "sg.stratum.slushpool.com","stratum-eth.antpool.com", + "stratum-ltc.antpool.com","stratum-zec.antpool.com", + "stratum.antpool.com","us-east.stratum.slushpool.com", + "us1.ethermine.org","us1.ethpool.org", + "us2.ethermine.org","us2.ethpool.org", + "xmr-asia1.nanopool.org","xmr-au1.nanopool.org", + "xmr-eu1.nanopool.org","xmr-eu2.nanopool.org", + "xmr-jp1.nanopool.org","xmr-us-east1.nanopool.org", + "xmr-us-west1.nanopool.org","xmr.crypto-pool.fr", + "xmr.pool.minergate.com" + ] + +- list: https_miner_domains + items: [ + "ca.minexmr.com", + "cn.stratum.slushpool.com", + "de.minexmr.com", + "fr.minexmr.com", + "mine.moneropool.com", + "mine.xmrpool.net", + "pool.minexmr.com", + "sg.minexmr.com", + "stratum-eth.antpool.com", + "stratum-ltc.antpool.com", + "stratum-zec.antpool.com", + "stratum.antpool.com", + "xmr.crypto-pool.fr" + ] + +- list: http_miner_domains + items: [ + "ca.minexmr.com", + "de.minexmr.com", + "fr.minexmr.com", + "mine.moneropool.com", + "mine.xmrpool.net", + "pool.minexmr.com", + "sg.minexmr.com", + "xmr.crypto-pool.fr" + ] + +# Add rule based on crypto mining IOCs +- macro: minerpool_https + condition: (fd.sport="443" and fd.sip.name in (https_miner_domains)) + +- macro: minerpool_http + condition: (fd.sport="80" and fd.sip.name in (http_miner_domains)) + +- macro: minerpool_other + condition: (fd.sport in (miner_ports) and fd.sip.name in (miner_domains)) + +- macro: net_miner_pool + condition: (evt.type in (sendto, sendmsg) and evt.dir=< and ((minerpool_http) or (minerpool_https) or (minerpool_other))) + +- rule: Detect outbound connections to common miner pool ports + desc: Miners typically connect to miner pools on common ports. + condition: net_miner_pool + output: Outbound connection to IP/Port flagged by cryptoioc.ch (command=%proc.cmdline port=%fd.rport ip=%fd.rip container=%container.info image=%container.image.repository) + priority: CRITICAL + tags: [network, mitre_execution] + +- rule: Detect crypto miners using the Stratum protocol + desc: Miners typically specify the mining pool to connect to with a URI that begins with 'stratum+tcp' + condition: spawned_process and proc.cmdline contains "stratum+tcp" + output: Possible miner running (command=%proc.cmdline container=%container.info image=%container.image.repository) + priority: CRITICAL + tags: [process, mitre_execution] + # Application rules have moved to application_rules.yaml. Please look # there if you want to enable them by adding to # falco_rules.local.yaml. diff --git a/test/falco_tests.yaml b/test/falco_tests.yaml index 7a58aa4a740..a6cb7eeab8e 100644 --- a/test/falco_tests.yaml +++ b/test/falco_tests.yaml @@ -123,6 +123,18 @@ trace_files: !mux trace_file: trace_files/cat_write.scap all_events: True + multiple_docs: + detect: True + detect_level: + - WARNING + - INFO + - ERROR + rules_file: + - rules/single_rule.yaml + - rules/double_rule.yaml + trace_file: trace_files/cat_write.scap + all_events: True + rules_directory: detect: True detect_level: @@ -435,6 +447,35 @@ trace_files: !mux - rules/invalid_append_macro.yaml trace_file: trace_files/cat_write.scap + invalid_overwrite_macro_multiple_docs: + exit_status: 1 + stdout_is: |+ + Compilation error when compiling "foo": Undefined macro 'foo' used in filter. + --- + - macro: some macro + condition: foo + append: false + --- + validate_rules_file: + - rules/invalid_overwrite_macro_multiple_docs.yaml + trace_file: trace_files/cat_write.scap + + invalid_append_macro_multiple_docs: + exit_status: 1 + stdout_is: |+ + Compilation error when compiling "evt.type=execve foo": 17: syntax error, unexpected 'foo', expecting 'or', 'and' + --- + - macro: some macro + condition: evt.type=execve + + - macro: some macro + condition: foo + append: true + --- + validate_rules_file: + - rules/invalid_append_macro_multiple_docs.yaml + trace_file: trace_files/cat_write.scap + invalid_overwrite_rule: exit_status: 1 stdout_contains: |+ @@ -477,6 +518,44 @@ trace_files: !mux - rules/invalid_append_rule.yaml trace_file: trace_files/cat_write.scap + invalid_overwrite_rule_multiple_docs: + exit_status: 1 + stdout_is: |+ + Undefined macro 'bar' used in filter. + --- + - rule: some rule + desc: some desc + condition: bar + output: some output + priority: INFO + append: false + --- + validate_rules_file: + - rules/invalid_overwrite_rule_multiple_docs.yaml + trace_file: trace_files/cat_write.scap + + invalid_append_rule_multiple_docs: + exit_status: 1 + stdout_contains: |+ + Compilation error when compiling "evt.type=open bar": 15: syntax error, unexpected 'bar', expecting 'or', 'and' + --- + - rule: some rule + desc: some desc + condition: evt.type=open + output: some output + priority: INFO + + - rule: some rule + desc: some desc + condition: bar + output: some output + priority: INFO + append: true + --- + validate_rules_file: + - rules/invalid_append_rule_multiple_docs.yaml + trace_file: trace_files/cat_write.scap + invalid_missing_rule_name: exit_status: 1 stdout_is: |+ diff --git a/test/rules/invalid_append_macro_multiple_docs.yaml b/test/rules/invalid_append_macro_multiple_docs.yaml new file mode 100644 index 00000000000..60a4f88c994 --- /dev/null +++ b/test/rules/invalid_append_macro_multiple_docs.yaml @@ -0,0 +1,8 @@ +--- +- macro: some macro + condition: evt.type=execve +--- +- macro: some macro + condition: foo + append: true + diff --git a/test/rules/invalid_append_rule_multiple_docs.yaml b/test/rules/invalid_append_rule_multiple_docs.yaml new file mode 100644 index 00000000000..92c5e6e4b99 --- /dev/null +++ b/test/rules/invalid_append_rule_multiple_docs.yaml @@ -0,0 +1,13 @@ +--- +- rule: some rule + desc: some desc + condition: evt.type=open + output: some output + priority: INFO +--- +- rule: some rule + desc: some desc + condition: bar + output: some output + priority: INFO + append: true \ No newline at end of file diff --git a/test/rules/invalid_overwrite_macro_multiple_docs.yaml b/test/rules/invalid_overwrite_macro_multiple_docs.yaml new file mode 100644 index 00000000000..723312e979d --- /dev/null +++ b/test/rules/invalid_overwrite_macro_multiple_docs.yaml @@ -0,0 +1,8 @@ +--- +- macro: some macro + condition: evt.type=execve +--- +- macro: some macro + condition: foo + append: false + diff --git a/test/rules/invalid_overwrite_rule_multiple_docs.yaml b/test/rules/invalid_overwrite_rule_multiple_docs.yaml new file mode 100644 index 00000000000..eef86359bdb --- /dev/null +++ b/test/rules/invalid_overwrite_rule_multiple_docs.yaml @@ -0,0 +1,13 @@ +--- +- rule: some rule + desc: some desc + condition: evt.type=open + output: some output + priority: INFO +--- +- rule: some rule + desc: some desc + condition: bar + output: some output + priority: INFO + append: false \ No newline at end of file diff --git a/test/rules/multiple_docs.yaml b/test/rules/multiple_docs.yaml new file mode 100644 index 00000000000..1857ee075b9 --- /dev/null +++ b/test/rules/multiple_docs.yaml @@ -0,0 +1,66 @@ +--- +# +# Copyright (C) 2016-2018 Draios Inc dba Sysdig. +# +# This file is part of falco. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +- required_engine_version: 2 + +- list: cat_binaries + items: [cat] + +- list: cat_capable_binaries + items: [cat_binaries] + +- macro: is_cat + condition: proc.name in (cat_capable_binaries) + +- rule: open_from_cat + desc: A process named cat does an open + condition: evt.type=open and is_cat + output: "An open was seen (command=%proc.cmdline)" + priority: WARNING +--- +# +# Copyright (C) 2016-2018 Draios Inc dba Sysdig. +# +# This file is part of falco. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This ruleset depends on the is_cat macro defined in single_rule.yaml + +- rule: exec_from_cat + desc: A process named cat does execve + condition: evt.type=execve and is_cat + output: "An exec was seen (command=%proc.cmdline)" + priority: ERROR + +- rule: access_from_cat + desc: A process named cat does an access + condition: evt.type=access and is_cat + output: "An access was seen (command=%proc.cmdline)" + priority: INFO \ No newline at end of file diff --git a/userspace/engine/lua/rule_loader.lua b/userspace/engine/lua/rule_loader.lua index 42ab9f4048e..f7e9fa574a2 100644 --- a/userspace/engine/lua/rule_loader.lua +++ b/userspace/engine/lua/rule_loader.lua @@ -225,7 +225,23 @@ function split_lines(rules_content) return lines, indices end -function get_orig_yaml_obj(rules_lines, row, num_lines) +function get_orig_yaml_obj(rules_lines, row) + local ret = "" + + idx = row + while (idx <= #rules_lines) do + ret = ret..rules_lines[idx].."\n" + idx = idx + 1 + + if idx > #rules_lines or rules_lines[idx] == "" or string.sub(rules_lines[idx], 1, 1) == '-' then + break + end + end + + return ret +end + +function get_lines(rules_lines, row, num_lines) local ret = "" idx = row @@ -238,7 +254,7 @@ function get_orig_yaml_obj(rules_lines, row, num_lines) end function build_error(rules_lines, row, num_lines, err) - local ret = err.."\n---\n"..get_orig_yaml_obj(rules_lines, row, num_lines).."---" + local ret = err.."\n---\n"..get_lines(rules_lines, row, num_lines).."---" return ret end @@ -248,65 +264,19 @@ function build_error_with_context(ctx, err) return ret end -function load_rules(sinsp_lua_parser, - json_lua_parser, - rules_content, - rules_mgr, - verbose, - all_events, - extra, - replace_container_info, - min_priority) - - local required_engine_version = 0 - - local lines, indices = split_lines(rules_content) - - local status, rules = pcall(yaml.load, rules_content) - - if status == false then - local pat = "^([%d]+):([%d]+): " - -- rules is actually an error string - - local row = 0 - local col = 0 - - row, col = string.match(rules, pat) - if row ~= nil and col ~= nil then - rules = string.gsub(rules, pat, "") - end - - row = tonumber(row) - col = tonumber(col) - - return false, build_error(lines, row, 3, rules) - end - - if rules == nil then - -- An empty rules file is acceptable - return true, required_engine_version - end - - if type(rules) ~= "table" then - return false, build_error(lines, 1, 1, "Rules content is not yaml") - end - - -- Look for non-numeric indices--implies that document is not array - -- of objects. - for key, val in pairs(rules) do - if type(key) ~= "number" then - return false, build_error(lines, 1, 1, "Rules content is not yaml array of objects") - end - end +function load_rules_doc(rules_mgr, doc, load_state) -- Iterate over yaml list. In this pass, all we're doing is -- populating the set of rules, macros, and lists. We're not -- expanding/compiling anything yet. All that will happen in a -- second pass - for i,v in ipairs(rules) do + for i,v in ipairs(doc) do + + load_state.cur_item_idx = load_state.cur_item_idx + 1 -- Save back the original object as it appeared in the file. Will be used to provide context. - local context = get_orig_yaml_obj(lines, indices[i], (indices[i+1]-indices[i])) + local context = get_orig_yaml_obj(load_state.lines, + load_state.indices[load_state.cur_item_idx]) if (not (type(v) == "table")) then return false, build_error_with_context(context, "Unexpected element of type " ..type(v)..". Each element should be a yaml associative array.") @@ -315,8 +285,8 @@ function load_rules(sinsp_lua_parser, v['context'] = context if (v['required_engine_version']) then - required_engine_version = v['required_engine_version'] - if type(required_engine_version) ~= "number" then + load_state.required_engine_version = v['required_engine_version'] + if type(load_state.required_engine_version) ~= "number" then return false, build_error_with_context(v['context'], "Value of required_engine_version must be a number") end @@ -458,7 +428,7 @@ function load_rules(sinsp_lua_parser, error("Invalid priority level: "..v['priority']) end - if v['priority_num'] <= min_priority then + if v['priority_num'] <= load_state.min_priority then -- Note that we can overwrite rules, but the rules are still -- loaded in the order in which they first appeared, -- potentially across multiple files. @@ -483,7 +453,74 @@ function load_rules(sinsp_lua_parser, end end - -- We've now loaded all the rules, macros, and list. Now + return true, "" +end + +function load_rules(sinsp_lua_parser, + json_lua_parser, + rules_content, + rules_mgr, + verbose, + all_events, + extra, + replace_container_info, + min_priority) + + local load_state = {lines={}, indices={}, cur_item_idx=0, min_priority=min_priority, required_engine_version=0} + + load_state.lines, load_state.indices = split_lines(rules_content) + + local status, docs = pcall(yaml.load, rules_content, { all = true }) + + if status == false then + local pat = "^([%d]+):([%d]+): " + -- docs is actually an error string + + local row = 0 + local col = 0 + + row, col = string.match(docs, pat) + if row ~= nil and col ~= nil then + docs = string.gsub(docs, pat, "") + end + + row = tonumber(row) + col = tonumber(col) + + return false, build_error(load_state.lines, row, 3, docs) + end + + if docs == nil then + -- An empty rules file is acceptable + return true, load_state.required_engine_version + end + + if type(docs) ~= "table" then + return false, build_error(load_state.lines, 1, 1, "Rules content is not yaml") + end + + for docidx, doc in ipairs(docs) do + + if type(doc) ~= "table" then + return false, build_error(load_state.lines, 1, 1, "Rules content is not yaml") + end + + -- Look for non-numeric indices--implies that document is not array + -- of objects. + for key, val in pairs(doc) do + if type(key) ~= "number" then + return false, build_error(load_state.lines, 1, 1, "Rules content is not yaml array of objects") + end + end + + res, errstr = load_rules_doc(rules_mgr, doc, load_state) + + if not res then + return res, errstr + end + end + + -- We've now loaded all the rules, macros, and lists. Now -- compile/expand the rules, macros, and lists. We use -- ordered_rule_{lists,macros,names} to compile them in the order -- in which they appeared in the file(s). @@ -705,7 +742,7 @@ function load_rules(sinsp_lua_parser, io.flush() - return true, required_engine_version + return true, load_state.required_engine_version end local rule_fmt = "%-50s %s"