Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion scripts/__load__.zeek
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
@load ./extract_cve
@load ./enrich
@load ./main
@load ./notice

@ifdef (Suricata::Info)
@load ./suricata
@endif
123 changes: 0 additions & 123 deletions scripts/enrich.zeek

This file was deleted.

52 changes: 31 additions & 21 deletions scripts/extract_cve.zeek
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
module CVEEnrichment;

export {
global extract_cve_sig: function(s: string): string;
global extract_cve_metadata: 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
{
# 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]+/);
}
# Extract all CVEs from a string
function extract_all_cves(s: string): vector of string
{
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;
}

# Extract all CVEs from a vector of strings
function extract_all_cves_from_vector(v: vector of string): vector of string
{
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;
}
31 changes: 31 additions & 0 deletions scripts/main.zeek
Original file line number Diff line number Diff line change
@@ -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 ]);
}
53 changes: 53 additions & 0 deletions scripts/notice.zeek
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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 )
{
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];
}
}
}
70 changes: 70 additions & 0 deletions scripts/suricata.zeek
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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 )
{
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;
}

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;
}
}
}
1 change: 1 addition & 0 deletions testing/Baseline/notice-enrichment.log
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"_path":"notice","note":"Notice::Misc","msg":"CVE-1234-56789 and cve_1234_00001 seen in message",...,"orig_vulnerable_host":{...}}
20 changes: 20 additions & 0 deletions testing/Scripts/test_notice.zeek
Original file line number Diff line number Diff line change
@@ -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,
]);
}
2 changes: 2 additions & 0 deletions testing/inputs/test-cve-data.tsv
Original file line number Diff line number Diff line change
@@ -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
Loading