components/proftpd/patches/013-21514375-4143-tls-xss.patch
author Tomas Klacko <tomas.klacko@oracle.com>
Fri, 09 Oct 2015 03:15:31 -0700
branchs11u3-sru
changeset 4930 b6f4cd2a91cf
permissions -rw-r--r--
21514375 problem in SERVICE/FTP-SERVER

http://bugs.proftpd.org/show_bug.cgi?id=4143#c0
https://github.com/proftpd/proftpd/pull/81.patch

diff --git a/include/cmd.h b/include/cmd.h
index a95cac3..814dc62 100644
--- a/include/cmd.h
+++ b/include/cmd.h
@@ -106,6 +106,16 @@ int pr_cmd_get_id(const char *name_name);
 #define PR_CMD_MIN_NAMELEN	3
 #define PR_CMD_MAX_NAMELEN	4
 
+/* Returns TRUE if the given command is a known HTTP method, FALSE if not
+ * a known HTTP method, and -1 if there is an error.
+ */
+int pr_cmd_is_http(cmd_rec *c);
+
+/* Returns TRUE if the given command is a known SMTP method, FALSE if not
+ * a known SMTP method, and -1 if there is an error.
+ */
+int pr_cmd_is_smtp(cmd_rec *c);
+
 int pr_cmd_set_name(cmd_rec *, const char *);
 
 /* Implemented in main.c */
diff --git a/include/dirtree.h b/include/dirtree.h
index fe7b14b..ddb31a8 100644
--- a/include/dirtree.h
+++ b/include/dirtree.h
@@ -130,6 +130,13 @@ typedef struct cmd_struc {
   int error_code;               /* Stores errno of failed file transfer
                                  * commands. Required for Solaris auditing.
                                  */
+
+  /* If we detect that the client sent commands for a protocol OTHER than
+   * FTP, then this field will be FALSE; the protocol field will identify
+   * the detected protocol.
+   */
+  int is_ftp;
+  const char *protocol;
 } cmd_rec;
 
 struct config_struc {
diff --git a/include/session.h b/include/session.h
index a0ccd1a..d47ea83 100644
--- a/include/session.h
+++ b/include/session.h
@@ -72,6 +72,9 @@
 /* Disconnected due to snprintf(3) buffer truncation. */
 #define PR_SESS_DISCONNECT_SNPRINTF_TRUNCATED	13
 
+/* Disconnected due to wrong protocol used (e.g. HTTP/SMTP). */
+#define PR_SESS_DISCONNECT_BAD_PROTOCOL                14
+
 /* Returns a string describing the reason the client was disconnected or
  * the session ended.  If a pointer to a char * was provided, any extra
  * disconnect details will be provided.
diff --git a/src/cmd.c b/src/cmd.c
index b441c54..4688ff3 100644
--- a/src/cmd.c
+++ b/src/cmd.c
@@ -112,6 +112,38 @@ static struct cmd_entry cmd_ids[] = {
   { NULL,	0 }
 };
 
+/* Due to potential XSS issues (see Bug#4143), we want to explicitly
+ * check for commands from other text-based protocols (e.g. HTTP and SMTP);
+ * if we see these, we want to close the connection with extreme prejudice.
+ */
+
+static struct cmd_entry http_ids[] = {
+  { " ",       1 },    /* Index 0 is intentionally filled with a sentinel */
+  { "CONNECT", 7 },
+  { "DELETE",  6 },
+  { "GET",     3 },
+  { "HEAD",    4 },
+  { "OPTIONS", 7 },
+  { "PATCH",   5 },
+  { "POST",    4 },
+  { "PUT",     3 },
+
+  { NULL,      0 }
+};
+
+static struct cmd_entry smtp_ids[] = {
+  { " ",       1 },    /* Index 0 is intentionally filled with a sentinel */
+  { "DATA",    4 },
+  { "EHLO",    4 },
+  { "HELO",    4 },
+  { "MAIL",    4 },
+  { "RCPT",    4 },
+  { "RSET",    4 },
+  { "VRFY",    4 },
+
+  { NULL,      0 }
+};
+
 cmd_rec *pr_cmd_alloc(pool *p, int argc, ...) { 
   pool *newpool = NULL;
   cmd_rec *cmd = NULL;
@@ -340,3 +372,59 @@ int pr_cmd_get_id(const char *cmd_name) {
   errno = ENOENT;
   return -1;
 }
+
+static int is_known_cmd(struct cmd_entry *known_cmds, const char *cmd_name,
+    size_t cmd_namelen) {
+  register unsigned int i;
+  int known = FALSE;
+
+  for (i = 0; known_cmds[i].cmd_name != NULL; i++) {
+    if (cmd_namelen == known_cmds[i].cmd_namelen) {
+      if (strncmp(cmd_name, known_cmds[i].cmd_name, cmd_namelen + 1) == 0) {
+        known = TRUE;
+        break;
+      }
+    }
+  }
+
+  return known;
+}
+
+int pr_cmd_is_http(cmd_rec *cmd) {
+  const char *cmd_name;
+  size_t cmd_namelen;
+
+  if (cmd == NULL) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  cmd_name = cmd->argv[0];
+  if (cmd_name == NULL) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  cmd_namelen = strlen(cmd_name);
+  return is_known_cmd(http_ids, cmd_name, cmd_namelen);
+}
+
+int pr_cmd_is_smtp(cmd_rec *cmd) {
+  const char *cmd_name;
+  size_t cmd_namelen;
+
+  if (cmd == NULL) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  cmd_name = cmd->argv[0];
+  if (cmd_name == NULL) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  cmd_namelen = strlen(cmd_name);
+  return is_known_cmd(smtp_ids, cmd_name, cmd_namelen);
+}
+
diff --git a/src/main.c b/src/main.c
index b951436..b0a8a2a 100644
--- a/src/main.c
+++ b/src/main.c
@@ -572,7 +572,21 @@ int pr_cmd_read(cmd_rec **res) {
     cmd = make_ftp_cmd(session.pool, cp, flags);
     if (cmd) {
       *res = cmd;
-    } 
+
+      if (pr_cmd_is_http(cmd) == TRUE) {
+        cmd->is_ftp = FALSE;
+        cmd->protocol = "HTTP";
+
+      } else if (pr_cmd_is_smtp(cmd) == TRUE) {
+        cmd->is_ftp = FALSE;
+        cmd->protocol = "SMTP";
+
+      } else {
+        /* Assume that the client is sending valid FTP commands. */
+        cmd->is_ftp = TRUE;
+        cmd->protocol = "FTP";
+      }
+    }
   }
 
   return 0;
@@ -827,6 +841,20 @@ static void cmd_loop(server_rec *server, conn_t *c) {
     }
 
     if (cmd) {
+
+      /* Detect known commands for other protocols; if found, drop the
+       * connection, lest we be used as part of an attack on a different
+       * protocol server (Bug#4143).
+       */
+      if (cmd->is_ftp == FALSE) {
+        pr_log_pri(PR_LOG_WARNING,
+          "client sent %s command '%s', disconnecting", cmd->protocol,
+          cmd->argv[0]);
+        pr_event_generate("core.bad-protocol", cmd);
+        pr_session_disconnect(NULL, PR_SESS_DISCONNECT_BAD_PROTOCOL,
+          cmd->protocol);
+      }
+
       pr_cmd_dispatch(cmd);
       destroy_pool(cmd->pool);