]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
decode: Improved FTP active mode handling
authorJeff Lucovsky <jeff@lucovsky.org>
Sat, 16 Mar 2019 13:54:01 +0000 (06:54 -0700)
committerVictor Julien <victor@inliniac.net>
Fri, 19 Apr 2019 05:32:31 +0000 (07:32 +0200)
This changeset addresses 2 issues:
- 2459
- 2527
and improves handling for FTP active mode over IPv4 and IPv6.

Active mode is triggered when the FTP client conveys the port
that should be used for a data connection (PORT, EPRT).

When this occurs, the FTP state is marked as "active".

src/app-layer-ftp.c
src/app-layer-ftp.h

index c36f561a772d4f1c7607cb27c2d12a4ffe5071a1..782a9c6ec05721cbf14dcbbe64bb30ec04c7e804 100644 (file)
@@ -330,6 +330,10 @@ static int FTPParseRequestCommand(void *ftp_state, uint8_t *input,
         fstate->command = FTP_COMMAND_PORT;
     }
 
+    if (input_len >= 4 && SCMemcmpLowercase("eprt", input, 4) == 0) {
+        fstate->command = FTP_COMMAND_EPRT;
+    }
+
     if (input_len >= 8 && SCMemcmpLowercase("auth tls", input, 8) == 0) {
         fstate->command = FTP_COMMAND_AUTH_TLS;
     }
@@ -374,6 +378,65 @@ static void FtpTransferCmdFree(void *data)
     FTPFree(cmd, sizeof(struct FtpTransferCmd));
 }
 
+static uint16_t ftp_validate_port(int computed_port_value)
+{
+    unsigned int port_val = computed_port_value;
+
+    if (port_val && port_val > UINT16_MAX)
+        return 0;
+
+    return ((uint16_t) (port_val));
+}
+
+/**
+ * \brief This function extracts a port number from the command input line for IPv6 FTP usage
+ * \param input input line of the command
+ * \param input_len length of the request
+ *
+ * \retval 0 if a port number could not be extracted; otherwise, the dynamic port number
+ */
+static uint16_t FTPGetV6PortNumber(uint8_t *input, uint32_t input_len)
+{
+    uint8_t *ptr = memrchr(input, '|', input_len);
+    if (ptr == NULL) {
+        return 0;
+    }
+
+    int n_length = ptr - input - 1;
+    if (n_length < 4)
+        return 0;
+
+    ptr = memrchr(input, '|', n_length);
+    if (ptr == NULL)
+        return 0;
+
+    return ftp_validate_port(atoi((char *)ptr + 1));
+}
+
+/**
+ * \brief This function extracts a port number from the command input line for IPv4 FTP usage
+ * \param input input line of the command
+ * \param input_len length of the request
+ *
+ * \retval 0 if a port number could not be extracted; otherwise, the dynamic port number
+ */
+static uint16_t FTPGetV4PortNumber(uint8_t *input, uint32_t input_len)
+{
+    uint16_t part1, part2;
+    uint8_t *ptr = memrchr(input, ',', input_len);
+    if (ptr == NULL)
+        return 0;
+
+    part2 = atoi((char *)ptr + 1);
+    ptr = memrchr(input, ',', (ptr - input) - 1);
+    if (ptr == NULL)
+        return 0;
+    part1 = atoi((char *)ptr + 1);
+
+    return ftp_validate_port(256 * part1 + part2);
+}
+
+
 /**
  * \brief This function is called to retrieve a ftp request
  * \param ftp_state the ftp state structure for the parser
@@ -410,19 +473,23 @@ static int FTPParseRequest(Flow *f, void *ftp_state,
         FTPParseRequestCommand(state,
                                state->current_line, state->current_line_len);
         switch (state->command) {
+            case FTP_COMMAND_EPRT:
+                // fallthrough
             case FTP_COMMAND_PORT:
                 if (state->current_line_len > state->port_line_size) {
+                    /* Allocate an extra byte for a NULL terminator */
                     ptmp = FTPRealloc(state->port_line, state->port_line_size,
-                                      state->current_line_len);
+                                      state->current_line_len + 1);
                     if (ptmp == NULL) {
-                        FTPFree(state->port_line, state->port_line_size);
-                        state->port_line = NULL;
-                        state->port_line_size = 0;
+                        if (state->port_line) {
+                            FTPFree(state->port_line, state->port_line_size);
+                            state->port_line = NULL;
+                            state->port_line_size = 0;
+                        }
                         return 0;
                     }
                     state->port_line = ptmp;
-
-                    state->port_line_size = state->current_line_len;
+                    state->port_line_size = state->current_line_len + 1;
                 }
                 memcpy(state->port_line, state->current_line,
                         state->current_line_len);
