# $Id: ftp-anonymizer.bro 47 2004-06-11 07:26:32Z vern $ @load ftp @load anon # Definitions of constants. # Check if those commands carry any argument; anonymize non-empty # argument. const ftp_cmds_with_no_arg = { "CDUP", "QUIT", "REIN", "PASV", "STOU", "ABOR", "PWD", "SYST", "NOOP", "FEAT", "XPWD", }; const ftp_cmds_with_file_arg = { "APPE", "CWD", "DELE", "LIST", "MKD", "NLST", "RMD", "RNFR", "RNTO", "RETR", "STAT", "STOR", "SMNT", # FTP extensions "SIZE", "MDTM", "MLSD", "MLST", "XCWD", }; # For following commands, we check if the argument conforms to the # specification -- if so, it is safe to be left in the clear. const ftp_cmds_with_safe_arg = { "TYPE", "STRU", "MODE", "ALLO", "REST", "HELP", "MACB", # MacBinary encoding }; # ftp_other_cmds can be redefined in site/trace-specific ways. const ftp_other_cmds = { "LPRT", "OPTS", "CLNT", "RETP", "EPSV", "XPWD", "SOCK", # old FTP command (RFC 354) } &redef; # Below defines patterns of arguments of FTP commands # The following patterns are case-insensitive const ftp_safe_cmd_arg_pattern = /TYPE (([AE]( [NTC])?)|I|(L [0-9]+))/ | /STRU [FRP]/ | /MODE [SBC]/ | /ALLO [0-9]+([ \t]+R[ \t]+[0-9]+)?/ | /REST [!-~]+/ | /MACB (E|DISABLE|ENABLE)/ | /SITE TRUTH ON/ &redef; # The following list includes privacy-safe [cmd, arg] pairs and can be # customized for particular traces const ftp_safe_arg_list: set[string, string] = { } &redef; # ftp_special_cmd_args offers an even more flexible way of customizing # argument anonymization: for each [cmd, arg] pair in the table, the # corresponding value will be the anonymized argument. const ftp_special_cmd_args: table[string, string] of string = { } &redef; # The following words are safe to be left in the clear as the argument # of a HELP command. const ftp_help_words = { "USER", "PORT", "STOR", "MSAM", "RNTO", "NLST", "MKD", "CDUP", "PASS", "PASV", "APPE", "MRSQ", "ABOR", "SITE", "XMKD", "XCUP", "ACCT", "TYPE", "MLFL", "MRCP", "DELE", "SYST", "RMD", "STOU", "SMNT", "STRU", "MAIL", "ALLO", "CWD", "STAT", "XRMD", "SIZE", "REIN", "MODE", "MSND", "REST", "XCWD", "HELP", "PWD", "MDTM", "QUIT", "RETR", "MSOM", "RNFR", "LIST", "NOOP", "XPWD", } &redef; const ftp_port_pat = /[0-9]+([[:blank:]]*,[[:blank:]]*[0-9]+){5}/; # Pattern for the argument of EPRT command. # TODO: the pattern works fot the common case but is not RFC2428-complete. const ftp_eprt_pat = /\|1\|[0-9]{1,3}(\.[0-9]{1,3}){3}\|[0-9]{1,5}\|/; # IP addresses. const ftp_ip_pat = /[0-9]{1,3}(\.[0-9]{1,3}){3}/; # Domain names (deficiency: domain suffices of countries). const ftp_domain_name_pat = /([\-0-9a-zA-Z]+\.)+(com|edu|net|org|gov|mil|uk|fr|nl|es|jp|it)/; # File names (printable characters). const ftp_file_name_pat = /[[:print:]]+/; # File names that can be left in the clear. const ftp_public_files = /\// | /\.\./ # "/" and ".." | /(\/etc\/|master\.)?(passwd|shadow|s?pwd\.db)/ # ftp_hot_files | /\/(etc|usr\/bin|bin|sbin|kernel)(\/)?/ | /\.rhosts/ | /\.forward/ # ftp_hot_guest_files &redef; const ftp_sensitive_files = /.*(etc\/|master\.)?(passwd|shadow|s?pwd\.db)/ # ftp_hot_files | /\/(etc|usr\/bin|bin|sbin|kernel)\/.*/ | /.*\.rhosts/ | /.*\.forward/ # ftp_hot_guest_files &redef; # Public servers. const ftp_public_servers: set[addr] = {} &redef; # Whether we keep all file names (valid or invalid) for public servers. const ftp_keep_all_files_for_public_servers = F &redef; # Public files. const ftp_known_public_files: set[addr, string] = {} &redef; # Hidden file/directory. const ftp_hidden_file = /.*\/\.[^.\/].*/; const ftp_public_hidden_file = /0/ &redef; # Options for file commands (LIST, NLST) that can be left in the clear. const ftp_known_option = /-[[:alpha:]]{1,5}[ ]*/; const ftp_known_site_cmd = { "UMASK", "GROUP", "INDEX", "GROUPS", "IDLE", "GPASS", "EXEC", "CHECKMETHOD", "CHMOD", "NEWER", "ALIAS", "CHECKSUM", "HELP", "MINFO", "CDPATH", "TRUTH", "UTIME", } &redef; const ftp_sensitive_ids: set[string] = { "backdoor", "bomb", "diag", "gdm", "issadmin", "msql", "netfrack", "netphrack", "own", "r00t", "root", "ruut", "smtp", "sundiag", "sync", "sys", "sysadm", "sysdiag", "sysop", "sysoper", "system", "toor", "tour", "y0uar3ownd", }; redef anonymize_ip_addr = T; redef rewriting_ftp_trace = T; global ftp_anon_log = open_log_file("ftp-anon") &redef; # Anonymized arguments, indexed by the anonymization seed. global anonymized_args: table[string] of string; # Arguments left in the clear, indexed by the argument and the context. global ftp_arg_left_in_the_clear: set[string, string]; # Valid files on public servers. global ftp_valid_public_files: set[addr, string]; type ftp_cmd_arg_anon_result: record { anonymized: bool; cmd: string; arg: string; }; # Whether anonymize_trace_specific_cmd_arg is defined: const trace_specific_cmd_arg_anonymization = F &redef; # This function is to be defined in a trace-specific script. By # default, use ftp-anonymizer-trace.bro. global anonymize_trace_specific_cmd_arg: function(session: ftp_session_info, cmd: string, arg: string): ftp_cmd_arg_anon_result; # Anonymize FTP replies by message patterns. const process_ftp_reply_by_message_pattern = F &redef; global anonymize_ftp_reply_by_msg_pattern: function(code: count, act_msg: string, cmd_arg: ftp_cmd_arg, session: ftp_session_info): string; # Anonymize an argument *completely* with a hash value of the string, # and log the anonymization. function anonymize_arg(typ: string, session: ftp_session_info, cmd: string, arg: string, seed: string): string { if ( arg == "" ) return ""; # an empty argument is safe local arg_seed = string_cat(typ, seed, arg); if ( arg_seed in anonymized_args ) return anonymized_args[arg_seed]; local a = anonymize_string(arg_seed); anonymized_args[arg_seed] = a; print ftp_anon_log, fmt("anonymize_arg: (%s) {%s} %s \"%s\" to \"%s\" in [%s]", typ, seed, cmd, to_string_literal(arg), to_string_literal(a), id_string(session$connection_id)); return a; } # This function is called whenever an argument is to be left in the # clear. It logs the action if it hasn't occurred before. function leave_in_the_clear(msg: string, session: ftp_session_info, arg: string, context: string): string { if ( [arg, context] !in ftp_arg_left_in_the_clear ) { add ftp_arg_left_in_the_clear[arg, context]; print ftp_anon_log, fmt("leave_in_the_clear: (%s) \"%s\" [%s] in [%s]", msg, to_string_literal(arg), context, id_string(session$connection_id)); } return arg; } # Sometimes the argument of a file command contains an option string # before the file name, such as in 'LIST -l /xyz/', the following # function identifies such option strings and separate the argument # accordingly. type separate_option_str_result: record { opt_str: string; file_name: string; }; function separate_option_str(file_name: string): separate_option_str_result { local ret: separate_option_str_result; if ( file_name == /-[[:alpha:]]+( .*)?/ ) { local parts = split_all(file_name, /-[[:alpha:]]+[ ]*/); ret$opt_str = string_cat(parts[1], parts[2]); parts[1] = ""; parts[2] = ""; ret$file_name = cat_string_array(parts); return ret; } else return [$opt_str = "", $file_name = file_name]; } # Anonymize a user id type login_status_type: enum { LOGIN_PENDING, LOGIN_SUCCESSFUL, LOGIN_FAILED, LOGIN_UNKNOWN, }; function anonymize_user_id(session: ftp_session_info, id: string, login_status: login_status_type, msg: string): string { if ( id in ftp_guest_ids ) { leave_in_the_clear("guest_id", session, id, msg); return id; } else if ( id in ftp_sensitive_ids && login_status == LOGIN_FAILED ) { leave_in_the_clear("sensitive_id", session, id, msg); return id; } else return anonymize_arg("user_name", session, "USER", id, cat(session$connection_id$resp_h, login_status)); } # Anonymize a file name argument. function anonymize_file_name_arg(session: ftp_session_info, cmd: string, arg: string, valid_file_name: bool): string { local file_name = arg; local opt_str = ""; if ( cmd == /LIST|NLST/ ) { # Separate the option from file name if there is one local ret = separate_option_str(file_name); if ( ret$opt_str != "" ) { opt_str = ret$opt_str; # Shall we anonymize the option string? if ( opt_str != ftp_known_option ) { # Anonymize the option conservatively print ftp_anon_log, fmt("option_anonymized: \"%s\" from (%s %s)", to_string_literal(opt_str), cmd, file_name); opt_str = "-