zeek / zeek

Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
https://www.zeek.org
Other
6.39k stars 1.21k forks source link

Zeek not generating notices #3657

Closed owengauci98 closed 6 months ago

owengauci98 commented 6 months ago

Hi, I have installed Zeek (6.2.0-dev.481) on Ubuntu 22 to for a personal little project to test SQLi and SSH Bruteforce/Dictionary attacks in my own network. For some reason doesn't generate notices when I test these attacks.

networks.cfg

172.16.1.0/24       Local Area Network
192.168.0.0/16      External Network

node.cfg

[zeek]
type=standalone
host=localhost
interface=ens37

local.zeek

redef digest_salt = "Please change this value.";
@load misc/loaded-scripts
@load tuning/defaults
@load misc/capture-loss
@load misc/stats
@load frameworks/software/vulnerable
@load frameworks/software/version-changes
@load-sigs frameworks/signatures/detect-windows-shells
@load protocols/ftp/software
@load protocols/smtp/software
@load protocols/ssh/software
@load protocols/http/software
@load protocols/dns/detect-external-names
@load protocols/ftp/detect
@load protocols/conn/known-hosts
@load protocols/conn/known-services
@load protocols/ssl/known-certs
@load protocols/ssl/validate-certs
@load protocols/ssl/log-hostcerts-only
@load protocols/ssh/geo-data
@load protocols/ssh/detect-bruteforcing
@load protocols/ssh/interesting-hostnames
@load protocols/http/detect-sqli
@load frameworks/files/hash-all-files
@load frameworks/files/detect-MHR
@load policy/frameworks/notice/extend-email/hostnames
@load frameworks/telemetry/log
@load /usr/local/zeek/share/zeek/policy/tuning/json-logs.zeek

detect-sqli.zeek

##! SQL injection attack detection in HTTP.

@load base/frameworks/notice
@load base/frameworks/sumstats
@load base/protocols/http

module HTTP;

