From: Jeff Lucovsky Date: Sat, 16 Mar 2019 13:54:01 +0000 (-0700) Subject: decode: Improved FTP active mode handling X-Git-Tag: suricata-5.0.0-beta1~39 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4f33b8c18d4c5afa7ec56f4f749b4e0be176ff82;p=thirdparty%2Fsuricata.git decode: Improved FTP active mode handling This changeset addresses 2 issues: - 2459 - 2527 and improves handling for FTP active mode over IPv4 and IPv6. Active mode is triggered when the FTP client conveys the port that should be used for a data connection (PORT, EPRT). When this occurs, the FTP state is marked as "active". --- diff --git a/src/app-layer-ftp.c b/src/app-layer-ftp.c index c36f561a77..782a9c6ec0 100644 --- a/src/app-layer-ftp.c +++ b/src/app-layer-ftp.c @@ -330,6 +330,10 @@ static int FTPParseRequestCommand(void *ftp_state, uint8_t *input, fstate->command = FTP_COMMAND_PORT; } + if (input_len >= 4 && SCMemcmpLowercase("eprt", input, 4) == 0) { + fstate->command = FTP_COMMAND_EPRT; + } + if (input_len >= 8 && SCMemcmpLowercase("auth tls", input, 8) == 0) { fstate->command = FTP_COMMAND_AUTH_TLS; } @@ -374,6 +378,65 @@ static void FtpTransferCmdFree(void *data) FTPFree(cmd, sizeof(struct FtpTransferCmd)); } +static uint16_t ftp_validate_port(int computed_port_value) +{ + unsigned int port_val = computed_port_value; + + if (port_val && port_val > UINT16_MAX) + return 0; + + return ((uint16_t) (port_val)); +} + +/** + * \brief This function extracts a port number from the command input line for IPv6 FTP usage + * \param input input line of the command + * \param input_len length of the request + * + * \retval 0 if a port number could not be extracted; otherwise, the dynamic port number + */ +static uint16_t FTPGetV6PortNumber(uint8_t *input, uint32_t input_len) +{ + uint8_t *ptr = memrchr(input, '|', input_len); + if (ptr == NULL) { + return 0; + } + + int n_length = ptr - input - 1; + if (n_length < 4) + return 0; + + ptr = memrchr(input, '|', n_length); + if (ptr == NULL) + return 0; + + return ftp_validate_port(atoi((char *)ptr + 1)); +} + +/** + * \brief This function extracts a port number from the command input line for IPv4 FTP usage + * \param input input line of the command + * \param input_len length of the request + * + * \retval 0 if a port number could not be extracted; otherwise, the dynamic port number + */ +static uint16_t FTPGetV4PortNumber(uint8_t *input, uint32_t input_len) +{ + uint16_t part1, part2; + uint8_t *ptr = memrchr(input, ',', input_len); + if (ptr == NULL) + return 0; + + part2 = atoi((char *)ptr + 1); + ptr = memrchr(input, ',', (ptr - input) - 1); + if (ptr == NULL) + return 0; + part1 = atoi((char *)ptr + 1); + + return ftp_validate_port(256 * part1 + part2); +} + + /** * \brief This function is called to retrieve a ftp request * \param ftp_state the ftp state structure for the parser @@ -410,19 +473,23 @@ static int FTPParseRequest(Flow *f, void *ftp_state, FTPParseRequestCommand(state, state->current_line, state->current_line_len); switch (state->command) { + case FTP_COMMAND_EPRT: + // fallthrough case FTP_COMMAND_PORT: if (state->current_line_len > state->port_line_size) { + /* Allocate an extra byte for a NULL terminator */ ptmp = FTPRealloc(state->port_line, state->port_line_size, - state->current_line_len); + state->current_line_len + 1); if (ptmp == NULL) { - FTPFree(state->port_line, state->port_line_size); - state->port_line = NULL; - state->port_line_size = 0; + if (state->port_line) { + FTPFree(state->port_line, state->port_line_size); + state->port_line = NULL; + state->port_line_size = 0; + } return 0; } state->port_line = ptmp; - - state->port_line_size = state->current_line_len; + state->port_line_size = state->current_line_len + 1; } memcpy(state->port_line, state->current_line, state->current_line_len); @@ -455,18 +522,23 @@ static int FTPParseRequest(Flow *f, void *ftp_state, memcpy(data->file_name, state->current_line + 5, state->current_line_len - 5); data->cmd = state->command; data->flow_id = FlowGetId(f); - int ret = AppLayerExpectationCreate(f, direction, 0, - state->dyn_port, - ALPROTO_FTPDATA, data); + int ret = AppLayerExpectationCreate(f, + state->active ? STREAM_TOSERVER : direction, + 0, state->dyn_port, ALPROTO_FTPDATA, data); if (ret == -1) { FTPFree(data, sizeof(struct FtpTransferCmd)); SCLogDebug("No expectation created."); SCReturnInt(-1); } else { - SCLogDebug("Expectation created."); + SCLogDebug("Expectation created [direction: %s, dynamic port %"PRIu16"].", + state->active ? "to server" : "to client", + state->dyn_port); } + /* reset the dyn port to avoid duplicate */ state->dyn_port = 0; + /* reset active/passive indicator */ + state->active = false; } break; default: @@ -479,27 +551,17 @@ static int FTPParseRequest(Flow *f, void *ftp_state, static int FTPParsePassiveResponse(Flow *f, FtpState *state, uint8_t *input, uint32_t input_len) { - uint16_t dyn_port; - + uint16_t dyn_port = #ifdef HAVE_RUST - dyn_port = rs_ftp_pasv_response(input, input_len); + rs_ftp_pasv_response(input, input_len); +#else + FTPGetV4PortNumber(input, input_len); +#endif if (dyn_port == 0) { return -1; } -#else - uint16_t part1, part2; - uint8_t *ptr = memrchr(input, ',', input_len); - if (ptr == NULL) - return -1; - - part2 = atoi((char *)ptr + 1); - ptr = memrchr(input, ',', (ptr - input) - 1); - if (ptr == NULL) - return -1; - part1 = atoi((char *)ptr + 1); - - dyn_port = 256 * part1 + part2; -#endif + SCLogDebug("FTP passive mode (v4): dynamic port %"PRIu16"", dyn_port); + state->active = false; state->dyn_port = dyn_port; return 0; @@ -507,27 +569,18 @@ static int FTPParsePassiveResponse(Flow *f, FtpState *state, uint8_t *input, uin static int FTPParsePassiveResponseV6(Flow *f, FtpState *state, uint8_t *input, uint32_t input_len) { + uint16_t dyn_port = #ifdef HAVE_RUST - uint16_t dyn_port = rs_ftp_epsv_response(input, input_len); + rs_ftp_epsv_response(input, input_len); +#else + FTPGetV6PortNumber(input, input_len); +#endif if (dyn_port == 0) { return -1; } - + SCLogDebug("FTP passive mode (v6): dynamic port %"PRIu16"", dyn_port); + state->active = false; state->dyn_port = dyn_port; -#else - uint8_t *ptr = memrchr(input, '|', input_len); - if (ptr == NULL) { - return -1; - } else { - int n_length = ptr - input - 1; - if (n_length < 4) - return -1; - ptr = memrchr(input, '|', n_length); - if (ptr == NULL) - return -1; - } - state->dyn_port = atoi((char *)ptr + 1); -#endif return 0; } @@ -552,6 +605,27 @@ static int FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstat } } + if (state->command == FTP_COMMAND_EPRT) { + uint16_t dyn_port = FTPGetV6PortNumber(state->port_line, state->port_line_len); + if (dyn_port == 0) { + return 0; + } + state->dyn_port = dyn_port; + state->active = true; + SCLogDebug("FTP active mode (v6): dynamic port %"PRIu16"", dyn_port); + } + + if (state->command == FTP_COMMAND_PORT) { + if ((flags & STREAM_TOCLIENT)) { + uint16_t dyn_port = FTPGetV4PortNumber(state->port_line, state->port_line_len); + if (dyn_port == 0) { + return 0; + } + state->dyn_port = dyn_port; + state->active = true; + SCLogDebug("FTP active mode (v4): dynamic port %"PRIu16"", dyn_port); + } + } if (state->command == FTP_COMMAND_PASV) { if (input_len >= 4 && SCMemcmp("227 ", input, 4) == 0) { FTPParsePassiveResponse(f, ftp_state, input, input_len); diff --git a/src/app-layer-ftp.h b/src/app-layer-ftp.h index 84373a8662..144937065c 100644 --- a/src/app-layer-ftp.h +++ b/src/app-layer-ftp.h @@ -78,7 +78,8 @@ typedef enum { FTP_COMMAND_SYST, FTP_COMMAND_TYPE, FTP_COMMAND_UMASK, - FTP_COMMAND_USER + FTP_COMMAND_USER, + FTP_COMMAND_EPRT /** \todo more if missing.. */ } FtpRequestCommand; typedef uint32_t FtpRequestCommandArgOfs; @@ -115,6 +116,7 @@ typedef struct FtpState_ { uint8_t *input; int32_t input_len; uint8_t direction; + bool active; /* --parser details-- */ /** current line extracted by the parser from the call to FTPGetline() */