ZeekJS
ZeekJS is an experimental Zeek plugin to support JavaScript as an alternative scripting language in Zeek.
zeek.on('zeek_init', function() {
console.log('Hello, Zeek!');
});
The plugin embeds Node.js and primarily deals with converting between JavaScript and Zeek data types and registering JavaScript functions as event or hook handlers within Zeek. It further installs Node’s IO loop as an IO source in Zeek.
Getting started
Compiling and running ZeekJS requires a Zeek installation and Node.js available as a shared library.
Zeek
Currently ZeekJS strives to be compatible with latest stable, feature and nightly releases of Zeek.
Note
With Zeek version 6.0, the ZeekJS plugin is automatically included as a builtin plugin when Node.js is available on the build system. The Zeek documentation received a section about JavaScript, too.
Node.js
If your operating system offers a way to install a modern Node.js version
as a shared library and includes development headers as well, that makes
things easy. For example, on Fedora 34 and 35 all that is needed is to
install the nodejs-devel
and nodejs-lib
packages.
If you’re not using Fedora, you’ll probably need to compile Node.js yourself.
Compiling Node.js from source
Start by fetching a source tarball, or cloning the Node.js repository and check out the tag of the release you want to build. Then, configure, compile and install it.
$ git clone https://github.com/nodejs/node.git
$ cd node
$ git reset --hard v19.9.0
$ ./configure --prefix=/opt/node-19 --shared
$ make
$ sudo make install
With Node.js in place, you should be prepared to compile ZeekJS.
You may want to look into docker/debian.Dockerfile
for some inspiration
around installing Node.js on Debian or other distributions.
Compiling ZeekJS
ZeekJS is a standard Zeek plugin. Existing documentation around building
and installing Zeek plugins should apply to it as well.
Ensure that zeek-config
is in your path, then use ./configure
and
make
for building and installing.
If Node.js is installed in a non-standard location, use
--with-nodejs=/path/to/nodejs
.
For example:
$ zeek-config --version
4.1.1
$ ./configure --with-nodejs=/opt/node-19
$ make
$ sudo make install
If everything worked out the plugin should be available available:
$ zeek -NN Zeek::JavaScript
Zeek::JavaScript - Experimental JavaScript support for Zeek (dynamic, version 0.7.0)
Implements LoadFile (priority 0)
Hello, Zeek!
Verify ZeekJS is functional by running a JavaScript file using Zeek:
$ cat << EOF > hello.js
zeek.on('zeek_init', function() {
console.log('Hello, Zeek!');
});
EOF
$ zeek ./hello.js
Hello, Zeek!
Limitations
No multi-index support for tables and sets. JavaScript objects have string properties only.
Generally, look out for [ ERROR ]
messages on stderr
.
If something doesn’t seem to work, it may just not be implemented.
Examples
Exposing Zeek stats via HTTP
This example shows how to start a HTTP server and expose network and event
stats gathered by invoking the bifs get_net_stats()
and get_event_stats()
.
'use strict';
const http = require('http');
// Render BigInt (count) types as strings in JSON.
BigInt.prototype.toJSON = function() {
return this.toString();
}
http.createServer((req, res) => {
let stats = {
net: zeek.invoke('get_net_stats'),
event: zeek.invoke('get_event_stats'),
zeek_version: zeek.invoke('zeek_version'),
};
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(stats));
}).listen(3000);
$ curl -sfS localhost:3000 | jq
{
"net": {
"pkts_recvd": "4861",
"pkts_dropped": "0",
"pkts_link": "4861",
"bytes_recvd": "6319316"
},
"event": {
"queued": "431",
"dispatched": "431"
},
"zeek_version": "4.1.1-debug"
}
Taking over logging
This leverages Zeek 4.1’s new Log::log_stream_policy
hook to bypass
Zeek logging.
'use strict';
const fs = require('fs');
// Render BigInt (count) types as strings in JSON.
BigInt.prototype.toJSON = function() {
return this.toString();
}
zeek.hook('Log::log_stream_policy', {priority: -1000}, function(rec, log_id) {
// Conn::Info to Conn, PacketFilter::Info to PacketFilter
if (log_id.includes('::'))
[log_id] = log_id.split('::')
// CamelCase to snake_case: PacketFilter to packet_filter
log_id = log_id.replace(/([a-z0-9])([A-Z])/g, '\$1_\$2').toLowerCase()
const log_rec = zeek.select_fields(rec, zeek.ATTR_LOG)
const flat_rec = zeek.flatten(log_rec)
// Write to the log file. Synchronous here for simplicity.
fs.appendFileSync(log_id + '.log', JSON.stringify(flat_rec) + '\n')
// If you wanted to hand-off logs to a central Redis server.
// redis_client.publish(log_id, JSON.stringify(flat_rec))
// Returning false from a hook handler is semantically the same as
// break in Zeekscript. Not returning or returning anything else
// has no effect in a hook handler.
return false
})
This will write JSON log entries into dns.log
, http.log
and ssl.log
when running, for example:
$ zeek -r tests/Traces/dns-http-https.pcap ./log-bypass.js
$ cat dns.log
{"ts":1630238733.951343,"uid":"CS00HK1MFHn2F03Px2","id.orig_h":"172.20.10.3","id.orig_p":55767,"id.resp_h":"172.20.10.1","id.resp_p":53,"proto":"udp","trans_id":"43556","rtt":0.03791093826293945,"query":"corelight.com","qclass":"1","qclass_name":"C_INTERNET","qtype":"1","qtype_name":"A","rcode":"0","rcode_name":"NOERROR","AA":false,"TC":false,"RD":true,"RA":true,"Z":"0","answers":["199.60.103.106","199.60.103.6"],"TTLs":[77,77],"rejected":false}
API Reference
The plugin adds a zeek
object into the global namespace. This object
provides the following functions to interact with Zeek.
- static zeek.on(name, options, handler)
Register a function to be called as a Zeek event handler.
- Arguments
name (string) – The Zeek event name. For example,
zeek_init
.options (object) – Optional options. Only supported key is priority.
handler (function) – The function to call.
Examples:
zeek.on('zeek_init', () => { console.log('Hello, Zeek!'); });
- static zeek.hook(name, options, handler)
Register a function to be called as a Zeek hook handler.
When
handler
returnsfalse
, this is equivalent to usingbreak
in a Zeek hook handler.- Arguments
name (string) – The name of the hook. For example,
DNS::log_policy
.options (object) – Optional options. Only supported key is priority.
handler (function) – The function to call.
- static zeek.event(name, args)
Queue a Zeek event.
Conversion of
args
to Zeek event arguments happens implicitly.- Arguments
name (string) – The name of the Zeek event to queue.
args (array) – Arguments to use.
- static zeek.invoke(name, args)
Invoke a Zeek function.
Conversion of
args
to Zeek function arguments happens implicitly.Invoking Zeek hooks is possible. If any of the hook handlers break, the return value will be false, else true.
To invoke a Zeek function taking an
any
typed parameter, usezeek.as
to convert a JavaScript value to a Zeek value and use the resulting object. The plugin will thread through the underlying Zeek value without attempting implicit conversion.- Arguments
name (string) – The name of the Zeek function to invoke.
args (array) – Arguments to use.
Examples:
zeek.on('zeek_init', () => { let version = zeek.invoke('zeek_version'); console.log(`Running on Zeek ${version}`); });
- static zeek.as(type_name, value)
Explicit type conversion from JavaScript to Zeek.
- Arguments
type_name (string) – The name of the Zeek type. For example,
addr
.value – The value to convert to
type_name
.
- Returns
An object referencing a Zeek value of type
type_name
.
- static zeek.select_fields(rec, mask)
Select properties with a given attribute.
To select only
&log
attributes forJSON.stringify()
:zeek.on('HTTP::log_http' (rec) => { console.log(JSON.stringify(zeek.select_fields(rec, zeek.ATTR_LOG))); });
- Arguments
rec (object) – A object backed by a Zeek record.
mask (number) – The attribute mask. Only
zeek.ATTR_LOG
is currently supported.
- static zeek.flatten(rec, prefix, res)
Flatten a Javascript object by concatenating nested properties with . similar to how Zeek would log them in JSON format.
- Arguments
rec (object) – The object to flatten.
prefix (string) – Key prefix, optional.
res (object) – Result object, optional.
Examples:
// http.log imitation zeek.on('HTTP::log_http' (rec) => { let log_rec = zeek.select_fields(rec, zeek.ATTR_LOG); console.log(JSON.stringify(zeek.flatten(log_rec))); });
- zeek.global_vars
Access Zeek side global variables.
This object allows access to global and exported variables.
Examples:
zeek.global_vars["Cluster::node"] worker-01