From 3fd9cdfa7e1bab657997e9d751948d08b44be969 Mon Sep 17 00:00:00 2001 From: d00f Date: Tue, 21 Jul 2026 06:48:18 +0200 Subject: [PATCH] httpd: parse the session cookie by name, not fixed offset Root cause of the "browser login always bounces back with Wrong password! while curl works": scan_header() read the session id from a fixed offset into the Cookie header (p + 17), assuming "session=" is the first and only cookie. Browsers keep stale cookies for a long time - e.g. an "admin" cookie left over from this switch's VENDOR firmware - so the header can arrive as "Cookie: admin=..; session=..", the fixed offset then points into the admin value, authentication silently fails and every page bounces to login although the password had been accepted. curl sends only "session=", which is why command-line tests passed while a real browser (with that stale cookie) failed. - scan_header(): scan the Cookie header for the actual "session=" key (matched as "session" - is_word() requires a separator after the pattern and '=' is on its list, the first value byte is not). - is_word_x(): accept ';' as a terminating separator so the session value also matches when it is not the last cookie in the header. Verified on hardware end-to-end in a real browser WITH the stale "admin" cookie present: login -> index.html, all pages and JSON endpoints work. --- httpd/httpd.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index c283b8f..51bd7eb 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -174,7 +174,10 @@ bool is_word_x(__xdata uint8_t *lhs_str_p, __xdata uint8_t *rhs_str_p) c = *rhs_str_p++; if (c == '\0') { - if (u != '\0' && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r') + /* ';' terminates a cookie value when it is not the last cookie + * in the header ("session=X; other=y") - accept it as a word + * boundary so such a session cookie still authenticates. */ + if (u != '\0' && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r' && u != ';') return false; return true; } @@ -252,8 +255,28 @@ __xdata uint8_t *scan_header(__xdata uint8_t *p) break; if (is_word(p, "\nContent-Type:")) content_type = p + 15; - else if (is_word(p, "\nCookie:")) - session = p + 17; + else if (is_word(p, "\nCookie:")) { + /* The Cookie header may carry SEVERAL cookies ("a=1; session=X"), + * and browsers keep stale cookies for years - e.g. an "admin" + * cookie left over from this switch's vendor firmware. The old + * fixed-offset parse (p + 17) assumed "session=" was the first + * and only cookie, so with any other cookie present it pointed + * into the wrong value, authentication silently failed and the + * UI bounced back to the login page although the password had + * been accepted (worked from curl, which sends only session=). + * Scan the header for the actual "session=" key instead. */ + /* Match "session" (not "session="): is_word() demands a separator + * after the pattern, and '=' is on its separator list while the + * first byte of the value is not. */ + __xdata uint8_t *c = p + 8; /* past "\nCookie:" */ + while (*c && *c != '\r' && *c != '\n') { + if (is_word(c, "session")) { + session = c + 8; /* past "session=" */ + break; + } + c++; + } + } } if (content_type && is_word(content_type, "multipart/form-data; boundary")) { dbg_string("\nFound multipart\n");