export {
    redef enum Notice::Type += {
        ## Indicates that a host performing SQL injection attacks was
        ## detected.
        SQL_Injection_Attacker,
        ## Indicates that a host was seen to have SQL injection attacks
        ## against it.  This is tracked by IP address as opposed to
        ## hostname.
        SQL_Injection_Victim,
    };

    redef enum Tags += {
        ## Indicator of a URI based SQL injection attack.
        URI_SQLI,
        ## Indicator of client body based SQL injection attack.  This is
        ## typically the body content of a POST request. Not implemented
        ## yet.
        POST_SQLI,
        ## Indicator of a cookie based SQL injection attack. Not
        ## implemented yet.
        COOKIE_SQLI,
    };

    ## Defines the threshold that determines if an SQL injection attack
    ## is ongoing based on the number of requests that appear to be SQL
    ## injection attacks.
    const sqli_requests_threshold: double = 3.0 &redef;

    ## Interval at which to watch for the
    ## :zeek:id:`HTTP::sqli_requests_threshold` variable to be crossed.
    ## At the end of each interval the counter is reset.
    const sqli_requests_interval = 1min &redef;

    ## Collecting samples will add extra data to notice emails
    ## by collecting some sample SQL injection url paths.  Disable
    ## sample collection by setting this value to 0.
    const collect_SQLi_samples = 5 &redef;

    ## Regular expression is used to match URI based SQL injections.
    const match_sql_injection_uri =
          /[\?&][^[:blank:]\x00-\x1f\|]+?=[\-[:alnum:]%]+([[:blank:]\x00-\x1f]|\/\*.*?\*\/)*['"]?([[:blank:]\x00-\x1f]|\/\*.*?\*\/|\)?;)+.*?([hH][aA][vV][iI][nN][gG]|[uU][nN][iI][oO][nN]|[eE][xX][eE][cC]|[sS][eE][lL][eE][cC][tT]|[dD][eE][lL][eE][tT][eE]|[dD][rR][oO][pP]|[dD][eE][cC][lL][aA][rR][eE]|[cC][rR][eE][aA][tT][eE]|[iI][nN][sS][eE][rR][tT])([[:blank:]\x00-\x1f]|\/\*.*?\*\/)+/
        | /[\?&][^[:blank:]\x00-\x1f\|]+?=[\-0-9%]+([[:blank:]\x00-\x1f]|\/\*.*?\*\/)*['"]?([[:blank:]\x00-\x1f]|\/\*.*?\*\/|\)?;)+([xX]?[oO][rR]|[nN]?[aA][nN][dD])([[:blank:]\x00-\x1f]|\/\*.*?\*\/)+['"]?(([^a-zA-Z&]+)?=|[eE][xX][iI][sS][tT][sS])/
        | /[\?&][^[:blank:]\x00-\x1f]+?=[\-0-9%]*([[:blank:]\x00-\x1f]|\/\*.*?\*\/)*['"]([[:blank:]\x00-\x1f]|\/\*.*?\*\/)*(-|=|\+|\|\|)([[:blank:]\x00-\x1f]|\/\*.*?\*\/)*([0-9]|\(?[cC][oO][nN][vV][eE][rR][tT]|[cC][aA][sS][tT])/
        | /[\?&][^[:blank:]\x00-\x1f\|]+?=([[:blank:]\x00-\x1f]|\/\*.*?\*\/)*['"]([[:blank:]\x00-\x1f]|\/\*.*?\*\/|;)*([xX]?[oO][rR]|[nN]?[aA][nN][dD]|[hH][aA][vV][iI][nN][gG]|[uU][nN][iI][oO][nN]|[eE][xX][eE][cC]|[sS][eE][lL][eE][cC][tT]|[dD][eE][lL][eE][tT][eE]|[dD][rR][oO][pP]|[dD][eE][cC][lL][aA][rR][eE]|[cC][rR][eE][aA][tT][eE]|[rR][eE][gG][eE][xX][pP]|[iI][nN][sS][eE][rR][tT])([[:blank:]\x00-\x1f]|\/\*.*?\*\/|[\[(])+[a-zA-Z&]{2,}/
        | /[\?&][^[:blank:]\x00-\x1f]+?=[^\.]*?([cC][hH][aA][rR]|[aA][sS][cC][iI][iI]|[sS][uU][bB][sS][tT][rR][iI][nN][gG]|[tT][rR][uU][nN][cC][aA][tT][eE]|[vV][eE][rR][sS][iI][oO][nN]|[lL][eE][nN][gG][tT][hH])\(/
        | /\/\*![[:digit:]]{5}.*?\*\// &redef;

    ## A hook that can be used to prevent specific requests from being counted
    ## as an injection attempt.  Use a 'break' statement to exit the hook
    ## early and ignore the request.
    global HTTP::sqli_policy: hook(c: connection, method: string, unescaped_URI: string);
}

function format_sqli_samples(samples: vector of SumStats::Observation): string
    {
    local ret = "SQL Injection samples\n---------------------";
    for ( i in samples )
        ret += "\n" + samples[i]$str;
    return ret;
    }

event zeek_init() &priority=3
    {
    # Add filters to the metrics so that the metrics framework knows how to
    # determine when it looks like an actual attack and how to respond when
    # thresholds are crossed.
    local r1: SumStats::Reducer = [$stream="http.sqli.attacker", $apply=set(SumStats::SUM, SumStats::SAMPLE), $num_samples=collect_SQLi_samples];
    SumStats::create([$name="detect-sqli-attackers",
                      $epoch=sqli_requests_interval,
                      $reducers=set(r1),
                      $threshold_val(key: SumStats::Key, result: SumStats::Result) =
                        {
                        return result["http.sqli.attacker"]$sum;
                        },
                      $threshold=sqli_requests_threshold,
                      $threshold_crossed(key: SumStats::Key, result: SumStats::Result) =
                        {
                        local r = result["http.sqli.attacker"];
                        NOTICE([$note=SQL_Injection_Attacker,
                                $msg="An SQL injection attacker was discovered!",
                                $email_body_sections=vector(format_sqli_samples(r$samples)),
                                $src=key$host,
                                $identifier=cat(key$host)]);
                        }]);

    local r2: SumStats::Reducer = [$stream="http.sqli.victim", $apply=set(SumStats::SUM, SumStats::SAMPLE), $num_samples=collect_SQLi_samples];
    SumStats::create([$name="detect-sqli-victims",
                      $epoch=sqli_requests_interval,
                      $reducers=set(r2),
                      $threshold_val(key: SumStats::Key, result: SumStats::Result) =
                        {
                        return result["http.sqli.victim"]$sum;
                        },
                      $threshold=sqli_requests_threshold,
                      $threshold_crossed(key: SumStats::Key, result: SumStats::Result) =
                        {
                        local r = result["http.sqli.victim"];
                        NOTICE([$note=SQL_Injection_Victim,
                                $msg="An SQL injection victim was discovered!",
                                $email_body_sections=vector(format_sqli_samples(r$samples)),
                                $src=key$host,
                                $identifier=cat(key$host)]);
                        }]);
    }

event http_request(c: connection, method: string, original_URI: string,
                   unescaped_URI: string, version: string) &priority=3
    {
    if ( ! hook HTTP::sqli_policy(c, method, unescaped_URI) )
        return;

    if ( match_sql_injection_uri in unescaped_URI )
        {
        add c$http$tags[URI_SQLI];

        SumStats::observe("http.sqli.attacker", [$host=c$id$orig_h], [$str=original_URI]);
        SumStats::observe("http.sqli.victim",   [$host=c$id$resp_h], [$str=original_URI]);
        }
    }

### detect-bruteforcing.zeek

##! Detect hosts which are doing password guessing attacks and/or password
##! bruteforcing over SSH.

@load base/protocols/ssh
@load base/frameworks/sumstats
@load base/frameworks/notice
@load base/frameworks/intel

module SSH;

export {
    redef enum Notice::Type += {
        ## Indicates that a host has been identified as crossing the
        ## :zeek:id:`SSH::password_guesses_limit` threshold with
        ## failed logins.
        Password_Guessing,
        ## Indicates that a host previously identified as a "password
        ## guesser" has now had a successful login
        ## attempt. This is not currently implemented.
        Login_By_Password_Guesser,
    };

    redef enum Intel::Where += {
        ## An indicator of the login for the intel framework.
        SSH::SUCCESSFUL_LOGIN,
    };

    ## The number of failed SSH connections before a host is designated as
    ## guessing passwords.
    const password_guesses_limit: double = 5 &redef;

    ## The amount of time to remember presumed non-successful logins to
    ## build a model of a password guesser.
    const guessing_timeout = 1 mins &redef;

    ## This value can be used to exclude hosts or entire networks from being
    ## tracked as potential "guessers". The index represents
    ## client subnets and the yield value represents server subnets.
    const ignore_guessers: table[subnet] of subnet &redef;
}

event zeek_init()
    {
    local r1: SumStats::Reducer = [$stream="ssh.login.failure", $apply=set(SumStats::SUM, SumStats::SAMPLE), $num_samples=5];
    SumStats::create([$name="detect-ssh-bruteforcing",
                      $epoch=guessing_timeout,
                      $reducers=set(r1),
                      $threshold_val(key: SumStats::Key, result: SumStats::Result) =
                        {
                        return result["ssh.login.failure"]$sum;
                        },
                      $threshold=password_guesses_limit,
                      $threshold_crossed(key: SumStats::Key, result: SumStats::Result) =
                        {
                        local r = result["ssh.login.failure"];
                        local sub_msg = fmt("Sampled servers: ");
                        local samples = r$samples;
                        for ( i in samples )
                            {
                            if ( samples[i]?$str )
                                sub_msg = fmt("%s%s %s", sub_msg, i==0 ? "":",", samples[i]$str);
                            }
                        # Generate the notice.
                        NOTICE([$note=Password_Guessing,
                                $msg=fmt("%s appears to be guessing SSH passwords (seen in %d connections).", key$host, r$num),
                                $sub=sub_msg,
                                $src=key$host,
                                $identifier=cat(key$host)]);
                        }]);
    }

event ssh_auth_successful(c: connection, auth_method_none: bool)
    {
    local id = c$id;

    Intel::seen([$host=id$orig_h,
                 $conn=c,
                 $where=SSH::SUCCESSFUL_LOGIN]);
    }

event ssh_auth_failed(c: connection)
    {
    local id = c$id;

    # Add data to the FAILED_LOGIN metric unless this connection should
    # be ignored.
    if ( ! (id$orig_h in ignore_guessers &&
            id$resp_h in ignore_guessers[id$orig_h]) )
        SumStats::observe("ssh.login.failure", [$host=id$orig_h], [$str=cat(id$resp_h)]);
    }
bbannier commented 6 months ago

We do not do support in issues (or discussions for that matter). The channels to request support are our forum or Zeek Slack.


As general feedback, it is not clear to me how to reproduce this issue with the given information. In particular when base scripts are changed making clear every changed bit is crucial. Typically a better approach is to change configuration values in local.zeek (the variables you changed are all marked &redef), e.g.,

@load policy/protocols/ssh/detect-bruteforcing
@load policy/protocols/http/detect-sqli

redef SSH::password_guesses_limit = 5;
redef SSH::guessing_timeout = 1min;

redef HTTP::sqli_requests_threshold = 3.0;
redef HTTP::sqli_requests_interval = 1min;

For my setup this produces Zeek SQL injection notices if I perform the following query derived from the Zeek test suite

curl "http://example.com/index.asp?ID=1'+139+'0"

This also produces SSH bruteforcing notices for me when I run against this file in the Zeek test suite.