Use Zeek to Monitor Connections to Specific Countries

Hi !

I have spoken about “Zeek” many times in the past. One of its strengths (if not one of the most interesting in my opinion) is its scripting language, which allows it to be programmed to perform the tasks one wants. Let’s imagine we want to identify all network connections to and from “friendly” countries. Since I don’t want to ostracize anyone, I’m going to include everyone in a somewhat mixed group. What interests me most is seeing who, from my information assets, initiates outbound communications to the “trusted relationships” mentioned above. Please note, and I repeat, I have deliberately included both the “not-so-bright” ones, as a friend would say, and those considered more trustworthy.

Here are a few additional important points:

Here is the Zeek script (“CountryMonitor.zeek”).

module CountryMonitor;

@load base/utils/site
@load base/frameworks/logging

export {
    # List of countries of interest
    const monitored_countries: set[string] = { "FR", "CN", "US", "RU" } &redef;
    
    redef enum Log::ID += { LOG_COUNTRY };

    type Info: record {
        ts: time          &log;
        uid: string       &log;
        id: conn_id       &log;
        orig_cc: string   &log &optional;
        resp_cc: string   &log &optional;
        state: string     &log &optional;
    };
}

event zeek_init() {
    Log::create_stream(CountryMonitor::LOG_COUNTRY, [
        $columns=Info, 
        $path="country_watch"
    ]);
}

event connection_state_remove(c: connection) {
    # Only connections with the "SF" flag are kept
    if ( ! c$conn?$conn_state || c$conn$conn_state != "SF" )
        return;

    # Retrieving geolocation (universal syntax)
    local orig_loc = lookup_location(c$id$orig_h);
    local resp_loc = lookup_location(c$id$resp_h);
    
    local o_cc = orig_loc?$country_code ? orig_loc$country_code : "XX";
    local r_cc = resp_loc?$country_code ? resp_loc$country_code : "XX";

    # Verification of target countries
    if (o_cc in monitored_countries || r_cc in monitored_countries) {
        local info: CountryMonitor::Info = [
            $ts=network_time(),
            $uid=c$uid,
            $id=c$id,
            $orig_cc=o_cc,
            $resp_cc=r_cc,
            $state=c$conn$conn_state
        ];
        
        Log::write(CountryMonitor::LOG_COUNTRY, info);
    }
}

Load it the usual way in ’local.zeek’ (for exemple).

@load CountryMonitor

Exemple output file:

1773696344.038141       CuerNs4HSdevW2LqQ8      5.143.254.86    59160   192.168.117.12  53      RU      CA      SF
1773696345.446331       CE6XC92s1wTA2DW5c       46.44.32.144    50246   192.168.246.8   587     RU      CA      SF
1773696346.236201       CQsrsU7DgxstAG7Ce       192.168.82.155  55432   180.113.220.173 20089   CA      CN      SF
1773696347.631179       CDybfq1ofPbMd1H5ad      172.16.24.32    41897   122.112.208.177 53      XX      CN      SF
1773696599.082492       CDcmn42kwB7EEbShOg      192.168.34.27   8567    220.197.69.156  15708   CA      CN      SF
1773696598.804429       C6dujrhpMAK5WMXU8       192.168.25.57   8567    220.197.69.156  15787   CA      CN      SF
1773696599.600217       C4jbL24cPRRYzuDbJe      192.168.117.12  55173   192.168.117.12  443     CN      CA      SF

I hope this gives you some ideas on how to use Zeek’s scripting language.

Cheers.