Преглед на файлове

RFC compliant URI processing of URIs with dots

Replace "remove_rouble_dots_and_double_slashes" by RFC compliant algorithm
"remove_dot_segments". See RFC 3986, Section 5.2.4 and #816
bel2125 преди 5 години
родител
ревизия
fa3f169ff3
променени са 1 файла, в които са добавени 109 реда и са изтрити 23 реда
  1. 109 23
      src/civetweb.c

+ 109 - 23
src/civetweb.c

@@ -7836,33 +7836,119 @@ parse_date_string(const char *datetime)
 #endif /* !NO_CACHING */
 
 
-/* Protect against directory disclosure attack by removing '..',
- * excessive '/' and '\' characters */
+/* Pre-process URIs according to RFC + protect against directory disclosure attacks
+ * by removing '..', excessive '/' and '\' characters */
 static void
-remove_double_dots_and_double_slashes(char *s)
-{
-	char *p = s;
-
-	while ((s[0] == '.') && (s[1] == '.')) {
-		s++;
-	}
+remove_dot_segments(char *inout)
+{
+	/* Windows backend protection (https://tools.ietf.org/html/rfc3986#section-7.3):
+	 * Replace backslash in URI by slash */
+	char *in_copy = mg_strdup(inout);
+	char *out_begin = inout;
+	char *out_end = inout;
+	char *in = in_copy;
+	
+	while (*in) {
+		if (*in == '\\') {
+			*in = '/';
+		}
+		in++;
+	}
+
+	/* Algorithm "remove_dot_segments" from https://tools.ietf.org/html/rfc3986#section-5.2.4 */
+	/* Step 1:
+	 * The input buffer is initialized.
+	 * The output buffer is initialized to the empty string.
+	 */
+	in = in_copy;
 
-	while (*s != '\0') {
-		*p++ = *s++;
-		if ((s[-1] == '/') || (s[-1] == '\\')) {
-			/* Skip all following slashes, backslashes and double-dots */
-			while (s[0] != '\0') {
-				if ((s[0] == '/') || (s[0] == '\\')) {
-					s++;
-				} else if ((s[0] == '.') && (s[1] == '.')) {
-					s += 2;
-				} else {
-					break;
-				}
+	/* Step 2:
+	 * While the input buffer is not empty, loop as follows:
+	 */
+	while (*in) {
+		/* Step 2a:
+		 * If the input buffer begins with a prefix of "../" or "./",
+		 * then remove that prefix from the input buffer;
+		 */
+		if (!strncmp(in, "../", 3)) {
+			in += 3;
+		} 
+		else if (!strncmp(in, "./", 2)) {
+			in += 2;
+		} 
+		/* otherwise */
+		/* Step 2b:
+		 * if the input buffer begins with a prefix of "/./" or "/.",
+		 * where "." is a complete path segment, then replace that
+		 * prefix with "/" in the input buffer;
+		 */
+		else if (!strncmp(in, "/./", 3)) {
+			in += 2;
+		}
+		else if (!strcmp(in, "/.")) {
+			in[1] = 0;
+		}
+		/* otherwise */
+		/* Step 2c:
+		 * if the input buffer begins with a prefix of "/../" or "/..",
+		 * where ".." is a complete path segment, then replace that
+		 * prefix with "/" in the input buffer and remove the last
+		 * segment and its preceding "/" (if any) from the output
+		 * buffer;
+		 */
+		else if (!strncmp(in, "/../", 4)) {
+			in += 3;
+			if (out_begin != out_end) {
+				/* remove last segment */
+				do {
+					out_end--;
+					*out_end = 0;
+				} while ((out_begin != out_end) && (*out_end != '/'));
+			}
+		}
+		else if (!strcmp(in, "/..")) {
+			in[1] = 0;
+			if (out_begin != out_end) {
+				/* remove last segment */
+				do {
+					out_end--;
+					*out_end = 0;
+				} while ((out_begin != out_end) && (*out_end != '/'));
 			}
 		}
+		/* otherwise */
+		/* Step 2d:
+		 * if the input buffer consists only of "." or "..", then remove
+		 * that from the input buffer;
+		 */
+		else if (!strcmp(in, ".") || !strcmp(in, "..")) {
+			*in = 0;
+		}
+		/* otherwise */
+		/* Step 2d:
+		 * move the first path segment in the input buffer to the end of
+		 * the output buffer, including the initial "/" character (if
+		 * any) and any subsequent characters up to, but not including,
+		 * the next "/" character or the end of the input buffer.
+		 */
+		else {
+			do {
+				*out_end = *in;
+				out_end++;
+				in++;
+			} while ((*in != 0) && (*in != '/'));
+		}
 	}
-	*p = '\0';
+
+	/* Step 3:
+	 * Finally, the output buffer is returned as the result of
+	 * remove_dot_segments.
+	 */
+	/* Terminate output */
+	*out_end = 0;
+
+	/* Free temporary copies */
+	mg_free(in_copy);
 }
 
 
@@ -13814,7 +13900,7 @@ handle_request(struct mg_connection *conn)
 
 	/* 1.4. clean URIs, so a path like allowed_dir/../forbidden_file is
 	 * not possible */
-	remove_double_dots_and_double_slashes((char *)ri->local_uri);
+	remove_dot_segments((char *)ri->local_uri);
 
 	/* step 1. completed, the url is known now */
 	uri_len = (int)strlen(ri->local_uri);