CVE-2023-41101 (C, published 2023-11-17T06:15Z)
Description: value: An issue was discovered in the captive portal in OpenNDS before version 10.1.3. get_query in http_microhttpd.c does not validate the length of the query string of GET requests. This leads to a stack-based buffer overflow in versions 9.x and earlier, and to a heap-based buffer overflow in versions 10.x and later. Attackers may exploit the issue to crash OpenNDS (Denial-of-Service condition) or to inject and execute arbitrary bytecode (Remote Code Execution). Affected OpenNDS before version 10.1.3 fixed in OpenWrt master and OpenWrt 23.05 on 23. November by updating OpenNDS to version 10.2.0.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-787 \u2014 Out-of-bounds Write
The product writes data past the end, or before the beginning, of the intended buffer.
Repository: https://github.com/openNDS/openNDS
Files changed:
src/http_microhttpd.c
Commit message: Fix - Buffer overflow causing segfault - CVE-2023-41101

Signed-off-by: Rob White <rob@blue-wave.net>


Vulnerable code:
	preauthpath = safe_calloc(SMALL_BUF);
	safe_asprintf(&preauthpath, "/%s/", config->preauthdir);
	if (strcmp(preauthpath, config->fas_path) == 0) {
		free (preauthpath);
		user_agent = safe_calloc(USER_AGENT);
		enc_user_agent = safe_calloc(ENC_USER_AGENT);
		MHD_get_connection_values(connection, MHD_HEADER_KIND, get_user_agent_callback, &user_agent);
		if (user_agent == NULL) {
			return send_error(connection, 403);
		}
		uh_urlencode(enc_user_agent, ENC_USER_AGENT, user_agent, strlen(user_agent));
		debug(LOG_DEBUG, "PreAuth: Encoded User Agent is [ %s ]", enc_user_agent);
		enc_query = safe_calloc(ENC_QUERYSTR);
		uh_urlencode(enc_query, ENC_QUERYSTR, query, strlen(query));
		debug(LOG_DEBUG, "PreAuth: Encoded query: %s", enc_query);
		msg = safe_calloc(HTMLMAXSIZE);
		if (!msg) {
			ret = send_error(connection, 503);
			free(msg);
			free(enc_user_agent);
			free(enc_query);
			return ret;
		}
		cmd = safe_calloc(QUERYMAXLEN);
		safe_asprintf(&cmd, "%s '%s' '%s' '%d' '%s'", config->preauth, enc_query, enc_user_agent, config->login_option_enabled, config->themespec_path);
		rc = execute_ret_url_encoded(msg, HTMLMAXSIZE - 1, cmd);
		free(cmd);
		if (rc != 0) {
			debug(LOG_WARNING, "Preauth script - failed to execute: %s, Query[%s]", config->preauth, query);
			free(msg);
			free(enc_user_agent);
			free(enc_query);
			return send_error(connection, 511);
		}
		// serve the script output (in msg)
		response = MHD_create_response_from_buffer(strlen(msg), (char *)msg, MHD_RESPMEM_MUST_FREE);
		if (!response) {
			return send_error(connection, 503);
		}
		MHD_add_response_header(response, "Content-Type", "text/html; charset=utf-8");
		ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
		MHD_destroy_response(response);
		free(enc_user_agent);
		free(enc_query);
		return ret;
	} else {
		free (preauthpath);
		return send_error(connection, 404);
	int j;
		strncpy(*query + j, elements[i], length - j);
		if (i == 0) {
			// query_str is empty when i = 0 so safe to copy a single char into it
			strcpy(query_str, "?");
		} else {
			if (QUERYMAXLEN - strlen(query_str) > length - j + 1) {
				strncat(query_str, separator, QUERYMAXLEN - strlen(query_str));
		}
		// note: query string will be truncated if too long
		if (QUERYMAXLEN - strlen(query_str) > length - j) {
			debug(LOG_WARNING, " Query string exceeds the maximum of %d bytes so has been truncated.", QUERYMAXLEN/2);

Fixed code:


	if ( strlen(query) < 1 ) {
		// query string is blank, too long or corrupt
		return send_error(connection, 511);
	} else {	
		preauthpath = safe_calloc(SMALL_BUF);
		safe_asprintf(&preauthpath, "/%s/", config->preauthdir);
		if (strcmp(preauthpath, config->fas_path) == 0) {
			free (preauthpath);
			user_agent = safe_calloc(USER_AGENT);
			debug(LOG_DEBUG, "PreAuth: User Agent ptr is [ %d ]", &user_agent);
			enc_user_agent = safe_calloc(ENC_USER_AGENT);
			MHD_get_connection_values(connection, MHD_HEADER_KIND, get_user_agent_callback, &user_agent);
			debug(LOG_DEBUG, "PreAuth: MHD User Agent ptr is [ %d ]", &user_agent);
			if (user_agent == NULL) {
				return send_error(connection, 403);
			}
			uh_urlencode(enc_user_agent, ENC_USER_AGENT, user_agent, strlen(user_agent));
			debug(LOG_DEBUG, "PreAuth: Encoded User Agent is [ %s ]", enc_user_agent);
			enc_query = safe_calloc(ENC_QUERYSTR);
			uh_urlencode(enc_query, ENC_QUERYSTR, query, strlen(query));
			debug(LOG_DEBUG, "PreAuth: Encoded query: %s", enc_query);
			msg = safe_calloc(HTMLMAXSIZE);
			if (!msg) {
				ret = send_error(connection, 503);
				free(msg);
				free(enc_user_agent);
				free(enc_query);
				return ret;
			}
			cmd = safe_calloc(QUERYMAXLEN);
			safe_asprintf(&cmd, "%s '%s' '%s' '%d' '%s'", config->preauth, enc_query, enc_user_agent, config->login_option_enabled, config->themespec_path);
			rc = execute_ret_url_encoded(msg, HTMLMAXSIZE - 1, cmd);
			free(cmd);
			if (rc != 0) {
				debug(LOG_WARNING, "Preauth script - failed to execute: %s, Query[%s]", config->preauth, query);
				free(msg);
				free(enc_user_agent);
				free(enc_query);
				return send_error(connection, 511);
			}
			// serve the script output (in msg)
			response = MHD_create_response_from_buffer(strlen(msg), (char *)msg, MHD_RESPMEM_MUST_FREE);
			if (!response) {
				return send_error(connection, 503);
			}
			MHD_add_response_header(response, "Content-Type", "text/html; charset=utf-8");
			ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
			MHD_destroy_response(response);
			free(enc_user_agent);
			free(enc_query);
			return ret;
		} else {
			free (preauthpath);
			return send_error(connection, 404);
		}

	int j = 4;

		debug(LOG_DEBUG, " cumulative element length is [%d]", length);
		if (length + j < QUERYMAXLEN) {

			strncpy(*query + j, elements[i], length - j);

			if (i == 0) {
				// query_str is empty when i = 0 so safe to copy a single char into it
				strcpy(query_str, "?");
			} else {
				if (QUERYMAXLEN - strlen(query_str) > length - j + 1) {
					strncat(query_str, separator, QUERYMAXLEN - strlen(query_str));
				}
			debug(LOG_WARNING, " Query string is too long, invalid or corrupt so is ignored.");