Skip to content

Commit 06a0770

Browse files
committed
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: OISF#8664
1 parent 49d9379 commit 06a0770

3 files changed

Lines changed: 44 additions & 9 deletions

File tree

doc/userguide/rules/ftp-keywords.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ ftpdata_command
77
---------------
88

99
Filter ftp-data channel based on command used on the FTP command channel.
10-
Currently supported commands are STOR and APPE (put/upload a file), RETR (get a
11-
file), and NLST, LIST and MLSD (list directory contents).
10+
Currently supported commands are STOR and APPE (put/upload a file), STOU (store
11+
a file with a unique name), RETR (get a file), and NLST, LIST and MLSD (list
12+
directory contents).
1213

1314
Syntax::
1415

15-
ftpdata_command:(stor|appe|retr|nlst|list|mlsd)
16+
ftpdata_command:(stor|appe|stou|retr|nlst|list|mlsd)
1617

1718
Signature Example:
1819

src/app-layer-ftp.c

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,8 @@ static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserSt
481481
* control direction.
482482
*/
483483
if ((state->active &&
484-
(state->command == FTP_COMMAND_STOR || state->command == FTP_COMMAND_APPE)) ||
484+
(state->command == FTP_COMMAND_STOR || state->command == FTP_COMMAND_APPE ||
485+
state->command == FTP_COMMAND_STOU)) ||
485486
(!state->active &&
486487
(state->command == FTP_COMMAND_RETR || state->command == FTP_COMMAND_NLST ||
487488
state->command == FTP_COMMAND_LIST ||
@@ -532,6 +533,11 @@ static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserSt
532533
}
533534
has_file = true;
534535
/* fallthrough */
536+
case FTP_COMMAND_STOU:
537+
if (line.len >= 6) {
538+
has_file = true;
539+
}
540+
/* fallthrough */
535541
case FTP_COMMAND_NLST:
536542
case FTP_COMMAND_LIST:
537543
case FTP_COMMAND_MLSD: {
@@ -567,6 +573,17 @@ static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserSt
567573
data->file_name[file_name_len] = 0;
568574
data->file_len = (uint16_t)file_name_len;
569575
memcpy(data->file_name, line.buf + 5, file_name_len);
576+
} else if (state->command == FTP_COMMAND_STOU) {
577+
const char default_file_name[] = "<stou>";
578+
uint32_t file_name_len = sizeof(default_file_name);
579+
data->file_name = FTPCalloc(file_name_len, sizeof(char));
580+
if (data->file_name == NULL) {
581+
FtpTransferCmdFree(data);
582+
SCReturnStruct(APP_LAYER_ERROR);
583+
}
584+
data->file_name[file_name_len - 1] = 0;
585+
data->file_len = (uint16_t)file_name_len - 1;
586+
memcpy(data->file_name, default_file_name, file_name_len);
570587
}
571588
int ret = AppLayerExpectationCreate(
572589
f, direction, 0, state->dyn_port, ALPROTO_FTPDATA, data);
@@ -1102,6 +1119,11 @@ static AppLayerResult FTPDataParse(Flow *f, FtpDataState *ftpdata_state,
11021119
SCLogDebug("APPE data to %s",
11031120
(ftpdata_state->direction & STREAM_TOSERVER) ? "toserver" : "toclient");
11041121
break;
1122+
case FTP_COMMAND_STOU:
1123+
ftpdata_state->direction = data->direction;
1124+
SCLogDebug("STOU data to %s",
1125+
(ftpdata_state->direction & STREAM_TOSERVER) ? "toserver" : "toclient");
1126+
break;
11051127
case FTP_COMMAND_RETR:
11061128
ftpdata_state->direction = data->direction;
11071129
SCLogDebug("RETR data to %s",
@@ -1476,6 +1498,9 @@ bool EveFTPDataAddMetadata(void *vtx, SCJsonBuilder *jb)
14761498
case FTP_COMMAND_APPE:
14771499
JB_SET_STRING(jb, "command", "APPE");
14781500
break;
1501+
case FTP_COMMAND_STOU:
1502+
JB_SET_STRING(jb, "command", "STOU");
1503+
break;
14791504
case FTP_COMMAND_RETR:
14801505
JB_SET_STRING(jb, "command", "RETR");
14811506
break;
@@ -1690,4 +1715,3 @@ void FTPParserRegisterTests(void)
16901715
UtRegisterTest("FTPParserTest13", FTPParserTest13);
16911716
#endif /* UNITTESTS */
16921717
}
1693-

src/detect-ftpdata.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
/**
3838
* \brief Regex for parsing our keyword options
3939
*/
40-
#define PARSE_REGEX "^\\s*(stor|appe|retr|nlst|list|mlsd)\\s*$"
40+
#define PARSE_REGEX "^\\s*(stor|appe|stou|retr|nlst|list|mlsd)\\s*$"
4141
static DetectParseRegex parse_regex;
4242

4343
/* Prototypes of functions registered in DetectFtpdataRegister below */
@@ -145,6 +145,8 @@ static DetectFtpdataData *DetectFtpdataParse(const char *ftpcommandstr)
145145
ftpcommandd->command = FTP_COMMAND_STOR;
146146
} else if (!strcmp(arg1, "appe")) {
147147
ftpcommandd->command = FTP_COMMAND_APPE;
148+
} else if (!strcmp(arg1, "stou")) {
149+
ftpcommandd->command = FTP_COMMAND_STOU;
148150
} else if (!strcmp(arg1, "retr")) {
149151
ftpcommandd->command = FTP_COMMAND_RETR;
150152
} else if (!strcmp(arg1, "nlst")) {
@@ -225,6 +227,11 @@ static int DetectFtpdataParseTest01(void)
225227
FAIL_IF(!(ftpcommandd->command == FTP_COMMAND_APPE));
226228
DetectFtpdataFree(NULL, ftpcommandd);
227229

230+
ftpcommandd = DetectFtpdataParse("stou");
231+
FAIL_IF_NULL(ftpcommandd);
232+
FAIL_IF(!(ftpcommandd->command == FTP_COMMAND_STOU));
233+
DetectFtpdataFree(NULL, ftpcommandd);
234+
228235
ftpcommandd = DetectFtpdataParse("list");
229236
FAIL_IF_NULL(ftpcommandd);
230237
FAIL_IF(!(ftpcommandd->command == FTP_COMMAND_LIST));
@@ -250,13 +257,16 @@ static int DetectFtpdataSignatureTest01(void)
250257
de_ctx, "alert ip any any -> any any (ftpdata_command:appe; sid:3; rev:1;)");
251258
FAIL_IF_NULL(sig);
252259
sig = DetectEngineAppendSig(
253-
de_ctx, "alert ip any any -> any any (ftpdata_command:list; sid:4; rev:1;)");
260+
de_ctx, "alert ip any any -> any any (ftpdata_command:stou; sid:4; rev:1;)");
261+
FAIL_IF_NULL(sig);
262+
sig = DetectEngineAppendSig(
263+
de_ctx, "alert ip any any -> any any (ftpdata_command:list; sid:5; rev:1;)");
254264
FAIL_IF_NULL(sig);
255265
sig = DetectEngineAppendSig(
256-
de_ctx, "alert ip any any -> any any (ftpdata_command:mlsd; sid:5; rev:1;)");
266+
de_ctx, "alert ip any any -> any any (ftpdata_command:mlsd; sid:6; rev:1;)");
257267
FAIL_IF_NULL(sig);
258268
sig = DetectEngineAppendSig(
259-
de_ctx, "alert ip any any -> any any (ftpdata_command:xxx; sid:6; rev:1;)");
269+
de_ctx, "alert ip any any -> any any (ftpdata_command:xxx; sid:7; rev:1;)");
260270
FAIL_IF_NOT_NULL(sig);
261271

262272
DetectEngineCtxFree(de_ctx);

0 commit comments

Comments
 (0)