module Login; # log telnet, rlogin, and rsh events to login.log export { redef enum Log::ID += { ## The logging stream identifier Log_LOGIN }; type Info : record { ## Time the event occurred ts : time &log; ## Unique ID for the connection uid : string &log; ## The connection's 4-tuple of endpoint addresses/port id : conn_id &log; ## proto (telnet, rlogin, or rsh) proto : string &log &optional; ## login_success event was seen (successful login) success : bool &log &default = F; ## login_confused event was seen (successful login) confused : bool &log &default = F; ## username given for login attempt user : string &log &optional; ## client_user given for login attempt (empty for telnet, set for rlogin) client_user : string &log &optional; ## password given for login attempt password : string &log &optional; ## whether or not a line has been written to login.log logged : bool &default = F; }; ## Event that can be handled to access the :zeek:type:`Login::Info` ## record as it is sent on to the logging framework. global log_login : event(rec : Info); } # Add the state tracking information variable to the connection record redef record connection += { login : Info &optional; }; ############################################### # constants borrowed from the old Bro 1.5 login.bro required to make some of the telnet/rlogin/rsh events work correctly # see https://github.com/zeek/zeek/blob/release/1.5/policy/login.bro#L178 # https://github.com/reservoirlabs/brorefguide/blob/master/analysis.texi#L3850 redef skip_authentication = { "WELCOME TO THE BERKELEY PUBLIC LIBRARY", }; redef direct_login_prompts = { "TERMINAL?", }; redef login_prompts = { "Login:", "login:", "Name:", "Username:", "User:", "Member Name", "User Access Verification", "Cisco Systems Console", direct_login_prompts }; redef login_non_failure_msgs = { "Failures", "failures", # probably is " failures since last login" "failure since last successful login", "failures since last successful login", }; redef login_non_failure_msgs = { "Failures", "failures", # probably is " failures since last login" "failure since last successful login", "failures since last successful login", } &redef; redef login_failure_msgs = { "invalid", "Invalid", "incorrect", "Incorrect", "failure", "Failure", # "Unable to authenticate", # "unable to authenticate", "User authorization failure", "Login failed", "INVALID", "Sorry.", "Sorry,", }; const router_prompts: set[string] &redef; redef login_success_msgs = { "Last login", "Last successful login", "Last successful login", "checking for disk quotas", "unsuccessful login attempts", "failure since last successful login", "failures since last successful login", router_prompts, }; redef login_timeouts = { "timeout", "timed out", "Timeout", "Timed out", "Error reading command input", # VMS }; # end borrowed constants from Bro 1.5 login.bro ############################################### # telnet, rlogin, rsh const telnet_port = 23/tcp; const telnet_ports = { telnet_port }; const rlogin_port = 513/tcp; const rlogin_ports = { rlogin_port }; const rsh_port = 514/tcp; const rsh_ports = { rsh_port }; redef likely_server_ports += { telnet_ports, rlogin_ports, rsh_ports }; # set_login_session - if has not yet been registered in the connection, instantiate # the Info record and assign in c$login function set_login_session(c : connection) { if ( ! c?$login ) { local s : Info = [$ts = network_time(), $uid = c$uid, $id = c$id]; switch c$id$resp_p { case telnet_port: s$proto = "telnet"; add c$service["telnet"]; break; case rlogin_port: s$proto = "rlogin"; add c$service["rlogin"]; break; case rsh_port: s$proto = "rsh"; add c$service["rsh"]; break; } c$login = s; } } # login_message - log to login.log function login_message(s : Info) { # strip some values that can happen in a "confused" state that aren't really valid values if (( s?$user ) && (( s$user == "" ) || ( s$user == "" ) || ( s$user == "" ))) delete s$user; if (( s?$client_user ) && (( s$client_user == "" ) || ( s$client_user == "" ) || ( s$client_user == "" ))) delete s$client_user; if (( s?$password ) && (( s$password == "" ) || ( s$password == "" ) || ( s$password == "" ))) delete s$password; if (( s?$proto ) && ( s$proto == "" )) delete s$proto; s$ts = network_time(); Log::write(Login::Log_LOGIN, s); s$logged = T; } # create log stream for login.log and register telnet, rlogin, and rsh analyzers event zeek_init() &priority = 5 { Log::create_stream(Login::Log_LOGIN, [$columns = Info, $ev = log_login, $path = "login"]); Analyzer::register_for_ports(Analyzer::ANALYZER_TELNET, telnet_ports); Analyzer::register_for_ports(Analyzer::ANALYZER_RLOGIN, rlogin_ports); Analyzer::register_for_ports(Analyzer::ANALYZER_RSH, rsh_ports); } # login_confused - Generated when tracking of Telnet/Rlogin authentication failed # https://docs.zeek.org/en/current/scripts/base/bif/plugins/Zeek_Login.events.bif.zeek.html#id-login_confused event login_confused(c : connection, msg : string, line : string) &priority = 5 { # print "login_confused", msg, line; set_login_session(c); c$login$confused = T; } # login_failure - Generated when tracking of Telnet/Rlogin authentication failed # https://docs.zeek.org/en/current/scripts/base/bif/plugins/Zeek_Login.events.bif.zeek.html#id-login_failure event login_failure(c : connection, user : string, client_user : string, password : string, line : string) &priority = 5 { # print "login_failure", user, client_user, password, line; set_login_session(c); if ((!c$login?$user) || (c$login$user == "")) c$login$user = user; if ((!c$login?$client_user) || (c$login$client_user == "")) c$login$client_user = client_user; if ((!c$login?$password) || (c$login$password == "")) c$login$password = password; login_message(c$login); } # login_success - Generated for successful Telnet/Rlogin logins # https://docs.zeek.org/en/current/scripts/base/bif/plugins/Zeek_Login.events.bif.zeek.html#id-login_success event login_success(c : connection, user : string, client_user : string, password : string, line : string) &priority = 5 { # print "login_success", user, client_user, password, line; set_login_session(c); c$login$success = T; c$login$user = user; c$login$client_user = client_user; # it appears for a successful login with rsh where client_user was checked, what we're getting in # the "password" field is actually not the password, but the first line of data if ((c$login$proto != "rsh") || (c$login$client_user == "")) c$login$password = password; login_message(c$login); } event connection_state_remove(c : connection) &priority = -5 { if (c?$login) { if ( c$login$logged == F) { login_message(c$login); } delete c$login; } } # for testing: # for file in /host/telnet/*; do cd /tmp; mkdir -p /host/logs/"$(basename "$file")"; /bin/rm -f /host/logs/"$(basename "$file")"/*; cd /host/logs/"$(basename "$file")"; zeek -r "$file" local > debug_output.txt; cd /tmp; done # event activating_encryption(c: connection) { print "activating_encryption"; } # event authentication_accepted(name: string, c: connection) { print "authentication_accepted", name; } # event authentication_rejected(name: string, c: connection) { print "authentication_rejected", name; } # event authentication_skipped(c: connection) { print "authentication_skipped"; } # event bad_option(c: connection) { print "bad_option"; } # event bad_option_termination(c: connection) { print "bad_option_termination"; } # event inconsistent_option(c: connection) { print "inconsistent_option"; } # event login_confused_text(c: connection, line: string) { print "login_confused_text", line; } # event login_display(c: connection, display: string) { print "login_display", display; } # event login_input_line(c: connection, line: string) { print "login_input_line", line; } # event login_output_line(c: connection, line: string) { print "login_output_line", line; } # event login_terminal(c: connection, terminal: string) { print "login_terminal", terminal; } # event rsh_reply(c: connection, client_user: string, server_user: string, line: string) { print "rsh_reply", client_user, server_user, line; } # event rsh_request(c: connection, client_user: string, server_user: string, line: string; new_session: bool) { print "rsh_request", client_user, server_user, line, new_session; }