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".
pull/3810/head
Jeff Lucovsky 7 years ago committed by Victor Julien
parent 164fb71898
commit 4f33b8c18d

@ -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);

@ -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() */

Loading…
Cancel
Save