Skip to content

Commit 49d9379

Browse files
committed
ftp: support APPE data channels
Support APPE on the FTP data channel. Ticket: OISF#8664
1 parent 272fb68 commit 49d9379

3 files changed

Lines changed: 74 additions & 8 deletions

File tree

doc/userguide/rules/ftp-keywords.rst

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

99
Filter ftp-data channel based on command used on the FTP command channel.
10-
Currently supported commands are RETR (get on a file) and STOR (put on a
11-
file).
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).
1212

1313
Syntax::
1414

15-
ftpdata_command:(retr|stor)
15+
ftpdata_command:(stor|appe|retr|nlst|list|mlsd)
1616

1717
Signature Example:
1818

src/app-layer-ftp.c

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,8 @@ static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserSt
480480
* For ftp active mode, data connection direction is opposite to
481481
* control direction.
482482
*/
483-
if ((state->active && state->command == FTP_COMMAND_STOR) ||
483+
if ((state->active &&
484+
(state->command == FTP_COMMAND_STOR || state->command == FTP_COMMAND_APPE)) ||
484485
(!state->active &&
485486
(state->command == FTP_COMMAND_RETR || state->command == FTP_COMMAND_NLST ||
486487
state->command == FTP_COMMAND_LIST ||
@@ -515,6 +516,8 @@ static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserSt
515516
case FTP_COMMAND_RETR:
516517
// fallthrough
517518
case FTP_COMMAND_STOR:
519+
// fallthrough
520+
case FTP_COMMAND_APPE:
518521
/* Ensure that there is a negotiated dyn port and a file
519522
* name -- need more than 5 chars: cmd [4], space, <filename>
520523
*/
@@ -1094,6 +1097,11 @@ static AppLayerResult FTPDataParse(Flow *f, FtpDataState *ftpdata_state,
10941097
SCLogDebug("STOR data to %s",
10951098
(ftpdata_state->direction & STREAM_TOSERVER) ? "toserver" : "toclient");
10961099
break;
1100+
case FTP_COMMAND_APPE:
1101+
ftpdata_state->direction = data->direction;
1102+
SCLogDebug("APPE data to %s",
1103+
(ftpdata_state->direction & STREAM_TOSERVER) ? "toserver" : "toclient");
1104+
break;
10971105
case FTP_COMMAND_RETR:
10981106
ftpdata_state->direction = data->direction;
10991107
SCLogDebug("RETR data to %s",
@@ -1465,6 +1473,9 @@ bool EveFTPDataAddMetadata(void *vtx, SCJsonBuilder *jb)
14651473
case FTP_COMMAND_STOR:
14661474
JB_SET_STRING(jb, "command", "STOR");
14671475
break;
1476+
case FTP_COMMAND_APPE:
1477+
JB_SET_STRING(jb, "command", "APPE");
1478+
break;
14681479
case FTP_COMMAND_RETR:
14691480
JB_SET_STRING(jb, "command", "RETR");
14701481
break;
@@ -1624,6 +1635,50 @@ static int FTPParserTest12(void)
16241635
StreamTcpFreeConfig(true);
16251636
PASS;
16261637
}
1638+
1639+
/** \test Supply APPE without a filename */
1640+
static int FTPParserTest13(void)
1641+
{
1642+
Flow f;
1643+
uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n";
1644+
uint8_t ftpbuf2[] = "APPE\r\n";
1645+
uint8_t ftpbuf3[] = "227 OK\r\n";
1646+
TcpSession ssn;
1647+
1648+
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1649+
1650+
memset(&f, 0, sizeof(f));
1651+
memset(&ssn, 0, sizeof(ssn));
1652+
1653+
f.protoctx = (void *)&ssn;
1654+
f.proto = IPPROTO_TCP;
1655+
f.alproto = ALPROTO_FTP;
1656+
1657+
StreamTcpInitConfig(true);
1658+
1659+
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER | STREAM_START,
1660+
ftpbuf1, sizeof(ftpbuf1) - 1);
1661+
FAIL_IF(r != 0);
1662+
1663+
/* Response */
1664+
r = AppLayerParserParse(
1665+
NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOCLIENT, ftpbuf3, sizeof(ftpbuf3) - 1);
1666+
FAIL_IF(r != 0);
1667+
1668+
r = AppLayerParserParse(
1669+
NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER, ftpbuf2, sizeof(ftpbuf2) - 1);
1670+
FAIL_IF(r != 0);
1671+
1672+
FtpState *ftp_state = f.alstate;
1673+
FAIL_IF_NULL(ftp_state);
1674+
1675+
FAIL_IF(ftp_state->command != FTP_COMMAND_APPE);
1676+
1677+
FLOW_DESTROY(&f);
1678+
AppLayerParserThreadCtxFree(alp_tctx);
1679+
StreamTcpFreeConfig(true);
1680+
PASS;
1681+
}
16271682
#endif /* UNITTESTS */
16281683

16291684
void FTPParserRegisterTests(void)
@@ -1632,6 +1687,7 @@ void FTPParserRegisterTests(void)
16321687
UtRegisterTest("FTPParserTest01", FTPParserTest01);
16331688
UtRegisterTest("FTPParserTest11", FTPParserTest11);
16341689
UtRegisterTest("FTPParserTest12", FTPParserTest12);
1690+
UtRegisterTest("FTPParserTest13", FTPParserTest13);
16351691
#endif /* UNITTESTS */
16361692
}
16371693

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|retr|nlst|list|mlsd)\\s*$"
40+
#define PARSE_REGEX "^\\s*(stor|appe|retr|nlst|list|mlsd)\\s*$"
4141
static DetectParseRegex parse_regex;
4242

4343
/* Prototypes of functions registered in DetectFtpdataRegister below */
@@ -143,6 +143,8 @@ static DetectFtpdataData *DetectFtpdataParse(const char *ftpcommandstr)
143143
goto error;
144144
if (!strcmp(arg1, "stor")) {
145145
ftpcommandd->command = FTP_COMMAND_STOR;
146+
} else if (!strcmp(arg1, "appe")) {
147+
ftpcommandd->command = FTP_COMMAND_APPE;
146148
} else if (!strcmp(arg1, "retr")) {
147149
ftpcommandd->command = FTP_COMMAND_RETR;
148150
} else if (!strcmp(arg1, "nlst")) {
@@ -218,6 +220,11 @@ static int DetectFtpdataParseTest01(void)
218220
FAIL_IF(!(ftpcommandd->command == FTP_COMMAND_STOR));
219221
DetectFtpdataFree(NULL, ftpcommandd);
220222

223+
ftpcommandd = DetectFtpdataParse("appe");
224+
FAIL_IF_NULL(ftpcommandd);
225+
FAIL_IF(!(ftpcommandd->command == FTP_COMMAND_APPE));
226+
DetectFtpdataFree(NULL, ftpcommandd);
227+
221228
ftpcommandd = DetectFtpdataParse("list");
222229
FAIL_IF_NULL(ftpcommandd);
223230
FAIL_IF(!(ftpcommandd->command == FTP_COMMAND_LIST));
@@ -240,13 +247,16 @@ static int DetectFtpdataSignatureTest01(void)
240247
sig = DetectEngineAppendSig(de_ctx, "alert ip any any -> any any (ftpdata_command:retr; sid:2; rev:1;)");
241248
FAIL_IF_NULL(sig);
242249
sig = DetectEngineAppendSig(
243-
de_ctx, "alert ip any any -> any any (ftpdata_command:list; sid:3; rev:1;)");
250+
de_ctx, "alert ip any any -> any any (ftpdata_command:appe; sid:3; rev:1;)");
251+
FAIL_IF_NULL(sig);
252+
sig = DetectEngineAppendSig(
253+
de_ctx, "alert ip any any -> any any (ftpdata_command:list; sid:4; rev:1;)");
244254
FAIL_IF_NULL(sig);
245255
sig = DetectEngineAppendSig(
246-
de_ctx, "alert ip any any -> any any (ftpdata_command:mlsd; sid:4; rev:1;)");
256+
de_ctx, "alert ip any any -> any any (ftpdata_command:mlsd; sid:5; rev:1;)");
247257
FAIL_IF_NULL(sig);
248258
sig = DetectEngineAppendSig(
249-
de_ctx, "alert ip any any -> any any (ftpdata_command:xxx; sid:5; rev:1;)");
259+
de_ctx, "alert ip any any -> any any (ftpdata_command:xxx; sid:6; rev:1;)");
250260
FAIL_IF_NOT_NULL(sig);
251261

252262
DetectEngineCtxFree(de_ctx);

0 commit comments

Comments
 (0)