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);