Replace Web Basic Auth With Cookie Sessions
Add bounded login challenges, CSRF/origin enforcement, logout, and session-bound WebSocket admission. Isolate private HTTPD access behind a version-guarded adapter and add focused host coverage. Also let empty admin SSH input reach the normal console handler.
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
/* Deliberately isolated dependency on the installed IDF HTTPD layout. */
|
||||
#include "web_httpd_adapter.h"
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include "esp_idf_version.h"
|
||||
#include "esp_httpd_priv.h"
|
||||
#include "secure_random.h"
|
||||
|
||||
#if ESP_IDF_VERSION != ESP_IDF_VERSION_VAL(5, 5, 0)
|
||||
#error "Reaudit HTTPD header storage and pre-handler upgrade behavior for this IDF"
|
||||
#endif
|
||||
|
||||
bool web_httpd_headers_valid(httpd_req_t *request)
|
||||
{
|
||||
if (!request || !request->aux) return false;
|
||||
const struct httpd_req_aux *aux = request->aux;
|
||||
const char *start = aux->scratch;
|
||||
if (!start || aux->scratch_cur_size > 1024U) return false;
|
||||
const char *end = start + aux->scratch_cur_size;
|
||||
const char *line = start;
|
||||
for (unsigned i = 0; i < aux->req_hdrs_count; ++i) {
|
||||
if (line >= end) return false;
|
||||
while (line < end && !*line) ++line;
|
||||
const char *stop = memchr(line, 0, (size_t)(end - line));
|
||||
if (!stop) return false;
|
||||
const char *colon = memchr(line, ':', (size_t)(stop - line));
|
||||
if (!colon || colon == line) return false;
|
||||
size_t length = (size_t)(colon - line);
|
||||
for (const char *p = line; p < colon; ++p) {
|
||||
if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') ||
|
||||
(*p >= '0' && *p <= '9') || strchr("!#$%&'*+-.^_`|~", *p))) return false;
|
||||
}
|
||||
for (const char *p = colon + 1; p < stop; ++p) {
|
||||
if ((unsigned char)*p < 32U || (unsigned char)*p == 127U) return false;
|
||||
}
|
||||
/* Reject transfer coding and Expect rather than draining an unbounded
|
||||
* body after an authentication failure. No application route uses them. */
|
||||
if ((length == 17U && !strncasecmp(line, "Transfer-Encoding", length)) ||
|
||||
(length == 6U && !strncasecmp(line, "Expect", length))) return false;
|
||||
const char *previous = start;
|
||||
for (unsigned j = 0; j < i; ++j) {
|
||||
while (previous < line && !*previous) ++previous;
|
||||
const char *previous_end = memchr(previous, 0, (size_t)(line - previous));
|
||||
if (!previous_end) return false;
|
||||
const char *previous_colon = memchr(previous, ':', (size_t)(previous_end - previous));
|
||||
if (!previous_colon) return false;
|
||||
if ((size_t)(previous_colon - previous) == length &&
|
||||
!strncasecmp(previous, line, length)) return false;
|
||||
previous = previous_end + 1;
|
||||
}
|
||||
line = stop + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool web_httpd_upgrade_requested(httpd_req_t *request)
|
||||
{
|
||||
const struct httpd_req_aux *aux = request->aux;
|
||||
if (!aux || !aux->sd || !aux->ws_handshake_detect || aux->sd->ws_handshake_done)
|
||||
return false;
|
||||
char version[3], key[25];
|
||||
if (httpd_req_get_hdr_value_len(request, "Sec-WebSocket-Version") != 2U ||
|
||||
httpd_req_get_hdr_value_str(request, "Sec-WebSocket-Version", version, sizeof(version)) != ESP_OK ||
|
||||
strcmp(version, "13") || httpd_req_get_hdr_value_len(request, "Sec-WebSocket-Key") != 24U ||
|
||||
httpd_req_get_hdr_value_str(request, "Sec-WebSocket-Key", key, sizeof(key)) != ESP_OK ||
|
||||
key[22] != '=' || key[23] != '=' || !strchr("AQgw", key[21])) return false;
|
||||
for (unsigned i = 0; i < 21; ++i)
|
||||
if (!strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", key[i])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t web_httpd_upgrade(httpd_req_t *request,
|
||||
esp_err_t (*handler)(httpd_req_t *))
|
||||
{
|
||||
if (!web_httpd_upgrade_requested(request)) return ESP_ERR_INVALID_STATE;
|
||||
esp_err_t error = httpd_ws_respond_server_handshake(request, NULL);
|
||||
if (error == ESP_OK) {
|
||||
struct httpd_req_aux *aux = request->aux;
|
||||
aux->sd->ws_handshake_done = true;
|
||||
aux->sd->ws_handler = handler;
|
||||
aux->sd->ws_control_frames = false;
|
||||
aux->sd->ws_user_ctx = NULL;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
void web_httpd_wipe_request(httpd_req_t *request, bool closing)
|
||||
{
|
||||
struct httpd_req_aux *aux = request->aux;
|
||||
if (!aux) return;
|
||||
if (aux->scratch) secure_wipe(aux->scratch, aux->scratch_cur_size);
|
||||
aux->req_hdrs_count = 0;
|
||||
if (aux->sd) {
|
||||
size_t keep = closing ? 0 : aux->sd->pending_len;
|
||||
/* httpd_unrecv()/httpd_recv_pending() right-align unread bytes. */
|
||||
if (keep <= sizeof(aux->sd->pending_data))
|
||||
secure_wipe(aux->sd->pending_data, sizeof(aux->sd->pending_data) - keep);
|
||||
}
|
||||
}
|
||||
|
||||
bool web_httpd_unread_body(httpd_req_t *request)
|
||||
{
|
||||
const struct httpd_req_aux *aux = request->aux;
|
||||
return aux && aux->remaining_len != 0;
|
||||
}
|
||||
Reference in New Issue
Block a user