Pārlūkot izejas kodu

Merge branch 'master' into fix/uri-len-dup

bel2125 11 stundas atpakaļ
vecāks
revīzija
48b58d1bfe
7 mainītis faili ar 115 papildinājumiem un 31 dzēšanām
  1. 1 1
      .github/workflows/cibuild.yml
  2. 1 1
      .github/workflows/codeql.yml
  3. 1 1
      README.md
  4. 70 16
      src/civetweb.c
  5. 38 8
      src/handle_form.inl
  6. 2 2
      test/page4.lp
  7. 2 2
      test/page4kepler.lp

+ 1 - 1
.github/workflows/cibuild.yml

@@ -392,7 +392,7 @@ jobs:
 
     steps:
         - name: Checkout code
-          uses: actions/checkout@v4.1.7
+          uses: actions/checkout@v5
 
         - name: Export number of CPUs
           run: |

+ 1 - 1
.github/workflows/codeql.yml

@@ -45,7 +45,7 @@ jobs:
 
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
       with:
         submodules: recursive
 

+ 1 - 1
README.md

@@ -182,7 +182,7 @@ CivetWeb is based on the [Mongoose project](https://github.com/cesanta/mongoose)
 Sergey Lyubka(2004-2013) who released it under the MIT license.
 However, on August 16, 2013,
 [Mongoose was relicensed to a dual GPL V2 + commercial license](https://groups.google.com/forum/#!topic/mongoose-users/aafbOnHonkI)
-and CiwetWeb was created by Thomas Davis (sunsetbrew) as "the MIT fork of mongoose".
+and CivetWeb was created by Thomas Davis (sunsetbrew) as "the MIT fork of mongoose".
 The license change and CivetWeb fork was mentioned on the Mongoose
 [Wikipedia](https://en.wikipedia.org/wiki/Mongoose_(web_server))
 page as well, but it's getting deleted (and added again) there every

+ 70 - 16
src/civetweb.c

@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2024 the Civetweb developers
+/* Copyright (c) 2013-2025 the Civetweb developers
  * Copyright (c) 2004-2013 Sergey Lyubka
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -2087,7 +2087,7 @@ enum {
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
 	LUA_WEBSOCKET_EXTENSIONS,
 #endif
-
+	REPLACE_ASTERISK_WITH_ORIGIN,
 	ACCESS_CONTROL_ALLOW_ORIGIN,
 	ACCESS_CONTROL_ALLOW_METHODS,
 	ACCESS_CONTROL_ALLOW_HEADERS,
@@ -2253,6 +2253,7 @@ static const struct mg_option config_options[] = {
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
     {"lua_websocket_pattern", MG_CONFIG_TYPE_EXT_PATTERN, "**.lua$"},
 #endif
+	{"replace_asterisk_with_origin", MG_CONFIG_TYPE_BOOLEAN, "no"},
     {"access_control_allow_origin", MG_CONFIG_TYPE_STRING, "*"},
     {"access_control_allow_methods", MG_CONFIG_TYPE_STRING, "*"},
     {"access_control_allow_headers", MG_CONFIG_TYPE_STRING, "*"},
@@ -4234,16 +4235,27 @@ send_cors_header(struct mg_connection *conn)
 	    conn->dom_ctx->config[ACCESS_CONTROL_EXPOSE_HEADERS];
 	const char *cors_meth_cfg =
 	    conn->dom_ctx->config[ACCESS_CONTROL_ALLOW_METHODS];
-
-	if (cors_orig_cfg && *cors_orig_cfg && origin_hdr && *origin_hdr) {
+	const char *cors_repl_asterisk_with_orig_cfg = 
+		conn->dom_ctx->config[REPLACE_ASTERISK_WITH_ORIGIN];
+		
+	if (cors_orig_cfg && *cors_orig_cfg && origin_hdr && *origin_hdr && cors_repl_asterisk_with_orig_cfg && *cors_repl_asterisk_with_orig_cfg) {
+		int cors_repl_asterisk_with_orig = mg_strcasecmp(cors_repl_asterisk_with_orig_cfg, "yes");
+		
 		/* Cross-origin resource sharing (CORS), see
 		 * http://www.html5rocks.com/en/tutorials/cors/,
 		 * http://www.html5rocks.com/static/images/cors_server_flowchart.png
 		 * CORS preflight is not supported for files. */
-		mg_response_header_add(conn,
+		if (cors_repl_asterisk_with_orig == 0 && cors_orig_cfg[0] == '*') {
+			mg_response_header_add(conn,
+		                       "Access-Control-Allow-Origin",
+		                       origin_hdr,
+		                       -1);
+		} else {
+			mg_response_header_add(conn,
 		                       "Access-Control-Allow-Origin",
 		                       cors_orig_cfg,
 		                       -1);
+		}
 	}
 
 	if (cors_cred_cfg && *cors_cred_cfg && origin_hdr && *origin_hdr) {
@@ -7272,6 +7284,7 @@ mg_url_decode(const char *src,
               int is_form_url_encoded)
 {
 	int i, j, a, b;
+
 #define HEXTOI(x) (isdigit(x) ? (x - '0') : (x - 'W'))
 
 	for (i = j = 0; (i < src_len) && (j < (dst_len - 1)); i++, j++) {
@@ -7284,11 +7297,15 @@ mg_url_decode(const char *src,
 			i += 2;
 		} else if (is_form_url_encoded && (src[i] == '+')) {
 			dst[j] = ' ';
+		} else if ((unsigned char)src[i] <= ' ') {
+			return -1; /* invalid character */
 		} else {
 			dst[j] = src[i];
 		}
 	}
 
+#undef HEXTOI
+
 	dst[j] = '\0'; /* Null-terminate the destination */
 
 	return (i >= src_len) ? j : -1;
@@ -10783,9 +10800,13 @@ is_not_modified(const struct mg_connection *conn,
 	const char *inm = mg_get_header(conn, "If-None-Match");
 	construct_etag(etag, sizeof(etag), filestat);
 
-	return ((inm != NULL) && !mg_strcasecmp(etag, inm))
-	       || ((ims != NULL)
-	           && (filestat->last_modified <= parse_date_string(ims)));
+	if (inm) {
+		return !mg_strcasecmp(etag, inm);
+	}
+	if (ims) {
+		return (filestat->last_modified <= parse_date_string(ims));
+	}
+	return 0;
 }
 
 
@@ -15165,13 +15186,18 @@ handle_request(struct mg_connection *conn)
 		const char *cors_acrm = get_header(ri->http_headers,
 		                                   ri->num_headers,
 		                                   "Access-Control-Request-Method");
-
+		const char *cors_repl_asterisk_with_orig_cfg = 
+			conn->dom_ctx->config[REPLACE_ASTERISK_WITH_ORIGIN];
+		
 		/* Todo: check if cors_origin is in cors_orig_cfg.
 		 * Or, let the client check this. */
 
 		if ((cors_meth_cfg != NULL) && (*cors_meth_cfg != 0)
 		    && (cors_orig_cfg != NULL) && (*cors_orig_cfg != 0)
-		    && (cors_origin != NULL) && (cors_acrm != NULL)) {
+		    && (cors_origin != NULL) && (cors_acrm != NULL)
+			&& (cors_repl_asterisk_with_orig_cfg != NULL) && (*cors_repl_asterisk_with_orig_cfg != 0)) {
+			int cors_repl_asterisk_with_orig = mg_strcasecmp(cors_repl_asterisk_with_orig_cfg, "yes");
+			
 			/* This is a valid CORS preflight, and the server is configured
 			 * to handle it automatically. */
 			const char *cors_acrh =
@@ -15192,7 +15218,7 @@ handle_request(struct mg_connection *conn)
 			          "Content-Length: 0\r\n"
 			          "Connection: %s\r\n",
 			          date,
-			          cors_orig_cfg,
+			          (cors_repl_asterisk_with_orig == 0 && cors_orig_cfg[0] == '*') ? cors_origin : cors_orig_cfg,
 			          ((cors_meth_cfg[0] == '*') ? cors_acrm : cors_meth_cfg),
 			          suggest_connection_header(conn));
 
@@ -15586,6 +15612,7 @@ handle_request(struct mg_connection *conn)
 	if (file.stat.is_directory && (uri_len > 0)
         && (ri->local_uri[uri_len - 1] != '/')) {
 
+
 		/* Path + server root */
 		size_t buflen = UTF8_PATH_MAX * 2 + 2;
 		char *new_path;
@@ -15598,12 +15625,26 @@ handle_request(struct mg_connection *conn)
 			mg_send_http_error(conn, 500, "out or memory");
 		} else {
 			mg_get_request_link(conn, new_path, buflen - 1);
-			strcat(new_path, "/");
+
+			size_t len = strlen(new_path);
+			if (len + 1 < buflen) {
+				new_path[len] = '/';
+				new_path[len + 1] = '\0';
+				len++;
+			}
+
 			if (ri->query_string) {
-				/* Append ? and query string */
-				strcat(new_path, "?");
-				strcat(new_path, ri->query_string);
+				if (len + 1 < buflen) {
+					new_path[len] = '?';
+					new_path[len + 1] = '\0';
+					len++;
+				}
+
+				/* Append with size of space left for query string + null terminator */
+				size_t max_append = buflen - len - 1;
+				strncat(new_path, ri->query_string, max_append);
 			}
+
 			mg_send_http_redirect(conn, new_path, 301);
 			mg_free(new_path);
 		}
@@ -18864,6 +18905,19 @@ get_uri_type(const char *uri)
 	 * and % encoded symbols.
 	 */
 	for (i = 0; uri[i] != 0; i++) {
+		/* Check for CRLF injection attempts */
+		if (uri[i] == '%') {
+			if (uri[i+1] == '0' && (uri[i+2] == 'd' || uri[i+2] == 'D')) {
+				/* Found %0d (CR) */
+				DEBUG_TRACE("CRLF injection attempt detected: %s\r\n", uri);
+				return 0;
+			}
+			if (uri[i+1] == '0' && (uri[i+2] == 'a' || uri[i+2] == 'A')) {
+				/* Found %0a (LF) */
+				DEBUG_TRACE("CRLF injection attempt detected: %s\r\n", uri);
+				return 0;
+			}
+		}
 		if ((unsigned char)uri[i] < 33) {
 			/* control characters and spaces are invalid */
 			return 0;
@@ -22026,7 +22080,7 @@ mg_start_domain2(struct mg_context *ctx,
 		}
 	}
 
-	new_dom->handlers = NULL;
+	new_dom->handlers = ctx->dd.handlers;
 	new_dom->next = NULL;
 	new_dom->nonce_count = 0;
 	new_dom->auth_nonce_mask = get_random() ^ (get_random() << 31);

+ 38 - 8
src/handle_form.inl

@@ -1,4 +1,4 @@
-/* Copyright (c) 2016-2021 the Civetweb developers
+/* Copyright (c) 2016-2025 the Civetweb developers
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -39,7 +39,7 @@ url_encoded_field_found(const struct mg_connection *conn,
 	    mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);
 
 	if (((size_t)key_dec_len >= (size_t)sizeof(key_dec)) || (key_dec_len < 0)) {
-		return MG_FORM_FIELD_STORAGE_SKIP;
+		return MG_FORM_FIELD_STORAGE_ABORT;
 	}
 
 	if (filename) {
@@ -53,7 +53,7 @@ url_encoded_field_found(const struct mg_connection *conn,
 		    || (filename_dec_len < 0)) {
 			/* Log error message and skip this field. */
 			mg_cry_internal(conn, "%s: Cannot decode filename", __func__);
-			return MG_FORM_FIELD_STORAGE_SKIP;
+			return MG_FORM_FIELD_STORAGE_ABORT;
 		}
 		remove_dot_segments(filename_dec);
 
@@ -95,6 +95,7 @@ url_encoded_field_get(
     struct mg_form_data_handler *fdh)
 {
 	char key_dec[1024];
+	int key_dec_len;
 
 	char *value_dec = (char *)mg_malloc_ctx(*value_len + 1, conn->phys_ctx);
 	int value_dec_len, ret;
@@ -108,7 +109,8 @@ url_encoded_field_get(
 		return MG_FORM_FIELD_STORAGE_ABORT;
 	}
 
-	mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);
+	key_dec_len = mg_url_decode(
+	    key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);
 
 	if (*value_len >= 2 && value[*value_len - 2] == '%')
 		*value_len -= 2;
@@ -117,6 +119,11 @@ url_encoded_field_get(
 	value_dec_len = mg_url_decode(
 	    value, (int)*value_len, value_dec, ((int)*value_len) + 1, 1);
 
+	if ((key_dec_len < 0) || (value_dec_len < 0)) {
+		mg_free(value_dec);
+		return MG_FORM_FIELD_STORAGE_ABORT;
+	}
+
 	ret = fdh->field_get(key_dec,
 	                     value_dec,
 	                     (size_t)value_dec_len,
@@ -136,9 +143,13 @@ unencoded_field_get(const struct mg_connection *conn,
                     struct mg_form_data_handler *fdh)
 {
 	char key_dec[1024];
+	int key_dec_len;
 	(void)conn;
 
-	mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);
+	key_dec_len = mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);
+	if (key_dec_len < 0) {
+		return MG_FORM_FIELD_STORAGE_ABORT;
+	}
 
 	return fdh->field_get(key_dec, value, value_len, fdh->user_data);
 }
@@ -191,6 +202,7 @@ mg_handle_form_request(struct mg_connection *conn,
 	size_t buf_fill = 0;
 	int r;
 	int field_count = 0;
+	int abort_read = 0;
 	struct mg_file fstore = STRUCT_FILE_INITIALIZER;
 	int64_t file_size = 0; /* init here, to a avoid a false positive
 	                         "uninitialized variable used" warning */
@@ -281,6 +293,7 @@ mg_handle_form_request(struct mg_connection *conn,
 				    conn, data, (size_t)keylen, val, (size_t *)&vallen, fdh);
 				if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 					/* Stop request handling */
+					abort_read = 1;
 					break;
 				}
 				if (r == MG_FORM_FIELD_HANDLE_NEXT) {
@@ -323,6 +336,7 @@ mg_handle_form_request(struct mg_connection *conn,
 							r = field_stored(conn, path, file_size, fdh);
 							if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 								/* Stop request handling */
+								abort_read = 1;
 								break;
 							}
 
@@ -361,6 +375,7 @@ mg_handle_form_request(struct mg_connection *conn,
 			if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT)
 			    == MG_FORM_FIELD_STORAGE_ABORT) {
 				/* Stop parsing the request */
+				abort_read = 1;
 				break;
 			}
 
@@ -389,7 +404,7 @@ mg_handle_form_request(struct mg_connection *conn,
 		 * Here we use "POST", and read the data from the request body.
 		 * The data read on the fly, so it is not required to buffer the
 		 * entire request in memory before processing it. */
-		for (;;) {
+		while (!abort_read) {
 			const char *val;
 			const char *next;
 			ptrdiff_t keylen, vallen;
@@ -443,6 +458,7 @@ mg_handle_form_request(struct mg_connection *conn,
 			if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT)
 			    == MG_FORM_FIELD_STORAGE_ABORT) {
 				/* Stop parsing the request */
+				abort_read = 1;
 				break;
 			}
 
@@ -471,6 +487,15 @@ mg_handle_form_request(struct mg_connection *conn,
 				} else {
 					vallen = (ptrdiff_t)strlen(val);
 					end_of_key_value_pair_found = all_data_read;
+					if ((buf + buf_fill) > (val + vallen)) {
+						/* Avoid DoS attacks by having a zero byte in the middle of
+						 * a request that is supposed to be URL encoded. Since this
+						 * request is certainly invalid, according to the protocol
+						 * specification, stop processing it. Fixes #1348 */
+						abort_read = 1;
+						break;
+					}
+
 				}
 
 				if (field_storage == MG_FORM_FIELD_STORAGE_GET) {
@@ -492,6 +517,7 @@ mg_handle_form_request(struct mg_connection *conn,
 					get_block++;
 					if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 						/* Stop request handling */
+						abort_read = 1;
 						break;
 					}
 					if (r == MG_FORM_FIELD_HANDLE_NEXT) {
@@ -560,7 +586,6 @@ mg_handle_form_request(struct mg_connection *conn,
 						val = buf;
 					}
 				}
-
 			} while (!end_of_key_value_pair_found);
 
 #if !defined(NO_FILESYSTEMS)
@@ -571,6 +596,7 @@ mg_handle_form_request(struct mg_connection *conn,
 					r = field_stored(conn, path, file_size, fdh);
 					if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 						/* Stop request handling */
+						abort_read = 1;
 						break;
 					}
 				} else {
@@ -584,7 +610,7 @@ mg_handle_form_request(struct mg_connection *conn,
 			}
 #endif /* NO_FILESYSTEMS */
 
-			if (all_data_read && (buf_fill == 0)) {
+			if ((all_data_read && (buf_fill == 0)) || abort_read) {
 				/* nothing more to process */
 				break;
 			}
@@ -972,6 +998,7 @@ mg_handle_form_request(struct mg_connection *conn,
 					get_block++;
 					if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 						/* Stop request handling */
+						abort_read = 1;
 						break;
 					}
 					if (r == MG_FORM_FIELD_HANDLE_NEXT) {
@@ -1046,6 +1073,7 @@ mg_handle_form_request(struct mg_connection *conn,
 				                        fdh);
 				if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 					/* Stop request handling */
+					abort_read = 1;
 					break;
 				}
 				if (r == MG_FORM_FIELD_HANDLE_NEXT) {
@@ -1074,6 +1102,7 @@ mg_handle_form_request(struct mg_connection *conn,
 							r = field_stored(conn, path, file_size, fdh);
 							if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 								/* Stop request handling */
+								abort_read = 1;
 								break;
 							}
 						} else {
@@ -1092,6 +1121,7 @@ mg_handle_form_request(struct mg_connection *conn,
 			if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT)
 			    == MG_FORM_FIELD_STORAGE_ABORT) {
 				/* Stop parsing the request */
+				abort_read = 1;
 				break;
 			}
 

+ 2 - 2
test/page4.lp

@@ -22,10 +22,10 @@ the "Kepler syntax" uses <code>&lt;?lua chunk ?&gt;</code>, <code>&lt;?lua= expr
 <h2>Tags</h2>
 
 <code>
-&lt;? greeting = 'CiwetWeb' ?&gt;<br/>
+&lt;? greeting = 'CivetWeb' ?&gt;<br/>
 &lt;strong&gt;&lt;?= greeting %&gt;&lt;/strong&gt;<br/>
 </code><br/>
-<? greeting = 'CiwetWeb' ?>
+<? greeting = 'CivetWeb' ?>
 ==> <strong><?= greeting ?></strong><br/>
 
 <br/>

+ 2 - 2
test/page4kepler.lp

@@ -19,10 +19,10 @@ the "Kepler syntax" uses <code>&lt;?lua chunk ?&gt;</code>, <code>&lt;?lua= expr
 <h2>Tags</h2>
 
 <code>
-&lt;? greeting = 'CiwetWeb' ?&gt;<br/>
+&lt;? greeting = 'CivetWeb' ?&gt;<br/>
 &lt;strong&gt;&lt;?= greeting %&gt;&lt;/strong&gt;<br/>
 </code><br/>
-<? greeting = 'CiwetWeb' ?>
+<? greeting = 'CivetWeb' ?>
 ==> <strong><?= greeting ?></strong><br/>
 
 <br/>