Merge commit 'd3432829c9' into topic/policy-scripts-new

* commit 'd3432829c9':
  Fixed some problems with the FTP analysis.
This commit is contained in:
Seth Hall 2011-03-16 17:03:15 -04:00
commit b6f6606398
2 changed files with 47 additions and 28 deletions

View file

@ -2,6 +2,7 @@ module FTP;
export { export {
type CmdArg: record { type CmdArg: record {
ts: time;
cmd: string &default="<unknown>"; cmd: string &default="<unknown>";
arg: string &default=""; arg: string &default="";
seq: count &default=0; seq: count &default=0;
@ -68,7 +69,7 @@ export {
function add_pending_cmd(pc: PendingCmds, cmd: string, arg: string): CmdArg function add_pending_cmd(pc: PendingCmds, cmd: string, arg: string): CmdArg
{ {
local ca = [$cmd = cmd, $arg = arg, $seq=|pc|+1]; local ca = [$cmd = cmd, $arg = arg, $seq=|pc|+1, $ts=network_time()];
pc[ca$seq] = ca; pc[ca$seq] = ca;
return ca; return ca;

View file

@ -1,18 +1,25 @@
##! The logging this script does is primarily focused on logging FTP commands
##! along with metadata. For example, if files are transferred, the argument
##! will take on the full path that the client is at along with the requested
##! file name.
##!
##! TODO:
##! * Handle encrypted sessions correctly (get an example?)
##! * Detect client software with CLNT command
##! * Detect server software with initial 220 message
##! * Detect client software with password given for anonymous users
##! (e.g. cyberduck@example.net)
@load functions @load functions
@load notice.bro @load notice.bro
@load ftp-lib @load ftp-lib
# TODO:
# * Handle encrypted sessions correctly (get an example?)
# * Detect client software with CLNT command
# * Detect server software with initial 220 message
# * Detect client software with password given for anonymous users (e.g. cyberduck@example.net)
module FTP; module FTP;
redef enum Notice::Type += { redef enum Notice::Type += {
## This indicates that a "SITE EXEC" command/arg pair was seen. ## This indicates that a successful response to a "SITE EXEC"
FTP_SiteExec, ## command/arg pair was seen.
FTP_Site_Exec_Success,
}; };
export { export {
@ -36,7 +43,10 @@ export {
id: conn_id; id: conn_id;
user: string &default="<unknown>"; user: string &default="<unknown>";
password: string &optional; password: string &optional;
cwd: string &default="<before_login>/"; ## By setting the CWD to '/.', we can indicate that unless something
## more concrete is discovered that the exiting but unknown
## directory is ok to use.
cwd: string &default="/.";
command: CmdArg &optional; command: CmdArg &optional;
reply_code: count &default=0; reply_code: count &default=0;
reply_msg: string &default=""; reply_msg: string &default="";
@ -69,15 +79,23 @@ export {
## The list of commands that should have their command/response pairs logged. ## The list of commands that should have their command/response pairs logged.
const logged_commands = { const logged_commands = {
"APPE", "DELE", "RETR", "STOR", "STOU", "CLNT", "ACCT", "SITE" "APPE", "DELE", "RETR", "STOR", "STOU", "CLNT", "ACCT"
} &redef; } &redef;
## These are the ports used as the default FTP ports for DPD.
const ports = { 21/tcp } &redef;
## This tracks all of the currently established FTP control sessions. ## This tracks all of the currently established FTP control sessions.
global active_conns: table[conn_id] of SessionInfo &read_expire=15mins; global active_conns: table[conn_id] of SessionInfo &read_expire=5mins;
} }
global ftp_data_expected: table[addr, port] of ExpectedConn &create_expire=5mins; global ftp_data_expected: table[addr, port] of ExpectedConn &create_expire=5mins;
# Configure DPD
redef capture_filters += { ["ftp"] = "port 21" };
redef dpd_config += { [ANALYZER_FTP] = [$ports = ports] };
event bro_init() event bro_init()
{ {
Log::create_stream("FTP", "FTP::Log"); Log::create_stream("FTP", "FTP::Log");
@ -140,12 +158,9 @@ function ftp_message(s: SessionInfo)
local arg = s$command$arg; local arg = s$command$arg;
if ( s$command$cmd in file_cmds ) if ( s$command$cmd in file_cmds )
{ arg = fmt("ftp://%s%s", s$id$resp_h, absolute_path(s$cwd, arg));
local pathfile = sub(absolute_path(s$cwd, arg), /<unknown>/, "/.");
arg = fmt("ftp://%s%s", s$id$resp_h, pathfile);
}
Log::write("FTP", [$ts=network_time(), $id=s$id, Log::write("FTP", [$ts=s$command$ts, $id=s$id,
$user=s$user, $password=pass, $user=s$user, $password=pass,
$command=s$command$cmd, $arg=arg, $command=s$command$cmd, $arg=arg,
$mime_type=s$mime_type, $mime_desc=s$mime_desc, $mime_type=s$mime_type, $mime_desc=s$mime_desc,
@ -178,9 +193,9 @@ event ftp_request(c: connection, command: string, arg: string)
local session = active_conns[id]; local session = active_conns[id];
# Log the previous command when a new command is seen. # Log the previous command when a new command is seen.
# The downside here is that commands definitely aren't logged until the # The downside here is that commands definitely aren't logged until the
# next command is issued or the control session ends. In practicality # next command is issued or the control session ends. In practicality
# this isn't an issue, but I suppose it could be a delay tactic for # this isn't an issue, but I suppose it could be a delay tactic for
# attackers. # attackers.
if ( session?$command && session$has_response ) if ( session?$command && session$has_response )
{ {
@ -215,18 +230,12 @@ event ftp_request(c: connection, command: string, arg: string)
# TODO: raise a notice? does anyone care? # TODO: raise a notice? does anyone care?
} }
} }
if ( command == "SITE" && /[Ee][Xx][Ee][Cc]/ in arg )
{
Notice::NOTICE([$note=FTP_SiteExec, $conn=c,
$msg=fmt("%s %s", command, arg)]);
}
} }
event ftp_reply(c: connection, code: count, msg: string, cont_resp: bool) event ftp_reply(c: connection, code: count, msg: string, cont_resp: bool)
{ {
# TODO: figure out what to do with continued FTP response # TODO: figure out what to do with continued FTP response (not used much)
if ( cont_resp ) return; if ( cont_resp ) return;
local id = c$id; local id = c$id;
@ -241,7 +250,7 @@ event ftp_reply(c: connection, code: count, msg: string, cont_resp: bool)
session$has_response = T; session$has_response = T;
# TODO: do some sort of generic clear text login processing here. # TODO: do some sort of generic clear text login processing here.
#local response_xyz = parse_ftp_reply_code(code); local response_xyz = parse_ftp_reply_code(code);
#if ( response_xyz$x == 2 && # successful #if ( response_xyz$x == 2 && # successful
# session$command$cmd == "PASS" ) # session$command$cmd == "PASS" )
# do_ftp_login(c, session); # do_ftp_login(c, session);
@ -260,9 +269,18 @@ event ftp_reply(c: connection, code: count, msg: string, cont_resp: bool)
# if that's given as well which would be more correct. # if that's given as well which would be more correct.
session$file_size = to_count(msg); session$file_size = to_count(msg);
} }
# If a successful SITE EXEC command is executed, raise a notice.
else if ( response_xyz$x == 2 &&
session$command$cmd == "SITE" &&
/[Ee][Xx][Ee][Cc]/ in session$command$arg )
{
NOTICE([$note=FTP_Site_Exec_Success, $conn=c,
$msg=fmt("%s %s", session$command$cmd, session$command$arg)]);
}
# PASV and EPSV processing # PASV and EPSV processing
if ( (code == 227 || code == 229) && else if ( (code == 227 || code == 229) &&
(session$command$cmd == "PASV" || session$command$cmd == "EPSV") ) (session$command$cmd == "PASV" || session$command$cmd == "EPSV") )
{ {
local data = (code == 227) ? parse_ftp_pasv(msg) : parse_ftp_epsv(msg); local data = (code == 227) ? parse_ftp_pasv(msg) : parse_ftp_epsv(msg);