From c72d232e2f21a5b4484be9bf7b0632642f3c72e2 Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Tue, 23 Jun 2026 01:09:43 +0200 Subject: [PATCH] Fix to restrict Basic auth for websocket routes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ⓘ Require websocket routes to opt in with allow_basic_ws before Basic auth is accepted in session mode. Mark linked ws-link routes and no-cookie backend-session routes as allowed, while leaving normal session-backed routes unmarked. --- miniserv.pl | 15 +++++++++++---- servers/link.cgi | 2 +- t/miniserv.t | 3 ++- web-lib-funcs.pl | 4 +++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/miniserv.pl b/miniserv.pl index bd0601cd8..fe17098f8 100755 --- a/miniserv.pl +++ b/miniserv.pl @@ -1512,6 +1512,7 @@ alarm(0); my $websocket_upgrade_request = lc($header{'connection'}) =~ /upgrade/ && lc($header{'upgrade'}) eq 'websocket'; my $websocket_configured_request; +my $websocket_basic_auth_ok; if ($websocket_upgrade_request) { # Check the configured websocket paths before auth, so Basic auth can # remain disabled for normal session-mode requests. @@ -1519,6 +1520,12 @@ if ($websocket_upgrade_request) { my $ws_simple = &simplify_path($page, $wsbogus); $websocket_configured_request = !$wsbogus && &find_websocket_config($ws_simple); + if ($websocket_configured_request) { + # Session-mode Basic auth stays disabled unless the websocket + # route explicitly allows a no-cookie Basic-auth hop. + $websocket_basic_auth_ok = + $websocket_configured_request->{'allow_basic_ws'}; + } } # If a remote IP is given in a header (such as via a proxy), only use it @@ -1840,11 +1847,11 @@ if (!$validated && !$deny_authentication) { } } -# Keep Basic auth disabled in session mode except for configured websocket -# proxy paths. Linked-server websocket hops need it, and token/user checks -# still run before the backend connection is opened. +# Keep Basic auth disabled in session mode except for websocket routes that +# explicitly allow no-cookie Basic-auth hops. Token/user checks still run +# before the backend connection is opened. if (!$validated && !$deny_authentication && - (!$config{'session'} || $websocket_configured_request) && + (!$config{'session'} || $websocket_basic_auth_ok) && $header{authorization} =~ /^basic\s+(\S+)$/i) { # authorization given.. ($authuser, $authpass) = split(/:/, &b64decode($1), 2); diff --git a/servers/link.cgi b/servers/link.cgi index 3459eb149..ea2b7c3d2 100755 --- a/servers/link.cgi +++ b/servers/link.cgi @@ -312,7 +312,7 @@ my %miniserv; $miniserv{"websockets_$wspath"} = "host=$backend_host port=$port ssl=$ssl wspath=$remote_path ". "hostheader=$hostheader origin=$origin auth=basic:$auth ". - "checkssl=$checkssl nokey=1 user=$main::remote_user ". + "checkssl=$checkssl nokey=1 allow_basic_ws=1 user=$main::remote_user ". "token=$token time=$now"; &put_miniserv_config(\%miniserv); &unlock_file(&get_miniserv_config_file()); diff --git a/t/miniserv.t b/t/miniserv.t index acb35c135..59a690352 100644 --- a/t/miniserv.t +++ b/t/miniserv.t @@ -1311,7 +1311,7 @@ EOF subtest 'parse_websockets_config' => sub { no warnings 'once'; local %miniserv::config = ( - 'websockets_/chat' => 'host=back.example.com port=9000 proto=ws ssl=1 checkssl=1 backend_session=abc123', + 'websockets_/chat' => 'host=back.example.com port=9000 proto=ws ssl=1 checkssl=1 backend_session=abc123 allow_basic_ws=1', 'unrelated_key' => 'ignored', ); local @miniserv::websocket_paths = (); @@ -1326,6 +1326,7 @@ subtest 'parse_websockets_config' => sub { is($ws->{ssl}, '1', 'ssl kv parsed'); is($ws->{checkssl}, '1', 'SSL check kv parsed'); is($ws->{backend_session}, 'abc123', 'backend session kv parsed'); + is($ws->{allow_basic_ws}, '1', 'Basic auth websocket flag parsed'); }; # find_websocket_config diff --git a/web-lib-funcs.pl b/web-lib-funcs.pl index 6c5a10eb9..005d821d0 100755 --- a/web-lib-funcs.pl +++ b/web-lib-funcs.pl @@ -14331,8 +14331,10 @@ my $opt_backend = ""; # cookie. Store the one-time backend session key that the child server expects. $opt_backend = " backend_session=$backend_session" if (defined($backend_session) && $backend_session =~ /^\S+$/); +my $opt_basic = ""; +$opt_basic = " allow_basic_ws=1" if ($opt_backend); $miniserv{"websockets_$wspath"} = "host=127.0.0.1 port=$port wspath=/ ". - "user=$remote_user$opt_buser$opt_backend token=$token time=$now"; + "user=$remote_user$opt_buser$opt_backend$opt_basic token=$token time=$now"; &put_miniserv_config(\%miniserv); &unlock_file(&get_miniserv_config_file()); &reload_miniserv();