diff --git a/doc/userguide/rules/ftp-keywords.rst b/doc/userguide/rules/ftp-keywords.rst index 8249b6aa35..849f37c02d 100644 --- a/doc/userguide/rules/ftp-keywords.rst +++ b/doc/userguide/rules/ftp-keywords.rst @@ -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: diff --git a/src/app-layer-ftp.c b/src/app-layer-ftp.c index a1164e5b07..f33ecfd55f 100644 --- a/src/app-layer-ftp.c +++ b/src/app-layer-ftp.c @@ -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[] = ""; + 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 */ } - diff --git a/src/detect-ftpdata.c b/src/detect-ftpdata.c index eadc6e2126..d6c03940ba 100644 --- a/src/detect-ftpdata.c +++ b/src/detect-ftpdata.c @@ -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);