@@ -455,18 +522,23 @@ static int FTPParseRequest(Flow *f, void *ftp_state,
                     memcpy(data->file_name, state->current_line + 5, state->current_line_len - 5);
                     data->cmd = state->command;
                     data->flow_id = FlowGetId(f);
-                    int ret = AppLayerExpectationCreate(f, direction, 0,
-                                                         state->dyn_port,
-                                                         ALPROTO_FTPDATA, data);
+                    int ret = AppLayerExpectationCreate(f,
+                                            state->active ? STREAM_TOSERVER : direction,
+                                            0, state->dyn_port, ALPROTO_FTPDATA, data);
                     if (ret == -1) {
                         FTPFree(data, sizeof(struct FtpTransferCmd));
                         SCLogDebug("No expectation created.");
                         SCReturnInt(-1);
                     } else {
-                        SCLogDebug("Expectation created.");
+                        SCLogDebug("Expectation created [direction: %s, dynamic port %"PRIu16"].",
+                            state->active ? "to server" : "to client",
+                            state->dyn_port);
                     }
+
                     /* reset the dyn port to avoid duplicate */
                     state->dyn_port = 0;
+                    /* reset active/passive indicator */
+                    state->active = false;
                 }
                 break;
             default:
@@ -479,27 +551,17 @@ static int FTPParseRequest(Flow *f, void *ftp_state,
 
 static int FTPParsePassiveResponse(Flow *f, FtpState *state, uint8_t *input, uint32_t input_len)
 {
-    uint16_t dyn_port;
-
+    uint16_t dyn_port =
 #ifdef HAVE_RUST
-    dyn_port = rs_ftp_pasv_response(input, input_len);
+            rs_ftp_pasv_response(input, input_len);
+#else
+            FTPGetV4PortNumber(input, input_len);
+#endif
     if (dyn_port == 0) {
         return -1;
     }
-#else
-    uint16_t part1, part2;
-    uint8_t *ptr = memrchr(input, ',', input_len);
-    if (ptr == NULL)
-        return -1;
-
-    part2 = atoi((char *)ptr + 1);
-    ptr = memrchr(input, ',', (ptr - input) - 1);
-    if (ptr == NULL)
-        return -1;
-    part1 = atoi((char *)ptr + 1);
-
-    dyn_port = 256 * part1 + part2;
-#endif
+    SCLogDebug("FTP passive mode (v4): dynamic port %"PRIu16"", dyn_port);
+    state->active = false;
     state->dyn_port = dyn_port;
 
     return 0;
@@ -507,27 +569,18 @@ static int FTPParsePassiveResponse(Flow *f, FtpState *state, uint8_t *input, uin
 
 static int FTPParsePassiveResponseV6(Flow *f, FtpState *state, uint8_t *input, uint32_t input_len)
 {
+    uint16_t dyn_port =
 #ifdef HAVE_RUST
-    uint16_t dyn_port = rs_ftp_epsv_response(input, input_len);
+            rs_ftp_epsv_response(input, input_len);
+#else
+            FTPGetV6PortNumber(input, input_len);
+#endif
     if (dyn_port == 0) {
         return -1;
     }
-
+    SCLogDebug("FTP passive mode (v6): dynamic port %"PRIu16"", dyn_port);
+    state->active = false;
     state->dyn_port = dyn_port;
-#else
-    uint8_t *ptr = memrchr(input, '|', input_len);
-    if (ptr == NULL) {
-        return -1;
-    } else {
-        int n_length =  ptr - input - 1;
-        if (n_length < 4)
-            return -1;
-        ptr = memrchr(input, '|', n_length);
-        if (ptr == NULL)
-            return -1;
-    }
-    state->dyn_port = atoi((char *)ptr + 1);
-#endif
     return 0;
 }
 
@@ -552,6 +605,27 @@ static int FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstat
         }
     }
 
+    if (state->command == FTP_COMMAND_EPRT) {
+        uint16_t dyn_port = FTPGetV6PortNumber(state->port_line, state->port_line_len);
+        if (dyn_port == 0) {
+            return 0;
+        }
+        state->dyn_port = dyn_port;
+        state->active = true;
+        SCLogDebug("FTP active mode (v6): dynamic port %"PRIu16"", dyn_port);
+    }
+
+    if (state->command == FTP_COMMAND_PORT) {
+        if ((flags & STREAM_TOCLIENT)) {
+            uint16_t dyn_port = FTPGetV4PortNumber(state->port_line, state->port_line_len);
+            if (dyn_port == 0) {
+                return 0;
+            }
+            state->dyn_port = dyn_port;
+            state->active = true;
+            SCLogDebug("FTP active mode (v4): dynamic port %"PRIu16"", dyn_port);
+        }
+    }
     if (state->command == FTP_COMMAND_PASV) {
         if (input_len >= 4 && SCMemcmp("227 ", input, 4) == 0) {
             FTPParsePassiveResponse(f, ftp_state, input, input_len);
index 84373a8662b108879f0cb24506c91f2834a4a034..144937065cf83f5b572dad3a9554d8d52e11669e 100644 (file)
@@ -78,7 +78,8 @@ typedef enum {
     FTP_COMMAND_SYST,
     FTP_COMMAND_TYPE,
     FTP_COMMAND_UMASK,
-    FTP_COMMAND_USER
+    FTP_COMMAND_USER,
+    FTP_COMMAND_EPRT
     /** \todo more if missing.. */
 } FtpRequestCommand;
 typedef uint32_t FtpRequestCommandArgOfs;
@@ -115,6 +116,7 @@ typedef struct FtpState_ {
     uint8_t *input;
     int32_t input_len;
     uint8_t direction;
+    bool active;
 
     /* --parser details-- */
     /** current line extracted by the parser from the call to FTPGetline() */