diff options
author | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:21:41 -0700 |
---|---|---|
committer | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:21:41 -0700 |
commit | 8879b125d26e8db1a5633de5a9c692eb2d1c4f83 (patch) | |
tree | c7259d85a991b83dfa85ab2e339360669fc1f58e /framework/src/suricata/src/app-layer-ftp.c | |
parent | 13d05bc8458758ee39cb829098241e89616717ee (diff) |
suricata checkin based on commit id a4bce14770beee46a537eda3c3f6e8e8565d5d0a
Change-Id: I9a214fa0ee95e58fc640e50bd604dac7f42db48f
Diffstat (limited to 'framework/src/suricata/src/app-layer-ftp.c')
-rw-r--r-- | framework/src/suricata/src/app-layer-ftp.c | 681 |
1 files changed, 681 insertions, 0 deletions
diff --git a/framework/src/suricata/src/app-layer-ftp.c b/framework/src/suricata/src/app-layer-ftp.c new file mode 100644 index 00000000..b5d4a03d --- /dev/null +++ b/framework/src/suricata/src/app-layer-ftp.c @@ -0,0 +1,681 @@ +/* Copyright (C) 2007-2013 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Pablo Rincon Crespo <pablo.rincon.crespo@gmail.com> + * + * App Layer Parser for FTP + */ + +#include "suricata-common.h" +#include "debug.h" +#include "decode.h" +#include "threads.h" + +#include "util-print.h" +#include "util-pool.h" + +#include "flow-util.h" + +#include "detect-engine-state.h" + +#include "stream-tcp-private.h" +#include "stream-tcp-reassemble.h" +#include "stream-tcp.h" +#include "stream.h" + +#include "app-layer.h" +#include "app-layer-protos.h" +#include "app-layer-parser.h" +#include "app-layer-ftp.h" + +#include "util-spm.h" +#include "util-unittest.h" +#include "util-debug.h" +#include "util-memcmp.h" + +static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state) +{ + void *ptmp; + if (line_state->current_line_lf_seen == 1) { + /* we have seen the lf for the previous line. Clear the parser + * details to parse new line */ + line_state->current_line_lf_seen = 0; + if (line_state->current_line_db == 1) { + line_state->current_line_db = 0; + SCFree(line_state->db); + line_state->db = NULL; + line_state->db_len = 0; + state->current_line = NULL; + state->current_line_len = 0; + } + } + + uint8_t *lf_idx = memchr(state->input, 0x0a, state->input_len); + + if (lf_idx == NULL) { + /* fragmented lines. Decoder event for special cases. Not all + * fragmented lines should be treated as a possible evasion + * attempt. With multi payload ftp chunks we can have valid + * cases of fragmentation. But within the same segment chunk + * if we see fragmentation then it's definitely something you + * should alert about */ + if (line_state->current_line_db == 0) { + line_state->db = SCMalloc(state->input_len); + if (line_state->db == NULL) { + return -1; + } + line_state->current_line_db = 1; + memcpy(line_state->db, state->input, state->input_len); + line_state->db_len = state->input_len; + } else { + ptmp = SCRealloc(line_state->db, + (line_state->db_len + state->input_len)); + if (ptmp == NULL) { + SCFree(line_state->db); + line_state->db = NULL; + line_state->db_len = 0; + return -1; + } + line_state->db = ptmp; + + memcpy(line_state->db + line_state->db_len, + state->input, state->input_len); + line_state->db_len += state->input_len; + } + state->input += state->input_len; + state->input_len = 0; + + return -1; + + } else { + line_state->current_line_lf_seen = 1; + + if (line_state->current_line_db == 1) { + ptmp = SCRealloc(line_state->db, + (line_state->db_len + (lf_idx + 1 - state->input))); + if (ptmp == NULL) { + SCFree(line_state->db); + line_state->db = NULL; + line_state->db_len = 0; + return -1; + } + line_state->db = ptmp; + + memcpy(line_state->db + line_state->db_len, + state->input, (lf_idx + 1 - state->input)); + line_state->db_len += (lf_idx + 1 - state->input); + + if (line_state->db_len > 1 && + line_state->db[line_state->db_len - 2] == 0x0D) { + line_state->db_len -= 2; + state->current_line_delimiter_len = 2; + } else { + line_state->db_len -= 1; + state->current_line_delimiter_len = 1; + } + + state->current_line = line_state->db; + state->current_line_len = line_state->db_len; + + } else { + state->current_line = state->input; + state->current_line_len = lf_idx - state->input; + + if (state->input != lf_idx && + *(lf_idx - 1) == 0x0D) { + state->current_line_len--; + state->current_line_delimiter_len = 2; + } else { + state->current_line_delimiter_len = 1; + } + } + + state->input_len -= (lf_idx - state->input) + 1; + state->input = (lf_idx + 1); + + return 0; + } + +} + +static int FTPGetLine(FtpState *state) +{ + SCEnter(); + + /* we have run out of input */ + if (state->input_len <= 0) + return -1; + + /* toserver */ + if (state->direction == 0) + return FTPGetLineForDirection(state, &state->line_state[0]); + else + return FTPGetLineForDirection(state, &state->line_state[1]); +} + +/** + * \brief This function is called to determine and set which command is being + * transfered to the ftp server + * \param ftp_state the ftp state structure for the parser + * \param input input line of the command + * \param len of the command + * + * \retval 1 when the command is parsed, 0 otherwise + */ +static int FTPParseRequestCommand(void *ftp_state, uint8_t *input, + uint32_t input_len) +{ + SCEnter(); + FtpState *fstate = (FtpState *)ftp_state; + fstate->command = FTP_COMMAND_UNKNOWN; + + if (input_len >= 4) { + if (SCMemcmpLowercase("port", input, 4) == 0) { + fstate->command = FTP_COMMAND_PORT; + } + + /* else { + * Add the ftp commands you need here + * } + */ + } + return 1; +} + +/** + * \brief This function is called to retrieve a ftp request + * \param ftp_state the ftp state structure for the parser + * \param input input line of the command + * \param input_len length of the request + * \param output the resulting output + * + * \retval 1 when the command is parsed, 0 otherwise + */ +static int FTPParseRequest(Flow *f, void *ftp_state, + AppLayerParserState *pstate, + uint8_t *input, uint32_t input_len, + void *local_data) +{ + SCEnter(); + /* PrintRawDataFp(stdout, input,input_len); */ + + FtpState *state = (FtpState *)ftp_state; + void *ptmp; + + if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) { + SCReturnInt(1); + } else if (input == NULL || input_len == 0) { + SCReturnInt(-1); + } + + state->input = input; + state->input_len = input_len; + /* toserver stream */ + state->direction = 0; + + while (FTPGetLine(state) >= 0) { + FTPParseRequestCommand(state, + state->current_line, state->current_line_len); + if (state->command == FTP_COMMAND_PORT) { + if (state->current_line_len > state->port_line_size) { + ptmp = SCRealloc(state->port_line, state->current_line_len); + if (ptmp == NULL) { + SCFree(state->port_line); + state->port_line = NULL; + state->port_line_size = 0; + return 0; + } + state->port_line = ptmp; + + state->port_line_size = state->current_line_len; + } + memcpy(state->port_line, state->current_line, + state->current_line_len); + state->port_line_len = state->current_line_len; + } + } + + return 1; +} + +/** + * \brief This function is called to retrieve a ftp response + * \param ftp_state the ftp state structure for the parser + * \param input input line of the command + * \param input_len length of the request + * \param output the resulting output + * + * \retval 1 when the command is parsed, 0 otherwise + */ +static int FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate, + uint8_t *input, uint32_t input_len, + void *local_data) +{ + return 1; +} + +#ifdef DEBUG +static SCMutex ftp_state_mem_lock = SCMUTEX_INITIALIZER; +static uint64_t ftp_state_memuse = 0; +static uint64_t ftp_state_memcnt = 0; +#endif + +static void *FTPStateAlloc(void) +{ + void *s = SCMalloc(sizeof(FtpState)); + if (unlikely(s == NULL)) + return NULL; + + memset(s, 0, sizeof(FtpState)); + +#ifdef DEBUG + SCMutexLock(&ftp_state_mem_lock); + ftp_state_memcnt++; + ftp_state_memuse+=sizeof(FtpState); + SCMutexUnlock(&ftp_state_mem_lock); +#endif + return s; +} + +static void FTPStateFree(void *s) +{ + FtpState *fstate = (FtpState *) s; + if (fstate->port_line != NULL) + SCFree(fstate->port_line); + if (fstate->line_state[0].db) + SCFree(fstate->line_state[0].db); + if (fstate->line_state[1].db) + SCFree(fstate->line_state[1].db); + SCFree(s); +#ifdef DEBUG + SCMutexLock(&ftp_state_mem_lock); + ftp_state_memcnt--; + ftp_state_memuse-=sizeof(FtpState); + SCMutexUnlock(&ftp_state_mem_lock); +#endif +} + +static int FTPRegisterPatternsForProtocolDetection(void) +{ + if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, + "USER ", 5, 0, STREAM_TOSERVER) < 0) + { + return -1; + } + if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, + "PASS ", 5, 0, STREAM_TOSERVER) < 0) + { + return -1; + } + if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, + "PORT ", 5, 0, STREAM_TOSERVER) < 0) + { + return -1; + } + + return 0; +} + +void RegisterFTPParsers(void) +{ + char *proto_name = "ftp"; + + /** FTP */ + if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) { + AppLayerProtoDetectRegisterProtocol(ALPROTO_FTP, proto_name); + if (FTPRegisterPatternsForProtocolDetection() < 0 ) + return; + } + + if (AppLayerParserConfParserEnabled("tcp", proto_name)) { + AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER, + FTPParseRequest); + AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOCLIENT, + FTPParseResponse); + AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTP, FTPStateAlloc, FTPStateFree); + AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER | STREAM_TOCLIENT); + } else { + SCLogInfo("Parsed disabled for %s protocol. Protocol detection" + "still on.", proto_name); + } +#ifdef UNITTESTS + AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_FTP, FTPParserRegisterTests); +#endif +} + +void FTPAtExitPrintStats(void) +{ +#ifdef DEBUG + SCMutexLock(&ftp_state_mem_lock); + SCLogDebug("ftp_state_memcnt %"PRIu64", ftp_state_memuse %"PRIu64"", + ftp_state_memcnt, ftp_state_memuse); + SCMutexUnlock(&ftp_state_mem_lock); +#endif +} + +/* UNITTESTS */ +#ifdef UNITTESTS + +/** \test Send a get request in one chunk. */ +int FTPParserTest01(void) +{ + int result = 1; + Flow f; + uint8_t ftpbuf[] = "PORT 192,168,1,1,0,80\r\n"; + uint32_t ftplen = sizeof(ftpbuf) - 1; /* minus the \0 */ + TcpSession ssn; + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + FLOW_INITIALIZE(&f); + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + + StreamTcpInitConfig(TRUE); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_EOF, ftpbuf, ftplen); + if (r != 0) { + SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + result = 0; + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + FtpState *ftp_state = f.alstate; + if (ftp_state == NULL) { + SCLogDebug("no ftp state: "); + result = 0; + goto end; + } + + if (ftp_state->command != FTP_COMMAND_PORT) { + SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command); + result = 0; + goto end; + } + +end: + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + return result; +} + +/** \test Send a splitted get request. */ +int FTPParserTest03(void) +{ + int result = 1; + Flow f; + uint8_t ftpbuf1[] = "POR"; + uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ + uint8_t ftpbuf2[] = "T 192,168,1"; + uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */ + uint8_t ftpbuf3[] = "1,1,10,20\r\n"; + uint32_t ftplen3 = sizeof(ftpbuf3) - 1; /* minus the \0 */ + TcpSession ssn; + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + + StreamTcpInitConfig(TRUE); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_START, ftpbuf1, ftplen1); + if (r != 0) { + SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + result = 0; + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + SCMutexLock(&f.m); + r = AppLayerParserParse(alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER, ftpbuf2, ftplen2); + if (r != 0) { + SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + result = 0; + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + SCMutexLock(&f.m); + r = AppLayerParserParse(alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_EOF, ftpbuf3, ftplen3); + if (r != 0) { + SCLogDebug("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); + result = 0; + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + FtpState *ftp_state = f.alstate; + if (ftp_state == NULL) { + SCLogDebug("no ftp state: "); + result = 0; + goto end; + } + + if (ftp_state->command != FTP_COMMAND_PORT) { + SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command); + result = 0; + goto end; + } + +end: + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + return result; +} + +/** \test See how it deals with an incomplete request. */ +int FTPParserTest06(void) +{ + int result = 1; + Flow f; + uint8_t ftpbuf1[] = "PORT"; + uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ + TcpSession ssn; + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + FLOW_INITIALIZE(&f); + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + + StreamTcpInitConfig(TRUE); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_START|STREAM_EOF, ftpbuf1, ftplen1); + if (r != 0) { + SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + result = 0; + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + FtpState *ftp_state = f.alstate; + if (ftp_state == NULL) { + SCLogDebug("no ftp state: "); + result = 0; + goto end; + } + + if (ftp_state->command != FTP_COMMAND_UNKNOWN) { + SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_UNKNOWN, ftp_state->command); + result = 0; + goto end; + } + +end: + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + return result; +} + +/** \test See how it deals with an incomplete request in multiple chunks. */ +int FTPParserTest07(void) +{ + int result = 1; + Flow f; + uint8_t ftpbuf1[] = "PO"; + uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ + uint8_t ftpbuf2[] = "RT\r\n"; + uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */ + TcpSession ssn; + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + FLOW_INITIALIZE(&f); + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + + StreamTcpInitConfig(TRUE); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_START, ftpbuf1, ftplen1); + if (r != 0) { + SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + result = 0; + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + SCMutexLock(&f.m); + r = AppLayerParserParse(alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_EOF, ftpbuf2, ftplen2); + if (r != 0) { + SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + result = 0; + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + FtpState *ftp_state = f.alstate; + if (ftp_state == NULL) { + SCLogDebug("no ftp state: "); + result = 0; + goto end; + } + + if (ftp_state->command != FTP_COMMAND_PORT) { + SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", + FTP_COMMAND_PORT, ftp_state->command); + result = 0; + goto end; + } + +end: + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + return result; +} + +/** \test Test case where chunks are smaller than the delim length and the + * last chunk is supposed to match the delim. */ +int FTPParserTest10(void) +{ + int result = 1; + Flow f; + uint8_t ftpbuf1[] = "PORT 1,2,3,4,5,6\r\n"; + uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ + TcpSession ssn; + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + int r = 0; + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + FLOW_INITIALIZE(&f); + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + + StreamTcpInitConfig(TRUE); + + uint32_t u; + for (u = 0; u < ftplen1; u++) { + uint8_t flags = 0; + + if (u == 0) flags = STREAM_TOSERVER|STREAM_START; + else if (u == (ftplen1 - 1)) flags = STREAM_TOSERVER|STREAM_EOF; + else flags = STREAM_TOSERVER; + + SCMutexLock(&f.m); + r = AppLayerParserParse(alp_tctx, &f, ALPROTO_FTP, flags, &ftpbuf1[u], 1); + if (r != 0) { + SCLogDebug("toserver chunk %" PRIu32 " returned %" PRId32 ", expected 0: ", u, r); + result = 0; + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + } + + FtpState *ftp_state = f.alstate; + if (ftp_state == NULL) { + SCLogDebug("no ftp state: "); + result = 0; + goto end; + } + + if (ftp_state->command != FTP_COMMAND_PORT) { + SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command); + result = 0; + goto end; + } + +end: + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + return result; +} +#endif /* UNITTESTS */ + +void FTPParserRegisterTests(void) +{ +#ifdef UNITTESTS + UtRegisterTest("FTPParserTest01", FTPParserTest01, 1); + UtRegisterTest("FTPParserTest03", FTPParserTest03, 1); + UtRegisterTest("FTPParserTest06", FTPParserTest06, 1); + UtRegisterTest("FTPParserTest07", FTPParserTest07, 1); + UtRegisterTest("FTPParserTest10", FTPParserTest10, 1); +#endif /* UNITTESTS */ +} + |