mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 22:58:20 +00:00
232 lines
6.4 KiB
Text
232 lines
6.4 KiB
Text
##! Implements base functionality for WebSocket analysis.
|
|
##!
|
|
##! Upon a websocket_established() event, logs all gathered information into
|
|
##! websocket.log and configures the WebSocket analyzer with the headers
|
|
##! collected via http events.
|
|
|
|
@load base/protocols/http
|
|
|
|
@load ./consts
|
|
|
|
module WebSocket;
|
|
|
|
# Register the WebSocket analyzer as HTTP upgrade analyzer.
|
|
redef HTTP::upgrade_analyzers += {
|
|
["websocket"] = Analyzer::ANALYZER_WEBSOCKET,
|
|
};
|
|
|
|
export {
|
|
redef enum Log::ID += { LOG };
|
|
|
|
## The record type for the WebSocket log.
|
|
type Info: record {
|
|
## Timestamp
|
|
ts: time &log;
|
|
## Unique ID for the connection.
|
|
uid: string &log;
|
|
## The connection's 4-tuple of endpoint addresses/ports.
|
|
id: conn_id &log;
|
|
## Same as in the HTTP log.
|
|
host: string &log &optional;
|
|
## Same as in the HTTP log.
|
|
uri: string &log &optional;
|
|
## Same as in the HTTP log.
|
|
user_agent: string &log &optional;
|
|
## The WebSocket subprotocol as selected by the server.
|
|
subprotocol: string &log &optional;
|
|
## The protocols requested by the client, if any.
|
|
client_protocols: vector of string &log &optional;
|
|
## The extensions selected by the the server, if any.
|
|
server_extensions: vector of string &log &optional;
|
|
## The extensions requested by the client, if any.
|
|
client_extensions: vector of string &log &optional;
|
|
## The Sec-WebSocket-Key header from the client.
|
|
client_key: string &optional;
|
|
## The Sec-WebSocket-Accept header from the server.
|
|
server_accept: string &optional;
|
|
};
|
|
|
|
## Event that can be handled to access the WebSocket record as it is
|
|
## sent on to the logging framework.
|
|
global log_websocket: event(rec: Info);
|
|
|
|
## Log policy hook.
|
|
global log_policy: Log::PolicyHook;
|
|
|
|
## Experimental: Hook to intercept WebSocket analyzer configuration.
|
|
##
|
|
## Breaking from this hook disables the WebSocket analyzer immediately.
|
|
## To modify the configuration of the analyzer, use the
|
|
## :zeek:see:`WebSocket::AnalyzerConfig` type.
|
|
##
|
|
## While this API allows quite some flexibility currently, should be
|
|
## considered experimental and may change in the future with or
|
|
## without a deprecation phase.
|
|
##
|
|
## c: The connection
|
|
##
|
|
## aid: The analyzer ID for the WebSocket analyzer.
|
|
##
|
|
## config: The configuration record, also containing information
|
|
## about the subprotocol and extensions.
|
|
global configure_analyzer: hook(c: connection, aid: count, config: AnalyzerConfig);
|
|
}
|
|
|
|
redef record connection += {
|
|
websocket: Info &optional;
|
|
};
|
|
|
|
function set_websocket(c: connection)
|
|
{
|
|
c$websocket = Info(
|
|
$ts=network_time(),
|
|
$uid=c$uid,
|
|
$id=c$id,
|
|
);
|
|
}
|
|
|
|
function expected_accept_for(key: string): string
|
|
{
|
|
return encode_base64(hexstr_to_bytestring(sha1_hash(key + HANDSHAKE_GUID)));
|
|
}
|
|
|
|
event http_header(c: connection, is_orig: bool, name: string, value: string)
|
|
{
|
|
if ( ! starts_with(name, "SEC-WEBSOCKET-") )
|
|
return;
|
|
|
|
if ( ! c?$websocket )
|
|
set_websocket(c);
|
|
|
|
local ws = c$websocket;
|
|
|
|
if ( is_orig )
|
|
{
|
|
if ( name == "SEC-WEBSOCKET-PROTOCOL" )
|
|
{
|
|
if ( ! ws?$client_protocols )
|
|
ws$client_protocols = vector();
|
|
|
|
ws$client_protocols += split_string(value, / *, */);
|
|
}
|
|
|
|
else if ( name == "SEC-WEBSOCKET-EXTENSIONS" )
|
|
{
|
|
if ( ! ws?$client_extensions )
|
|
ws$client_extensions = vector();
|
|
|
|
ws$client_extensions += split_string(value, / *, */);
|
|
}
|
|
else if ( name == "SEC-WEBSOCKET-KEY" )
|
|
{
|
|
if ( ws?$client_key )
|
|
Reporter::conn_weird("websocket_multiple_key_headers", c, "", "WebSocket");
|
|
|
|
ws$client_key = value;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( name == "SEC-WEBSOCKET-PROTOCOL" )
|
|
{
|
|
if ( ws?$subprotocol )
|
|
Reporter::conn_weird("websocket_multiple_protocol_headers", c, "", "WebSocket");
|
|
|
|
ws$subprotocol = value;
|
|
}
|
|
else if ( name == "SEC-WEBSOCKET-EXTENSIONS" )
|
|
{
|
|
if ( ! ws?$server_extensions )
|
|
ws$server_extensions = vector();
|
|
|
|
ws$server_extensions += split_string(value, / *, */);
|
|
}
|
|
else if ( name == "SEC-WEBSOCKET-ACCEPT" )
|
|
{
|
|
if ( ws?$server_accept )
|
|
Reporter::conn_weird("websocket_multiple_accept_headers", c, "", "WebSocket");
|
|
|
|
ws$server_accept = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
event http_request(c: connection, method: string, original_URI: string,
|
|
unescaped_URI: string, version: string)
|
|
{
|
|
# If we see a http_request and have websocket state, wipe it as
|
|
# we should've seen a websocket_established even on success and
|
|
# likely no more http events.
|
|
if ( ! c?$websocket )
|
|
delete c$websocket;
|
|
}
|
|
|
|
event websocket_established(c: connection, aid: count) &priority=5
|
|
{
|
|
if ( ! c?$websocket )
|
|
{
|
|
# This means we never saw a Sec-WebSocket-* header, weird.
|
|
Reporter::conn_weird("websocket_established_unexpected", c, "", "WebSocket");
|
|
set_websocket(c);
|
|
}
|
|
|
|
local ws = c$websocket;
|
|
|
|
if ( ! ws?$client_key )
|
|
Reporter::conn_weird("websocket_missing_key_header", c, "", "WebSocket");
|
|
|
|
if ( ! ws?$server_accept )
|
|
Reporter::conn_weird("websocket_missing_accept_header", c, "", "WebSocket");
|
|
|
|
# Verify the Sec-WebSocket-Accept header's value given the Sec-WebSocket-Key header's value.
|
|
if ( ws?$client_key && ws?$server_accept )
|
|
{
|
|
local expected_accept = expected_accept_for(ws$client_key);
|
|
if ( ws$server_accept != expected_accept )
|
|
Reporter::conn_weird("websocket_wrong_accept_header", c,
|
|
fmt("expected=%s, found=%s", expected_accept, ws$server_accept),
|
|
"WebSocket");
|
|
}
|
|
|
|
# Replicate some information from the HTTP.log
|
|
if ( c?$http )
|
|
{
|
|
if ( c$http?$host )
|
|
ws$host = c$http$host;
|
|
|
|
if ( c$http?$uri )
|
|
ws$uri = c$http$uri;
|
|
|
|
if ( c$http?$user_agent )
|
|
ws$user_agent = c$http$user_agent;
|
|
}
|
|
}
|
|
|
|
event websocket_established(c: connection, aid: count) &priority=-5
|
|
{
|
|
local ws = c$websocket;
|
|
|
|
local config = AnalyzerConfig();
|
|
if ( ws?$subprotocol )
|
|
config$subprotocol = ws$subprotocol;
|
|
|
|
if ( ws?$server_extensions )
|
|
config$server_extensions = ws$server_extensions;
|
|
|
|
# Give other scripts a chance to modify the analyzer configuration.
|
|
#
|
|
# Breaking from this hook disables the new WebSocket analyzer
|
|
# completely instead of configuring it.
|
|
if ( hook WebSocket::configure_analyzer(c, aid, config) )
|
|
WebSocket::__configure_analyzer(c, aid, config);
|
|
else
|
|
disable_analyzer(c$id, aid);
|
|
|
|
ws$ts = network_time();
|
|
Log::write(LOG, ws);
|
|
}
|
|
|
|
event zeek_init()
|
|
{
|
|
Log::create_stream(LOG, [$columns=Info, $ev=log_websocket, $path="websocket", $policy=log_policy]);
|
|
}
|