ftp: support STOU data transfers

Support STOU on the FTP data channel.

With STOU a filename is optional. If no filname is provided, use
"<stou>". The server does respond with the filename to use, but there
can be a race condition in getting this filename and setting up file
storage before data starts flowing.

Ticket: #8664
pull/15726/head
Jason Ish 3 weeks ago committed by Victor Julien
parent e65bf4c905
commit f59960d42b

@ -7,12 +7,13 @@ ftpdata_command
---------------
Filter ftp-data channel based on command used on the FTP command channel.
Currently supported commands are STOR and APPE (put/upload a file), RETR (get a
file), and NLST, LIST and MLSD (list directory contents).
Currently supported commands are STOR and APPE (put/upload a file), STOU (store
a file with a unique name), RETR (get a file), and NLST, LIST and MLSD (list
directory contents).
Syntax::
ftpdata_command:(stor|appe|retr|nlst|list|mlsd)
ftpdata_command:(stor|appe|stou|retr|nlst|list|mlsd)
Signature Example:

@ -481,7 +481,8 @@ static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserSt
* control direction.
*/
if ((state->active &&
(state->command == FTP_COMMAND_STOR || state->command == FTP_COMMAND_APPE)) ||
(state->command == FTP_COMMAND_STOR || state->command == FTP_COMMAND_APPE ||
state->command == FTP_COMMAND_STOU)) ||
(!state->active &&
(state->command == FTP_COMMAND_RETR || state->command == FTP_COMMAND_NLST ||
state->command == FTP_COMMAND_LIST ||
@ -528,6 +529,11 @@ static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserSt
}
has_file = true;
/* fallthrough */
case FTP_COMMAND_STOU:
if (line.len >= 6) {
has_file = true;
}
/* fallthrough */
case FTP_COMMAND_NLST:
case FTP_COMMAND_LIST:
case FTP_COMMAND_MLSD: {
@ -563,6 +569,17 @@ static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserSt
data->file_name[file_name_len] = 0;
data->file_len = (uint16_t)file_name_len;
memcpy(data->file_name, line.buf + 5, file_name_len);
} else if (state->command == FTP_COMMAND_STOU) {
const char default_file_name[] = "<stou>";
uint32_t file_name_len = sizeof(default_file_name);
data->file_name = FTPCalloc(file_name_len, sizeof(char));
if (data->file_name == NULL) {
FtpTransferCmdFree(data);
SCReturnStruct(APP_LAYER_ERROR);
}
data->file_name[file_name_len - 1] = 0;
data->file_len = (uint16_t)file_name_len - 1;
memcpy(data->file_name, default_file_name, file_name_len);
}
int ret = AppLayerExpectationCreate(
f, direction, 0, state->dyn_port, ALPROTO_FTPDATA, data);
@ -1098,6 +1115,11 @@ static AppLayerResult FTPDataParse(Flow *f, FtpDataState *ftpdata_state,
SCLogDebug("APPE data to %s",
(ftpdata_state->direction & STREAM_TOSERVER) ? "toserver" : "toclient");
break;
case FTP_COMMAND_STOU:
ftpdata_state->direction = data->direction;
SCLogDebug("STOU data to %s",
(ftpdata_state->direction & STREAM_TOSERVER) ? "toserver" : "toclient");
break;
case FTP_COMMAND_RETR:
ftpdata_state->direction = data->direction;
SCLogDebug("RETR data to %s",
@ -1472,6 +1494,9 @@ bool EveFTPDataAddMetadata(void *vtx, SCJsonBuilder *jb)
case FTP_COMMAND_APPE:
JB_SET_STRING(jb, "command", "APPE");
break;
case FTP_COMMAND_STOU:
JB_SET_STRING(jb, "command", "STOU");
break;
case FTP_COMMAND_RETR:
JB_SET_STRING(jb, "command", "RETR");
break;
@ -1641,4 +1666,3 @@ void FTPParserRegisterTests(void)
UtRegisterTest("FTPParserTest12", FTPParserTest12);
#endif /* UNITTESTS */
}

@ -37,7 +37,7 @@
/**
* \brief Regex for parsing our keyword options
*/
#define PARSE_REGEX "^\\s*(stor|appe|retr|nlst|list|mlsd)\\s*$"
#define PARSE_REGEX "^\\s*(stor|appe|stou|retr|nlst|list|mlsd)\\s*$"
static DetectParseRegex parse_regex;
/* Prototypes of functions registered in DetectFtpdataRegister below */
@ -145,6 +145,8 @@ static DetectFtpdataData *DetectFtpdataParse(const char *ftpcommandstr)
ftpcommandd->command = FTP_COMMAND_STOR;
} else if (!strcmp(arg1, "appe")) {
ftpcommandd->command = FTP_COMMAND_APPE;
} else if (!strcmp(arg1, "stou")) {
ftpcommandd->command = FTP_COMMAND_STOU;
} else if (!strcmp(arg1, "retr")) {
ftpcommandd->command = FTP_COMMAND_RETR;
} else if (!strcmp(arg1, "nlst")) {
@ -225,6 +227,11 @@ static int DetectFtpdataParseTest01(void)
FAIL_IF(!(ftpcommandd->command == FTP_COMMAND_APPE));
DetectFtpdataFree(NULL, ftpcommandd);
ftpcommandd = DetectFtpdataParse("stou");
FAIL_IF_NULL(ftpcommandd);
FAIL_IF(!(ftpcommandd->command == FTP_COMMAND_STOU));
DetectFtpdataFree(NULL, ftpcommandd);
ftpcommandd = DetectFtpdataParse("list");
FAIL_IF_NULL(ftpcommandd);
FAIL_IF(!(ftpcommandd->command == FTP_COMMAND_LIST));
@ -250,13 +257,16 @@ static int DetectFtpdataSignatureTest01(void)
de_ctx, "alert ip any any -> any any (ftpdata_command:appe; sid:3; rev:1;)");
FAIL_IF_NULL(sig);
sig = DetectEngineAppendSig(
de_ctx, "alert ip any any -> any any (ftpdata_command:list; sid:4; rev:1;)");
de_ctx, "alert ip any any -> any any (ftpdata_command:stou; sid:4; rev:1;)");
FAIL_IF_NULL(sig);
sig = DetectEngineAppendSig(
de_ctx, "alert ip any any -> any any (ftpdata_command:list; sid:5; rev:1;)");
FAIL_IF_NULL(sig);
sig = DetectEngineAppendSig(
de_ctx, "alert ip any any -> any any (ftpdata_command:mlsd; sid:5; rev:1;)");
de_ctx, "alert ip any any -> any any (ftpdata_command:mlsd; sid:6; rev:1;)");
FAIL_IF_NULL(sig);
sig = DetectEngineAppendSig(
de_ctx, "alert ip any any -> any any (ftpdata_command:xxx; sid:6; rev:1;)");
de_ctx, "alert ip any any -> any any (ftpdata_command:xxx; sid:7; rev:1;)");
FAIL_IF_NOT_NULL(sig);
DetectEngineCtxFree(de_ctx);

Loading…
Cancel
Save