From dcc46710f742e29710a0a68fa2bd29975bde0019 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Fri, 27 Mar 2026 15:07:56 +0800 Subject: [PATCH] feat(esp_http_server): adds check for crlf in response creation --- components/esp_http_server/src/httpd_txrx.c | 20 +++++++++- .../test_apps/main/test_http_server.c | 37 ++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/components/esp_http_server/src/httpd_txrx.c b/components/esp_http_server/src/httpd_txrx.c index 7171de145c3..a8bd89cdf2b 100644 --- a/components/esp_http_server/src/httpd_txrx.c +++ b/components/esp_http_server/src/httpd_txrx.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -170,6 +170,12 @@ esp_err_t httpd_resp_set_hdr(httpd_req_t *r, const char *field, const char *valu return ESP_ERR_HTTPD_INVALID_REQ; } + /* Reject CRLF in header field or value */ + if (strpbrk(field, "\r\n") || strpbrk(value, "\r\n")) { + ESP_LOGW(TAG, LOG_FMT("rejecting header with CRLF: %.32s"), field); + return ESP_ERR_INVALID_ARG; + } + struct httpd_req_aux *ra = r->aux; struct httpd_data *hd = (struct httpd_data *) r->handle; @@ -201,6 +207,12 @@ esp_err_t httpd_resp_set_status(httpd_req_t *r, const char *status) return ESP_ERR_HTTPD_INVALID_REQ; } + /* Reject CRLF in status */ + if (strpbrk(status, "\r\n")) { + ESP_LOGW(TAG, LOG_FMT("rejecting status with CRLF: %.32s"), status); + return ESP_ERR_INVALID_ARG; + } + struct httpd_req_aux *ra = r->aux; ra->status = (char *)status; return ESP_OK; @@ -220,6 +232,12 @@ esp_err_t httpd_resp_set_type(httpd_req_t *r, const char *type) return ESP_ERR_HTTPD_INVALID_REQ; } + /* Reject CRLF in content type */ + if (strpbrk(type, "\r\n")) { + ESP_LOGW(TAG, LOG_FMT("rejecting content type with CRLF: %.32s"), type); + return ESP_ERR_INVALID_ARG; + } + struct httpd_req_aux *ra = r->aux; ra->content_type = (char *)type; return ESP_OK; diff --git a/components/esp_http_server/test_apps/main/test_http_server.c b/components/esp_http_server/test_apps/main/test_http_server.c index 0acb8c9ed40..e7edf35cc35 100644 --- a/components/esp_http_server/test_apps/main/test_http_server.c +++ b/components/esp_http_server/test_apps/main/test_http_server.c @@ -100,7 +100,7 @@ httpd_handle_t test_httpd_start(uint16_t id) /* Currently this only tests for the number of tasks. * Heap leakage is not tested as LWIP allocates memory - * which may not be freed immedietly causing erroneous + * which may not be freed immediately causing erroneous * evaluation. Another test to implement would be the * monitoring of open sockets, but LWIP presently provides * no such API for getting the number of open sockets. @@ -244,6 +244,41 @@ TEST_CASE("Max Allowed Sockets Test", "[HTTP SERVER]") TEST_ASSERT(httpd_start(&hd, &config) != ESP_OK); } +TEST_CASE("httpd_resp_set_hdr rejects CRLF in header field and value", "[HTTP SERVER][security]") +{ + httpd_req_t fake_req = {0}; + + /* \r\n in value */ + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, + httpd_resp_set_hdr(&fake_req, "X-Field", "val\r\nX-Injected: pwned")); + /* bare \n in value */ + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, + httpd_resp_set_hdr(&fake_req, "X-Field", "val\nX-Injected: pwned")); + /* \r\n in field name */ + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, + httpd_resp_set_hdr(&fake_req, "X-Field\r\nX-Injected: pwned", "val")); +} + +TEST_CASE("httpd_resp_set_status rejects CRLF in status string", "[HTTP SERVER][security]") +{ + httpd_req_t fake_req = {0}; + + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, + httpd_resp_set_status(&fake_req, "200 OK\r\nX-Injected: pwned")); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, + httpd_resp_set_status(&fake_req, "200 OK\nX-Injected: pwned")); +} + +TEST_CASE("httpd_resp_set_type rejects CRLF in content type", "[HTTP SERVER][security]") +{ + httpd_req_t fake_req = {0}; + + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, + httpd_resp_set_type(&fake_req, "text/html\r\nX-Injected: pwned")); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, + httpd_resp_set_type(&fake_req, "text/html\nX-Injected: pwned")); +} + void app_main(void) { unity_run_menu();