From ced8410d4379614d4761321d5bdea331626478d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20I=C3=B1iguez=20Goia?= Date: Fri, 24 Jan 2025 00:47:02 +0100 Subject: [PATCH] allow to use lists of md5s to block connections Besides domains, net ranges and IPs, now it's possible to filter connections by the MD5 of a binary, if it's enabled. --- daemon/rule/operator.go | 44 +++++++++++-------- daemon/rule/operator_lists.go | 79 ++++++++++++++++++++++------------- 2 files changed, 76 insertions(+), 47 deletions(-) diff --git a/daemon/rule/operator.go b/daemon/rule/operator.go index 3ec8f2e7..5c577f76 100644 --- a/daemon/rule/operator.go +++ b/daemon/rule/operator.go @@ -15,6 +15,7 @@ import ( "github.com/evilsocket/opensnitch/daemon/conman" "github.com/evilsocket/opensnitch/daemon/core" "github.com/evilsocket/opensnitch/daemon/log" + "github.com/evilsocket/opensnitch/daemon/procmon" ) // Type is the type of rule. @@ -64,8 +65,9 @@ const ( OpDomainsRegexpLists = Operand("lists.domains_regexp") OpIPLists = Operand("lists.ips") OpNetLists = Operand("lists.nets") + OpHashMD5Lists = Operand("lists.hash.md5") + // TODO - //OpHashMD5Lists = Operand("lists.hash.md5") //OpQuota = Operand("quota") //OpQuotaTxOver = Operand("quota.sent.over") // 1000b, 1kb, 1mb, 1gb, ... //OpQuotaRxOver = Operand("quota.recv.over") // 1000b, 1kb, 1mb, 1gb, ... @@ -225,7 +227,7 @@ func (o *Operator) Compile() error { return fmt.Errorf("Operand lists is empty, nothing to load: %s", o) } o.loadLists() - o.cb = o.domainsListCmp + o.cb = o.domainsListsCmp } else if o.Operand == OpDomainsRegexpLists { if o.Data == "" { return fmt.Errorf("Operand regexp lists is empty, nothing to load: %s", o) @@ -237,13 +239,19 @@ func (o *Operator) Compile() error { return fmt.Errorf("Operand ip lists is empty, nothing to load: %s", o) } o.loadLists() - o.cb = o.ipListCmp + o.cb = o.simpleListsCmp } else if o.Operand == OpNetLists { if o.Data == "" { return fmt.Errorf("Operand net lists is empty, nothing to load: %s", o) } o.loadLists() o.cb = o.ipNetCmp + } else if o.Operand == OpHashMD5Lists { + if o.Data == "" { + return fmt.Errorf("Operand lists.hash.md5 is empty, nothing to load: %s", o) + } + o.loadLists() + o.cb = o.simpleListsCmp } else if o.Operand == OpProcessHashMD5 || o.Operand == OpProcessHashSHA1 { o.cb = o.hashCmp } @@ -288,7 +296,15 @@ func (o *Operator) cmpNetwork(destIP interface{}) bool { return o.netMask.Contains(destIP.(net.IP)) } -func (o *Operator) domainsListCmp(v interface{}) bool { +func (o *Operator) matchListsCmp(msg, what string) bool { + if item, found := o.lists[what]; found { + log.Debug("%s: %s, %s", log.Red(msg), what, item) + return true + } + return false +} + +func (o *Operator) domainsListsCmp(v interface{}) bool { dstHost := v.(string) if dstHost == "" { return false @@ -299,26 +315,18 @@ func (o *Operator) domainsListCmp(v interface{}) bool { o.RLock() defer o.RUnlock() - if _, found := o.lists[dstHost]; found { - log.Debug("%s: %s, %s", log.Red("domain list match"), dstHost, o.lists[dstHost]) - return true - } - return false + return o.matchListsCmp("domains list match", dstHost) } -func (o *Operator) ipListCmp(v interface{}) bool { - dstIP := v.(string) - if dstIP == "" { +func (o *Operator) simpleListsCmp(v interface{}) bool { + what := v.(string) + if what == "" { return false } o.RLock() defer o.RUnlock() - if _, found := o.lists[dstIP]; found { - log.Debug("%s: %s, %s", log.Red("IP list match"), dstIP, o.lists[dstIP].(string)) - return true - } - return false + return o.matchListsCmp("simple list match", what) } func (o *Operator) ipNetCmp(dstIP interface{}) bool { @@ -393,6 +401,8 @@ func (o *Operator) Match(con *conman.Connection, hasChecksums bool) bool { return o.cb(con.DstHost) } else if o.Operand == OpIPLists { return o.cb(con.DstIP.String()) + } else if o.Operand == OpHashMD5Lists { + return o.cb(con.Process.Checksums[procmon.HashMD5]) } else if o.Operand == OpUserID || o.Operand == OpUserName { return o.cb(strconv.Itoa(con.Entry.UserId)) } else if o.Operand == OpDstNetwork { diff --git a/daemon/rule/operator_lists.go b/daemon/rule/operator_lists.go index 9fe0394e..01838093 100644 --- a/daemon/rule/operator_lists.go +++ b/daemon/rule/operator_lists.go @@ -104,32 +104,46 @@ func (o *Operator) StopMonitoringLists() { } } -func (o *Operator) readDomainsList(raw, fileName string) (dups uint64) { - log.Debug("Loading domains list: %s, size: %d", fileName, len(raw)) - lines := strings.Split(string(raw), "\n") - for _, domain := range lines { - if len(domain) < 9 { - continue - } - // exclude not valid lines - if domain[:7] != "0.0.0.0" && domain[:9] != "127.0.0.1" { - continue - } - host := domain[8:] - // exclude localhost entries - if domain[:9] == "127.0.0.1" { - host = domain[10:] - } - if host == "local" || host == "localhost" || host == "localhost.localdomain" || host == "broadcasthost" { - continue - } +func filterDomains(line, defValue string) (bool, string, string) { + if len(line) < 9 { + return true, line, defValue + } + // exclude not valid lines + if line[:7] != "0.0.0.0" && line[:9] != "127.0.0.1" { + return true, line, defValue + } + host := line[8:] + // exclude localhost entries + if line[:9] == "127.0.0.1" { + host = line[10:] + } + if host == "local" || host == "localhost" || host == "localhost.localdomain" || host == "broadcasthost" { + return true, line, defValue + } - host = core.Trim(host) - if _, found := o.lists[host]; found { + return false, host, defValue +} + +func filterSimple(line, hashPath string) (bool, string, string) { + // XXX: some lists may use TABs as separator + hash := strings.SplitN(line, " ", 2) + return false, hash[0], hash[1] +} + +func (o *Operator) readTupleList(raw, fileName string, filter func(line, defValue string) (bool, string, string)) (dups uint64) { + log.Debug("Loading list: %s, size: %d", fileName, len(raw)) + lines := strings.Split(string(raw), "\n") + for _, line := range lines { + skip, key, value := filter(line, fileName) + if skip || len(line) < 9 { + continue + } + key = core.Trim(key) + if _, found := o.lists[key]; found { dups++ continue } - o.lists[host] = fileName + o.lists[key] = value } lines = nil log.Info("%d domains loaded, %s", len(o.lists), fileName) @@ -187,22 +201,25 @@ func (o *Operator) readRegexpList(raw, fileName string) (dups uint64) { return dups } -func (o *Operator) readIPList(raw, fileName string) (dups uint64) { - log.Debug("Loading IPs list: %s, size: %d", fileName, len(raw)) +// A simple list is a list composed of one column with several entries, that +// don't require manipulation. +// It can be a list of IPs, domains, etc. +func (o *Operator) readSimpleList(raw, fileName string) (dups uint64) { + log.Debug("Loading simple list: %s, size: %d", fileName, len(raw)) lines := strings.Split(string(raw), "\n") for _, line := range lines { if line == "" || line[0] == '#' { continue } - ip := core.Trim(line) - if _, found := o.lists[ip]; found { + what := core.Trim(line) + if _, found := o.lists[what]; found { dups++ continue } - o.lists[ip] = fileName + o.lists[what] = fileName } lines = nil - log.Info("%d IPs loaded, %s", len(o.lists), fileName) + log.Info("%d entries loaded, %s", len(o.lists), fileName) return dups } @@ -236,13 +253,15 @@ func (o *Operator) readLists() error { } if o.Operand == OpDomainsLists { - dups += o.readDomainsList(string(raw), fileName) + dups += o.readTupleList(string(raw), fileName, filterDomains) } else if o.Operand == OpDomainsRegexpLists { dups += o.readRegexpList(string(raw), fileName) } else if o.Operand == OpNetLists { dups += o.readNetList(string(raw), fileName) } else if o.Operand == OpIPLists { - dups += o.readIPList(string(raw), fileName) + dups += o.readSimpleList(string(raw), fileName) + } else if o.Operand == OpHashMD5Lists { + dups += o.readSimpleList(string(raw), fileName) } else { log.Warning("Unknown lists operand type: %s", o.Operand) }