From 3c2f81c6a81e436ffab19b9e0466d55996477ec2 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 | 35 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/components/esp_http_server/src/httpd_txrx.c b/components/esp_http_server/src/httpd_txrx.c index edd62c5f1d..b4dd58d53a 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-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -178,6 +178,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; @@ -209,6 +215,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; @@ -228,6 +240,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 3d8a7f03fa..101644448e 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 @@ -367,6 +367,41 @@ TEST_CASE("Interface Binding Test", "[HTTP SERVER]") TEST_ASSERT(httpd_stop(hd2) == 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();