From 66760c318d2f065b16621f1301c04d35715f7400 Mon Sep 17 00:00:00 2001 From: James Lagermann Date: Fri, 13 Jun 2025 14:16:23 -0500 Subject: [PATCH 1/8] extract the CVE from the rule --- scripts/enrich.zeek | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/enrich.zeek b/scripts/enrich.zeek index 1199d07..c8694d6 100644 --- a/scripts/enrich.zeek +++ b/scripts/enrich.zeek @@ -61,6 +61,13 @@ event Suricata::connection_alert(c: connection, cve = extract_cve_sig(msg$alert$signature); } + # Look for CVE in rule, if not found yet. + if (cve == "" && msg$alert?$rule) + { + cve = extract_cve_sig(msg$alert$rule) + } + + # return if no CVE data found if ( cve == "" ) return; From 8441aaa4d92e55df05edf20a53125020f0d7326a Mon Sep 17 00:00:00 2001 From: James Lagermann Date: Fri, 13 Jun 2025 16:13:07 -0500 Subject: [PATCH 2/8] update formating Signed-off-by: James Lagermann --- scripts/enrich.zeek | 201 +++++++++++++++++++-------------------- scripts/extract_cve.zeek | 32 +++---- 2 files changed, 113 insertions(+), 120 deletions(-) diff --git a/scripts/enrich.zeek b/scripts/enrich.zeek index c8694d6..8929151 100644 --- a/scripts/enrich.zeek +++ b/scripts/enrich.zeek @@ -3,128 +3,121 @@ module CVEEnrichment; type Idx: record { - ip: addr; + ip: addr; }; type Val: record { - cve_list: string_set; - ## The ID of the known CVE on the vulnerable host. - cve: string &log &optional; - ## The hostname of the vulnerable host. - hostname: string &log &optional; - ## The unique identifier, assigned by the CVE information source, of the vulnerable host. - host_uid: string &log &optional; - ## The machine domain of the vulnerable host. - machine_domain: string &log &optional; - ## The Operating System version of the vulnerable host. - os_version: string &log &optional; - ## The source of the CVE information. - source: string &log &optional; - ## The criticality of the host. - criticality: string &log &optional; + cve_list: string_set; + ## The ID of the known CVE on the vulnerable host. + cve: string &log &optional; + ## The hostname of the vulnerable host. + hostname: string &log &optional; + ## The unique identifier, assigned by the CVE information source, of the vulnerable host. + host_uid: string &log &optional; + ## The machine domain of the vulnerable host. + machine_domain: string &log &optional; + ## The Operating System version of the vulnerable host. + os_version: string &log &optional; + ## The source of the CVE information. + source: string &log &optional; + ## The criticality of the host. + criticality: string &log &optional; }; global cve_data: table[addr] of Val = table(); event zeek_init() - { - Input::add_table([ $source="cve_data.tsv", $name="cve_data", $idx=Idx, - $val=Val, $destination=cve_data, $mode=Input::REREAD ]); - } + { + Input::add_table([ $source="cve_data.tsv", $name="cve_data", $idx=Idx, + $val=Val, $destination=cve_data, $mode=Input::REREAD ]); + } # Enrich the Suricata_corelight log. redef record Suricata::Info += { - orig_vulnerable_host: Val &log &optional; - resp_vulnerable_host: Val &log &optional; + orig_vulnerable_host: Val &log &optional; + resp_vulnerable_host: Val &log &optional; }; event Suricata::connection_alert(c: connection, msg: Corelight::Suricata::SuricataMsg) - { - if ( ! msg?$alert || ( ! msg$alert?$signature && ! msg$alert?$metadata ) ) - return; - - local orig = c$id$orig_h; - local resp = c$id$resp_h; - - local cve = ""; - - # Look for CVE in metadata. - if ( msg$alert?$metadata ) - { - cve = extract_cve_metadata(msg$alert$metadata); - } - - # Look for CVE in signature name, if it's not in metadata. - if ( cve == "" && msg$alert?$signature ) - { - cve = extract_cve_sig(msg$alert$signature); - } - - # Look for CVE in rule, if not found yet. - if (cve == "" && msg$alert?$rule) - { - cve = extract_cve_sig(msg$alert$rule) - } - - # return if no CVE data found - if ( cve == "" ) - return; - - # Some CVE's have "_" and some have "-". We normalize them to "-". - cve = subst_string(cve, "_", "-"); - cve = subst_string(cve, "cve", "CVE"); - - if ( orig in cve_data && cve in cve_data[orig]$cve_list ) - { - cve_data[orig]$cve = cve; - c$suricata_alert$orig_vulnerable_host = cve_data[orig]; - } - - if ( resp in cve_data && cve in cve_data[resp]$cve_list ) - { - cve_data[resp]$cve = cve; - c$suricata_alert$resp_vulnerable_host = cve_data[resp]; - } - } + { + if ( ! msg?$alert || ( ! msg$alert?$signature && ! msg$alert?$metadata ) ) + return; + + local orig = c$id$orig_h; + local resp = c$id$resp_h; + + local cve = ""; + + # Look for CVE in metadata. + if ( msg$alert?$metadata ) + cve = extract_cve_metadata(msg$alert$metadata); + + # Look for CVE in signature name, if it's not in metadata. + if ( cve == "" && msg$alert?$signature ) + cve = extract_cve_sig(msg$alert$signature); + + # Look for CVE in rule, if not found yet. + if ( cve == "" && msg$alert?$rule ) + cve = extract_cve_sig(msg$alert$rule); + + # return if no CVE data found + if ( cve == "" ) + return; + + # Some CVE's have "_" and some have "-". We normalize them to "-". + cve = subst_string(cve, "_", "-"); + cve = subst_string(cve, "cve", "CVE"); + + if ( orig in cve_data && cve in cve_data[orig]$cve_list ) + { + cve_data[orig]$cve = cve; + c$suricata_alert$orig_vulnerable_host = cve_data[orig]; + } + + if ( resp in cve_data && cve in cve_data[resp]$cve_list ) + { + cve_data[resp]$cve = cve; + c$suricata_alert$resp_vulnerable_host = cve_data[resp]; + } + } # Enrich the Notice log. redef record Notice::Info += { - orig_vulnerable_host: Val &log &optional; - resp_vulnerable_host: Val &log &optional; + orig_vulnerable_host: Val &log &optional; + resp_vulnerable_host: Val &log &optional; }; hook Notice::notice(n: Notice::Info) - { - if ( ! n?$msg && ! n?$note ) - return; - - if ( ! n?$src && ! n?$dst ) - return; - - local cve: string; - - # Look for CVE in msg. - if ( n?$msg ) - cve = extract_cve_sig(n$msg); - # Look for CVE in note if not in msg. - if ( cve == "" && n?$note ) - cve = extract_cve_sig(fmt("%s", n$note)); - - # Some CVE's have "_" and some have "-". We normalize them to "-". - cve = subst_string(cve, "_", "-"); - - if ( cve == "" ) - return; - - if ( n?$src && n$src in cve_data && cve in cve_data[n$src]$cve_list ) - { - cve_data[n$src]$cve = cve; - n$orig_vulnerable_host = cve_data[n$src]; - } - if ( n?$dst && n$dst in cve_data && cve in cve_data[n$dst]$cve_list ) - { - cve_data[n$dst]$cve = cve; - n$resp_vulnerable_host = cve_data[n$dst]; - } - } + { + if ( !n?$msg && !n?$note ) + return; + if ( !n?$src && !n?$dst ) + return; + + local cve: string; + + # Look for CVE in msg. + if ( n?$msg ) + cve = extract_cve_sig(n$msg); + # Look for CVE in note if not in msg. + if ( cve == "" && n?$note ) + cve = extract_cve_sig(fmt("%s", n$note)); + + # Some CVE's have "_" and some have "-". We normalize them to "-". + cve = subst_string(cve, "_", "-"); + + if ( cve == "" ) + return; + + if ( n?$src && n$src in cve_data && cve in cve_data[n$src]$cve_list ) + { + cve_data[n$src]$cve = cve; + n$orig_vulnerable_host = cve_data[n$src]; + } + if ( n?$dst && n$dst in cve_data && cve in cve_data[n$dst]$cve_list ) + { + cve_data[n$dst]$cve = cve; + n$resp_vulnerable_host = cve_data[n$dst]; + } + } diff --git a/scripts/extract_cve.zeek b/scripts/extract_cve.zeek index ed4ffa1..fb089da 100644 --- a/scripts/extract_cve.zeek +++ b/scripts/extract_cve.zeek @@ -1,25 +1,25 @@ module CVEEnrichment; export { - global extract_cve_sig: function(s: string): string; - global extract_cve_metadata: function(s: vector of string): string; + global extract_cve_sig: function(s: string): string; + global extract_cve_metadata: function(s: vector of string): string; } function extract_cve_sig(s: string): string - { - # The string can contain "CVE" or "cve" and "-" or "_". - if ( /(?i:CVE)(-|_)/ !in s ) - return ""; - return find_last(s, /(?i:CVE)(-|_)[0-9]+(-|_)[0-9]+/); - } + { + # The string can contain "CVE" or "cve" and "-" or "_". + if ( /(?i:CVE)(-|_)/ !in s ) + return ""; + return find_last(s, /(?i:CVE)(-|_)[0-9]+(-|_)[0-9]+/); + } # works with zeek 5.1.0 and later function extract_cve_metadata(s: vector of string): string - { - for ( _, val in s ) - { - if ( /cve/ in val ) - return lstrip(val, "cve:"); - } - return ""; - } + { + for ( _, val in s ) + { + if ( /cve/ in val ) + return lstrip(val, "cve:"); + } + return ""; + } From b23da0c2ff7347bc4e0df6d12fafd3c9daaa2151 Mon Sep 17 00:00:00 2001 From: James Lagermann Date: Mon, 30 Jun 2025 17:11:37 -0500 Subject: [PATCH 3/8] add reference Signed-off-by: James Lagermann --- scripts/enrich.zeek | 10 +++++++--- scripts/extract_cve.zeek | 12 ++++++++++++ zkg.meta | 2 +- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/scripts/enrich.zeek b/scripts/enrich.zeek index 8929151..aa78f8e 100644 --- a/scripts/enrich.zeek +++ b/scripts/enrich.zeek @@ -57,9 +57,13 @@ event Suricata::connection_alert(c: connection, if ( cve == "" && msg$alert?$signature ) cve = extract_cve_sig(msg$alert$signature); - # Look for CVE in rule, if not found yet. - if ( cve == "" && msg$alert?$rule ) - cve = extract_cve_sig(msg$alert$rule); + # Look for CVE in references, if not found yet. + if ( cve == "" && msg$alert?$references ) + cve = extract_cve_references(msg$alert$references); + + # # Look for CVE in rule, if not found yet. + # if ( cve == "" && msg$alert?$rule ) + # cve = extract_cve_sig(msg$alert$rule); # return if no CVE data found if ( cve == "" ) diff --git a/scripts/extract_cve.zeek b/scripts/extract_cve.zeek index fb089da..5fbe832 100644 --- a/scripts/extract_cve.zeek +++ b/scripts/extract_cve.zeek @@ -3,6 +3,7 @@ module CVEEnrichment; export { global extract_cve_sig: function(s: string): string; global extract_cve_metadata: function(s: vector of string): string; + global extract_cve_references: function(s: vector of string): string; } function extract_cve_sig(s: string): string @@ -23,3 +24,14 @@ function extract_cve_metadata(s: vector of string): string } return ""; } + +function extract_cve_references(s: vector of string): string + { + for ( _, val in s ) + { + if ( /(?i:CVE)(-|_)/ !in val ) + return ""; + return find_last(val, /(?i:CVE)(-|_)[0-9]+(-|_)[0-9]+/); + } + return ""; + } diff --git a/zkg.meta b/zkg.meta index 6a150bf..2961d47 100644 --- a/zkg.meta +++ b/zkg.meta @@ -2,7 +2,7 @@ script_dir = scripts test_command = make test summary = Enriches the `suricata_corelight` and `notice` Corelight logs with known CVE information. -description = Enrich `suricata_corelight.log` and `notice.log` with known CVE information. +description = 4 Enrich `suricata_corelight.log` and `notice.log` with known CVE information. depends = zeek >=5.1.0 From c820d67b9a6354fb23f7eda36f5428fc1980a3c8 Mon Sep 17 00:00:00 2001 From: James Lagermann Date: Mon, 30 Jun 2025 17:21:48 -0500 Subject: [PATCH 4/8] add rule Signed-off-by: James Lagermann --- scripts/enrich.zeek | 6 +++--- zkg.meta | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/enrich.zeek b/scripts/enrich.zeek index aa78f8e..1207243 100644 --- a/scripts/enrich.zeek +++ b/scripts/enrich.zeek @@ -61,9 +61,9 @@ event Suricata::connection_alert(c: connection, if ( cve == "" && msg$alert?$references ) cve = extract_cve_references(msg$alert$references); - # # Look for CVE in rule, if not found yet. - # if ( cve == "" && msg$alert?$rule ) - # cve = extract_cve_sig(msg$alert$rule); + # Look for CVE in rule, if not found yet. + if ( cve == "" && msg$alert?$rule ) + cve = extract_cve_sig(msg$alert$rule); # return if no CVE data found if ( cve == "" ) diff --git a/zkg.meta b/zkg.meta index 2961d47..6a150bf 100644 --- a/zkg.meta +++ b/zkg.meta @@ -2,7 +2,7 @@ script_dir = scripts test_command = make test summary = Enriches the `suricata_corelight` and `notice` Corelight logs with known CVE information. -description = 4 Enrich `suricata_corelight.log` and `notice.log` with known CVE information. +description = Enrich `suricata_corelight.log` and `notice.log` with known CVE information. depends = zeek >=5.1.0 From 3b7d9a4afd62cf00f41efc8aa1999cd0deb28915 Mon Sep 17 00:00:00 2001 From: James Lagermann Date: Wed, 2 Jul 2025 14:33:10 -0500 Subject: [PATCH 5/8] add severity Signed-off-by: James Lagermann --- scripts/enrich.zeek | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/scripts/enrich.zeek b/scripts/enrich.zeek index 1207243..fa71dbc 100644 --- a/scripts/enrich.zeek +++ b/scripts/enrich.zeek @@ -49,21 +49,23 @@ event Suricata::connection_alert(c: connection, local cve = ""; - # Look for CVE in metadata. - if ( msg$alert?$metadata ) - cve = extract_cve_metadata(msg$alert$metadata); - - # Look for CVE in signature name, if it's not in metadata. - if ( cve == "" && msg$alert?$signature ) - cve = extract_cve_sig(msg$alert$signature); - - # Look for CVE in references, if not found yet. - if ( cve == "" && msg$alert?$references ) - cve = extract_cve_references(msg$alert$references); - # Look for CVE in rule, if not found yet. if ( cve == "" && msg$alert?$rule ) cve = extract_cve_sig(msg$alert$rule); + else + { + # Look for CVE in references, if not found yet. + if ( msg$alert?$references ) + cve = extract_cve_references(msg$alert$references); + + # Look for CVE in metadata. + if ( cve == "" && msg$alert?$metadata ) + cve = extract_cve_metadata(msg$alert$metadata); + + # Look for CVE in signature name, if it's not in metadata. + if ( cve == "" && msg$alert?$signature ) + cve = extract_cve_sig(msg$alert$signature); + } # return if no CVE data found if ( cve == "" ) @@ -71,18 +73,21 @@ event Suricata::connection_alert(c: connection, # Some CVE's have "_" and some have "-". We normalize them to "-". cve = subst_string(cve, "_", "-"); + cve = subst_string(cve, ",", "-"); cve = subst_string(cve, "cve", "CVE"); if ( orig in cve_data && cve in cve_data[orig]$cve_list ) { cve_data[orig]$cve = cve; c$suricata_alert$orig_vulnerable_host = cve_data[orig]; + c$suricata_alert$alert$severity = 1; } if ( resp in cve_data && cve in cve_data[resp]$cve_list ) { cve_data[resp]$cve = cve; c$suricata_alert$resp_vulnerable_host = cve_data[resp]; + c$suricata_alert$alert$severity = 1; } } From f0bcba8b1bed4f60b2e92bea60fa5c6c29a2d7e8 Mon Sep 17 00:00:00 2001 From: James Lagermann Date: Thu, 10 Jul 2025 09:47:30 -0500 Subject: [PATCH 6/8] Update to include all CVEs found in the alert. Signed-off-by: James Lagermann --- scripts/__load__.zeek | 7 ++- scripts/enrich.zeek | 132 --------------------------------------- scripts/extract_cve.zeek | 54 ++++++++-------- scripts/main.zeek | 31 +++++++++ scripts/notice.zeek | 49 +++++++++++++++ scripts/suricata.zeek | 66 ++++++++++++++++++++ 6 files changed, 178 insertions(+), 161 deletions(-) delete mode 100644 scripts/enrich.zeek create mode 100644 scripts/main.zeek create mode 100644 scripts/notice.zeek create mode 100644 scripts/suricata.zeek diff --git a/scripts/__load__.zeek b/scripts/__load__.zeek index 907b921..2e318f3 100644 --- a/scripts/__load__.zeek +++ b/scripts/__load__.zeek @@ -1,2 +1,7 @@ @load ./extract_cve -@load ./enrich +@load ./main +@load ./notice + +@ifdef (Suricata::Info) + @load ./suricata +@endif diff --git a/scripts/enrich.zeek b/scripts/enrich.zeek deleted file mode 100644 index fa71dbc..0000000 --- a/scripts/enrich.zeek +++ /dev/null @@ -1,132 +0,0 @@ -@load Corelight/Suricata - -module CVEEnrichment; - -type Idx: record { - ip: addr; -}; - -type Val: record { - cve_list: string_set; - ## The ID of the known CVE on the vulnerable host. - cve: string &log &optional; - ## The hostname of the vulnerable host. - hostname: string &log &optional; - ## The unique identifier, assigned by the CVE information source, of the vulnerable host. - host_uid: string &log &optional; - ## The machine domain of the vulnerable host. - machine_domain: string &log &optional; - ## The Operating System version of the vulnerable host. - os_version: string &log &optional; - ## The source of the CVE information. - source: string &log &optional; - ## The criticality of the host. - criticality: string &log &optional; -}; - -global cve_data: table[addr] of Val = table(); - -event zeek_init() - { - Input::add_table([ $source="cve_data.tsv", $name="cve_data", $idx=Idx, - $val=Val, $destination=cve_data, $mode=Input::REREAD ]); - } - -# Enrich the Suricata_corelight log. -redef record Suricata::Info += { - orig_vulnerable_host: Val &log &optional; - resp_vulnerable_host: Val &log &optional; -}; - -event Suricata::connection_alert(c: connection, - msg: Corelight::Suricata::SuricataMsg) - { - if ( ! msg?$alert || ( ! msg$alert?$signature && ! msg$alert?$metadata ) ) - return; - - local orig = c$id$orig_h; - local resp = c$id$resp_h; - - local cve = ""; - - # Look for CVE in rule, if not found yet. - if ( cve == "" && msg$alert?$rule ) - cve = extract_cve_sig(msg$alert$rule); - else - { - # Look for CVE in references, if not found yet. - if ( msg$alert?$references ) - cve = extract_cve_references(msg$alert$references); - - # Look for CVE in metadata. - if ( cve == "" && msg$alert?$metadata ) - cve = extract_cve_metadata(msg$alert$metadata); - - # Look for CVE in signature name, if it's not in metadata. - if ( cve == "" && msg$alert?$signature ) - cve = extract_cve_sig(msg$alert$signature); - } - - # return if no CVE data found - if ( cve == "" ) - return; - - # Some CVE's have "_" and some have "-". We normalize them to "-". - cve = subst_string(cve, "_", "-"); - cve = subst_string(cve, ",", "-"); - cve = subst_string(cve, "cve", "CVE"); - - if ( orig in cve_data && cve in cve_data[orig]$cve_list ) - { - cve_data[orig]$cve = cve; - c$suricata_alert$orig_vulnerable_host = cve_data[orig]; - c$suricata_alert$alert$severity = 1; - } - - if ( resp in cve_data && cve in cve_data[resp]$cve_list ) - { - cve_data[resp]$cve = cve; - c$suricata_alert$resp_vulnerable_host = cve_data[resp]; - c$suricata_alert$alert$severity = 1; - } - } - -# Enrich the Notice log. -redef record Notice::Info += { - orig_vulnerable_host: Val &log &optional; - resp_vulnerable_host: Val &log &optional; -}; - -hook Notice::notice(n: Notice::Info) - { - if ( !n?$msg && !n?$note ) - return; - if ( !n?$src && !n?$dst ) - return; - - local cve: string; - - # Look for CVE in msg. - if ( n?$msg ) - cve = extract_cve_sig(n$msg); - # Look for CVE in note if not in msg. - if ( cve == "" && n?$note ) - cve = extract_cve_sig(fmt("%s", n$note)); - - # Some CVE's have "_" and some have "-". We normalize them to "-". - cve = subst_string(cve, "_", "-"); - - if ( cve == "" ) - return; - - if ( n?$src && n$src in cve_data && cve in cve_data[n$src]$cve_list ) - { - cve_data[n$src]$cve = cve; - n$orig_vulnerable_host = cve_data[n$src]; - } - if ( n?$dst && n$dst in cve_data && cve in cve_data[n$dst]$cve_list ) - { - cve_data[n$dst]$cve = cve; - n$resp_vulnerable_host = cve_data[n$dst]; - } - } diff --git a/scripts/extract_cve.zeek b/scripts/extract_cve.zeek index 5fbe832..34486e3 100644 --- a/scripts/extract_cve.zeek +++ b/scripts/extract_cve.zeek @@ -1,37 +1,35 @@ module CVEEnrichment; -export { - global extract_cve_sig: function(s: string): string; - global extract_cve_metadata: function(s: vector of string): string; - global extract_cve_references: function(s: vector of string): string; -} +# Shared CVE pattern: matches CVE-YYYY-NN... (2 or more digits in last part) +# It could be just the numbers, for example: 2003-0605. However, the reference +# section of a rule would proceed the CVE name with "cve,". +# The last section of the numbers could be 3 or more digits. +# It could include "cve" or "CVE" as a prefix. +# Each section could be separated by "_", "-", or ",". For example, +# the reference section could have "cve,2003-0605". +# The metadata section could have "cve CVE-2003-0605". +# There's no standard "required" format for how CVE numbers are added to rules. +const cve_pattern: pattern = /(?i:CVE)[-_,][0-9]{4}[-_,][0-9]{2,}/; -function extract_cve_sig(s: string): string +# Extract all CVEs from a string +function extract_all_cves(s: string): vector of string { - # The string can contain "CVE" or "cve" and "-" or "_". - if ( /(?i:CVE)(-|_)/ !in s ) - return ""; - return find_last(s, /(?i:CVE)(-|_)[0-9]+(-|_)[0-9]+/); - } + local cves: vector of string = vector(); + if ( /(?i:CVE)/ !in s ) + return cves; -# works with zeek 5.1.0 and later -function extract_cve_metadata(s: vector of string): string - { - for ( _, val in s ) - { - if ( /cve/ in val ) - return lstrip(val, "cve:"); - } - return ""; + cves = find_all_ordered(s, cve_pattern); + return cves; } -function extract_cve_references(s: vector of string): string +# Extract all CVEs from a vector of strings +function extract_all_cves_from_vector(v: vector of string): vector of string { - for ( _, val in s ) - { - if ( /(?i:CVE)(-|_)/ !in val ) - return ""; - return find_last(val, /(?i:CVE)(-|_)[0-9]+(-|_)[0-9]+/); - } - return ""; + local all_cves: vector of string = vector(); + for ( _, val in v ) + { + if ( /(?i:CVE)/ in val ) + all_cves += find_all_ordered(val, cve_pattern); + } + return all_cves; } diff --git a/scripts/main.zeek b/scripts/main.zeek new file mode 100644 index 0000000..959e8e3 --- /dev/null +++ b/scripts/main.zeek @@ -0,0 +1,31 @@ +module CVEEnrichment; + +type Idx: record { + ip: addr; +}; + +type Val: record { + cve_list: string_set; + ## The ID of the known CVE on the vulnerable host. + cve: set[string] &log &optional; + ## The hostname of the vulnerable host. + hostname: string &log &optional; + ## The unique identifier, assigned by the CVE information source, of the vulnerable host. + host_uid: string &log &optional; + ## The machine domain of the vulnerable host. + machine_domain: string &log &optional; + ## The Operating System version of the vulnerable host. + os_version: string &log &optional; + ## The source of the CVE information. + source: string &log &optional; + ## The criticality of the host. + criticality: string &log &optional; +}; + +global cve_data: table[addr] of Val = table(); + +event zeek_init() + { + Input::add_table([ $source="cve_data.tsv", $name="cve_data", $idx=Idx, + $val=Val, $destination=cve_data, $mode=Input::REREAD ]); + } diff --git a/scripts/notice.zeek b/scripts/notice.zeek new file mode 100644 index 0000000..e6f7dc3 --- /dev/null +++ b/scripts/notice.zeek @@ -0,0 +1,49 @@ +module CVEEnrichment; + +# Enrich the Notice log. +redef record Notice::Info += { + orig_vulnerable_host: Val &log &optional; + resp_vulnerable_host: Val &log &optional; +}; + +hook Notice::notice(n: Notice::Info) + { + if ( !n?$msg && !n?$note ) + return; + if ( !n?$src && !n?$dst ) + return; + + local cves: vector of string = vector(); + + # Look for CVE in msg. + if ( n?$msg ) + cves += extract_all_cves(n$msg); + + # Look for CVE in note if not in msg. + if ( n?$note ) + # convert enum to string + cves += extract_all_cves(fmt("%s", n$note)); + + # return if no CVE data found + if ( |cves| == 0 ) + return; + + for ( _, raw_cve in cves ) + { + # Some CVE's have "_" and some have "-". We normalize them to "-". + local cve = subst_string(raw_cve, "_", "-"); + cve = subst_string(cve, ",", "-"); + cve = subst_string(cve, "cve", "CVE"); + + if ( n?$src && n$src in cve_data && cve in cve_data[n$src]$cve_list ) + { + add cve_data[n$src]$cve[cve]; + n$orig_vulnerable_host = cve_data[n$src]; + } + if ( n?$dst && n$dst in cve_data && cve in cve_data[n$dst]$cve_list ) + { + add cve_data[n$dst]$cve[cve]; + n$resp_vulnerable_host = cve_data[n$dst]; + } + } + } diff --git a/scripts/suricata.zeek b/scripts/suricata.zeek new file mode 100644 index 0000000..cee7a81 --- /dev/null +++ b/scripts/suricata.zeek @@ -0,0 +1,66 @@ +module CVEEnrichment; + +# Enrich the Suricata_corelight log. +redef record Suricata::Info += { + orig_vulnerable_host: Val &log &optional; + resp_vulnerable_host: Val &log &optional; +}; + +event Suricata::connection_alert(c: connection, + msg: Corelight::Suricata::SuricataMsg) + { + if ( ! msg?$alert || ( ! msg$alert?$signature && ! msg$alert?$metadata ) ) + return; + + local orig = c$id$orig_h; + local resp = c$id$resp_h; + + local cves: vector of string = vector(); + + # Look for CVE in rule, if rule is present. + if ( msg$alert?$rule ) + { + cves = extract_all_cves(msg$alert$rule); + } + else + { + # Look for CVEs in references. + if ( msg$alert?$references ) + cves += extract_all_cves_from_vector(msg$alert$references); + + # Look for CVEs in metadata and add them to the previously found CVEs. + if ( msg$alert?$metadata ) + cves += extract_all_cves_from_vector(msg$alert$metadata); + + # Look for CVEs in signature name and add them to the previously found CVEs. + if ( msg$alert?$signature ) + cves += extract_all_cves(msg$alert$signature); + } + + + # return if no CVE data found + if ( |cves| == 0 ) + return; + + for ( _, raw_cve in cves ) + { + # Some CVE's have "_" and some have "-". We normalize them to "-". + local cve = subst_string(raw_cve, "_", "-"); + cve = subst_string(cve, ",", "-"); + cve = subst_string(cve, "cve", "CVE"); + + if ( orig in cve_data && cve in cve_data[orig]$cve_list ) + { + add cve_data[orig]$cve[cve]; + c$suricata_alert$orig_vulnerable_host = cve_data[orig]; + c$suricata_alert$alert$severity = 1; + } + + if ( resp in cve_data && cve in cve_data[resp]$cve_list ) + { + add cve_data[resp]$cve[cve]; + c$suricata_alert$resp_vulnerable_host = cve_data[resp]; + c$suricata_alert$alert$severity = 1; + } + } + } From 8076ab996245181e45cb64956de8cf2eb8d8e1a5 Mon Sep 17 00:00:00 2001 From: James Lagermann Date: Thu, 10 Jul 2025 14:29:17 -0500 Subject: [PATCH 7/8] add cve set if missing --- scripts/notice.zeek | 4 ++++ scripts/suricata.zeek | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/scripts/notice.zeek b/scripts/notice.zeek index e6f7dc3..b31ee66 100644 --- a/scripts/notice.zeek +++ b/scripts/notice.zeek @@ -37,11 +37,15 @@ hook Notice::notice(n: Notice::Info) if ( n?$src && n$src in cve_data && cve in cve_data[n$src]$cve_list ) { + if ( ! cve_data[n$src]?$cve ) + cve_data[n$src]$cve = set(); add cve_data[n$src]$cve[cve]; n$orig_vulnerable_host = cve_data[n$src]; } if ( n?$dst && n$dst in cve_data && cve in cve_data[n$dst]$cve_list ) { + if ( ! cve_data[n$dst]?$cve ) + cve_data[n$dst]$cve = set(); add cve_data[n$dst]$cve[cve]; n$resp_vulnerable_host = cve_data[n$dst]; } diff --git a/scripts/suricata.zeek b/scripts/suricata.zeek index cee7a81..a49f3d6 100644 --- a/scripts/suricata.zeek +++ b/scripts/suricata.zeek @@ -51,6 +51,8 @@ event Suricata::connection_alert(c: connection, if ( orig in cve_data && cve in cve_data[orig]$cve_list ) { + if ( ! cve_data[orig]?$cve ) + cve_data[orig]$cve = set(); add cve_data[orig]$cve[cve]; c$suricata_alert$orig_vulnerable_host = cve_data[orig]; c$suricata_alert$alert$severity = 1; @@ -58,6 +60,8 @@ event Suricata::connection_alert(c: connection, if ( resp in cve_data && cve in cve_data[resp]$cve_list ) { + if ( ! cve_data[resp]?$cve ) + cve_data[resp]$cve = set(); add cve_data[resp]$cve[cve]; c$suricata_alert$resp_vulnerable_host = cve_data[resp]; c$suricata_alert$alert$severity = 1; From 9f80ca0b0d1c9e54b93960b2d6d1cf5f96258486 Mon Sep 17 00:00:00 2001 From: James Lagermann Date: Thu, 10 Jul 2025 15:07:49 -0500 Subject: [PATCH 8/8] update tests --- testing/Baseline/notice-enrichment.log | 1 + testing/Scripts/test_notice.zeek | 20 +++++++++ testing/inputs/test-cve-data.tsv | 2 + testing/tests/extract-cve.zeek | 61 +++++++++++++++++--------- 4 files changed, 63 insertions(+), 21 deletions(-) create mode 100644 testing/Baseline/notice-enrichment.log create mode 100644 testing/Scripts/test_notice.zeek create mode 100644 testing/inputs/test-cve-data.tsv diff --git a/testing/Baseline/notice-enrichment.log b/testing/Baseline/notice-enrichment.log new file mode 100644 index 0000000..4139a41 --- /dev/null +++ b/testing/Baseline/notice-enrichment.log @@ -0,0 +1 @@ +{"_path":"notice","note":"Notice::Misc","msg":"CVE-1234-56789 and cve_1234_00001 seen in message",...,"orig_vulnerable_host":{...}} diff --git a/testing/Scripts/test_notice.zeek b/testing/Scripts/test_notice.zeek new file mode 100644 index 0000000..7951e9d --- /dev/null +++ b/testing/Scripts/test_notice.zeek @@ -0,0 +1,20 @@ +@load base/frameworks/notice +@load ./../../extract_cve +@load ./../../main +@load ./../../notice + +module TestNotice; + +redef Input::read_files += { + fmt("%s/../inputs/test-cve-data.tsv", get_script_path()) +}; + +event zeek_init() &priority=5 +{ + NOTICE([ + $note = Notice::Misc, + $msg = "CVE-1234-56789 and cve_1234_00001 seen in message", + $src = 1.2.3.4, + $dst = 9.8.7.6, + ]); +} diff --git a/testing/inputs/test-cve-data.tsv b/testing/inputs/test-cve-data.tsv new file mode 100644 index 0000000..6341901 --- /dev/null +++ b/testing/inputs/test-cve-data.tsv @@ -0,0 +1,2 @@ +#fields ip hostname uid cid criticality machine_domain os_version source cve_list +1.2.3.4 test-host - - Medium - Linux test_source CVE-1234-56789,CVE-1234-00001 diff --git a/testing/tests/extract-cve.zeek b/testing/tests/extract-cve.zeek index 859da3b..2f82940 100644 --- a/testing/tests/extract-cve.zeek +++ b/testing/tests/extract-cve.zeek @@ -1,26 +1,45 @@ # @TEST-EXEC: zeek ../../../scripts/extract_cve.zeek %INPUT > out # @TEST-EXEC: btest-diff out -global cases: string_vec = vector("hello", "", "CVE-NOPE", "", - "ET EXPLOIT Zoho ManageEngine Desktop Central RCE Inbound (CVE-2020-10189)", - "CVE-2020-10189"); +@load ./../../extract_cve event zeek_init() - { - local n = 0; - while ( n < |cases| ) - { - local i = cases[n]; - local ex = cases[n + 1]; - local got = CVEEnrichment::extract_cve_sig(i); - if ( got == ex ) - { - print fmt("OK extract_cve(%s) == '%s'", i, ex); - } - else - { - print fmt("FAIL extract_cve(%s) != '%s', got '%s'", i, ex, got); - } - n += 2; - } - } + { + local cases: table[int] of string = { + [0] = "CVE-2023-1234", + [1] = "exploit CVE_2022_9876 in the wild", + [2] = "something with CVE-1999-0001 and CVE_2005_123456", + [3] = "CVE,2021-44444", + [4] = "cve CVE-2020-0002 and cve_2021_0003", + [5] = "no CVE here", + [6] = "multiple: CVE-2000-1111, CVE-2000-2222, CVE-2000-3333", + [7] = "cve-2001-01 cve_2001_0001 cve-2001_999999" + }; + + local expected: table[int] of vector of string = { + [0] = vector("CVE-2023-1234"), + [1] = vector("CVE_2022_9876"), + [2] = vector("CVE-1999-0001", "CVE_2005_123456"), + [3] = vector("CVE,2021-44444"), + [4] = vector("CVE-2020-0002", "cve_2021_0003"), + [5] = vector(), + [6] = vector("CVE-2000-1111", "CVE-2000-2222", "CVE-2000-3333"), + [7] = vector("cve-2001-01", "cve_2001_0001", "cve-2001_999999") + }; + + for ( i in cases ) + { + local input = cases[i]; + local result = extract_all_cves(input); + local want = expected[i]; + + if ( result == want ) + print fmt("PASS [%d]: %s", i, input); + else + { + print fmt("FAIL [%d]: %s", i, input); + print fmt(" Expected: %s", want); + print fmt(" Got : %s", result); + } + } + }