fix(esp_http_client): strip Authorization header on cross-origin redirect

This commit is contained in:
Ashish Sharma
2026-05-21 15:51:05 +08:00
parent fb4cf0f0dc
commit 3ba6f4b786
2 changed files with 57 additions and 1 deletions

View File

@@ -1239,6 +1239,10 @@ esp_err_t esp_http_client_set_url(esp_http_client_handle_t client, const char *u
free(old_host);
return ESP_ERR_NO_MEM;
}
/* Cross-origin credential hygiene: an Authorization header set by the
* application for the original host must not be re-sent to a different
* host (e.g. an attacker-controlled redirect target). */
http_header_delete(client->request->headers, "Authorization");
/* Free cached data if any, as we are closing this connection */
esp_http_client_cached_buf_cleanup(client->response->buffer);
esp_http_client_close(client);

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -186,6 +186,58 @@ TEST_CASE("esp_http_client_set_header() should not return error if header value
esp_http_client_cleanup(client);
}
/**
* Cross-origin credential leak: an Authorization header set by the application
* must NOT be carried across a host change in esp_http_client_set_url (which
* happens on redirects). Failing to clear it leaks tokens to attacker-controlled
* hosts when the trusted server returns a 30x Location pointing elsewhere.
*/
TEST_CASE("set_url() to a different host strips Authorization header", "[esp_http_client]")
{
esp_http_client_config_t config = {
.url = "http://httpbin.org/get",
};
esp_http_client_handle_t client = esp_http_client_init(&config);
TEST_ASSERT_NOT_NULL(client);
TEST_ASSERT_EQUAL(ESP_OK, esp_http_client_set_header(client, "Authorization", "Bearer secret-token"));
char *value = NULL;
TEST_ASSERT_EQUAL(ESP_OK, esp_http_client_get_header(client, "Authorization", &value));
TEST_ASSERT_NOT_NULL(value);
/* Simulate an attacker-controlled redirect target */
TEST_ASSERT_EQUAL(ESP_OK, esp_http_client_set_url(client, "http://attacker.example/steal"));
value = NULL;
esp_err_t err = esp_http_client_get_header(client, "Authorization", &value);
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, err);
TEST_ASSERT_NULL(value);
esp_http_client_cleanup(client);
}
/* Regression guard: same-host set_url (e.g. redirect to a different path on the
* same origin) must preserve the Authorization header. */
TEST_CASE("set_url() to the same host preserves Authorization header", "[esp_http_client]")
{
esp_http_client_config_t config = {
.url = "http://httpbin.org/get",
};
esp_http_client_handle_t client = esp_http_client_init(&config);
TEST_ASSERT_NOT_NULL(client);
TEST_ASSERT_EQUAL(ESP_OK, esp_http_client_set_header(client, "Authorization", "Bearer token"));
TEST_ASSERT_EQUAL(ESP_OK, esp_http_client_set_url(client, "http://httpbin.org/other"));
char *value = NULL;
TEST_ASSERT_EQUAL(ESP_OK, esp_http_client_get_header(client, "Authorization", &value));
TEST_ASSERT_NOT_NULL(value);
TEST_ASSERT_EQUAL_STRING("Bearer token", value);
esp_http_client_cleanup(client);
}
static int disconnect_event_count = 0;
static esp_err_t disconnect_event_handler(esp_http_client_event_t *evt)