From 0c3733fa73fd68eab14c672c65137764eacae4d5 Mon Sep 17 00:00:00 2001 From: Marius Vollmer Date: Fri, 2 Aug 2024 16:46:50 +0300 Subject: [PATCH 1/3] WIP - prevent direct multi host scenarios by finding the current session and redirecting to it from the login page. --- pkg/static/login.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/pkg/static/login.js b/pkg/static/login.js index 37481144381e..e3956d0f9286 100644 --- a/pkg/static/login.js +++ b/pkg/static/login.js @@ -341,6 +341,19 @@ import "./login.scss"; function boot() { window.onload = null; + if (!environment.page.allow_multi_host) { + // If we are currently logged in, we do not want to allow + // another login to a different machine. So we redirect to + // the current login. + + const cur_machine = window.localStorage.getItem("current-machine"); + if (cur_machine == "localhost" && window.location.pathname.startsWith("/=")) { + login_reload("/"); + } else if (cur_machine && !window.location.pathname.startsWith("/=" + cur_machine)) { + login_reload("/=" + cur_machine); + } + } + translate(); if (window.cockpit_po && window.cockpit_po[""]) { document.documentElement.lang = window.cockpit_po[""].language; @@ -948,6 +961,8 @@ import "./login.scss"; } function login_reload (wanted) { + console.log("RELOAD", wanted); + // Force a reload if not triggered below // because only the hash part of the url // changed @@ -980,7 +995,7 @@ import "./login.scss"; } } - function setup_localstorage (response) { + function setup_localstorage (response, machine) { /* Clear anything not prefixed with * different application from sessionStorage */ @@ -1013,6 +1028,8 @@ import "./login.scss"; const ca_cert_url = environment.CACertUrl; if (ca_cert_url) window.sessionStorage.setItem('CACertUrl', ca_cert_url); + + window.localStorage.setItem('current-machine', machine || "localhost"); } function run(response) { @@ -1039,7 +1056,7 @@ import "./login.scss"; */ clear_storage(window.sessionStorage, application, false); - setup_localstorage(response); + setup_localstorage(response, machine); login_reload(wanted); } From 0e276140f50160fad6c1bfc35191214f08055f4d Mon Sep 17 00:00:00 2001 From: Marius Vollmer Date: Fri, 2 Aug 2024 17:29:34 +0300 Subject: [PATCH 2/3] WIP - show warning if multihost is allowed --- pkg/static/login.html | 8 ++++++++ pkg/static/login.js | 41 ++++++++++++++++++++++++++++++++--------- pkg/static/login.scss | 4 ++-- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/pkg/static/login.html b/pkg/static/login.html index d91201916952..3740f0a7e490 100644 --- a/pkg/static/login.html +++ b/pkg/static/login.html @@ -21,6 +21,14 @@ + +
diff --git a/pkg/static/login.js b/pkg/static/login.js index e3956d0f9286..e3db47a7b963 100644 --- a/pkg/static/login.js +++ b/pkg/static/login.js @@ -338,21 +338,41 @@ import "./login.scss"; event.stopPropagation(); } - function boot() { - window.onload = null; + function deal_with_multihost() { + // If we are currently logged in to some machine, but still + // end up on the login page, we are about to load resources + // from two machines into the same browser origin. - if (!environment.page.allow_multi_host) { - // If we are currently logged in, we do not want to allow - // another login to a different machine. So we redirect to - // the current login. + const cur_machine = window.localStorage.getItem("current-machine"); - const cur_machine = window.localStorage.getItem("current-machine"); - if (cur_machine == "localhost" && window.location.pathname.startsWith("/=")) { + // Protect against outdated cur_machine values. + if (cur_machine == "localhost" && !window.location.pathname.startsWith("/=")) + return; + if (cur_machine && cur_machine != "localhost" && window.location.pathname.startsWith("/=" + cur_machine)) + return; + + function redirect_to_current_machine() { + if (cur_machine == "localhost") login_reload("/"); - } else if (cur_machine && !window.location.pathname.startsWith("/=" + cur_machine)) { + else login_reload("/=" + cur_machine); + } + + environment.page.allow_multi_host = true; // XXX + + if (cur_machine) { + if (!environment.page.allow_multi_host) + redirect_to_current_machine(); + else { + id("multihost-message").textContent = format(_("You are already connected to '$0' in this browser session. Connecting to other hosts will allow them to execute arbitrary code on each other. Please be careful."), cur_machine); + id("multihost-get-me-there").addEventListener("click", redirect_to_current_machine); + show('#multihost-warning'); } } + } + + function boot() { + window.onload = null; translate(); if (window.cockpit_po && window.cockpit_po[""]) { @@ -361,6 +381,8 @@ import "./login.scss"; document.documentElement.dir = window.cockpit_po[""]["language-direction"]; } + deal_with_multihost(); + setup_path_globals(window.location.pathname); /* Determine if we are nested or not, and switch styles */ @@ -420,6 +442,7 @@ import "./login.scss"; oauth_auto_login(); } } else if (logout_intent) { + window.localStorage.removeItem("current-machine"); show_login(logout_reason); } else if (need_host()) { show_login(); diff --git a/pkg/static/login.scss b/pkg/static/login.scss index 33e664562fa9..b1fddb29d11f 100644 --- a/pkg/static/login.scss +++ b/pkg/static/login.scss @@ -354,14 +354,14 @@ label.checkbox { display: none; } -.login-pf #banner { +.login-pf #banner, .login-pf #multihost-warning { margin-block: 1rem 0.5rem; margin-inline: 0; grid-area: banner; inline-size: 100%; } -#banner-message { +#banner-message, #multihost-message { white-space: pre-wrap; max-block-size: 12em; overflow: auto; From 2d267b8ab9883bcf6e72706d6263cf89111b575d Mon Sep 17 00:00:00 2001 From: Marius Vollmer Date: Wed, 7 Aug 2024 12:37:17 +0300 Subject: [PATCH 3/3] WIP - inform login page about present cookies --- pkg/static/login.js | 23 ++++++-------------- src/ws/cockpitauth.c | 10 +++++++++ src/ws/cockpitauth.h | 2 ++ src/ws/cockpithandlers.c | 46 ++++++++++++++++++++++++++++++++++++++-- 4 files changed, 63 insertions(+), 18 deletions(-) diff --git a/pkg/static/login.js b/pkg/static/login.js index e3db47a7b963..7524dc6d4c17 100644 --- a/pkg/static/login.js +++ b/pkg/static/login.js @@ -343,28 +343,22 @@ import "./login.scss"; // end up on the login page, we are about to load resources // from two machines into the same browser origin. - const cur_machine = window.localStorage.getItem("current-machine"); - - // Protect against outdated cur_machine values. - if (cur_machine == "localhost" && !window.location.pathname.startsWith("/=")) - return; - if (cur_machine && cur_machine != "localhost" && window.location.pathname.startsWith("/=" + cur_machine)) - return; + const logged_into = environment["logged-into"]; + const cur_machine = logged_into.length > 0 ? logged_into[0] : null; function redirect_to_current_machine() { - if (cur_machine == "localhost") + if (cur_machine === ".") login_reload("/"); else login_reload("/=" + cur_machine); } - environment.page.allow_multi_host = true; // XXX - if (cur_machine) { if (!environment.page.allow_multi_host) redirect_to_current_machine(); else { - id("multihost-message").textContent = format(_("You are already connected to '$0' in this browser session. Connecting to other hosts will allow them to execute arbitrary code on each other. Please be careful."), cur_machine); + id("multihost-message").textContent = format(_("You are already connected to '$0' in this browser session. Connecting to other hosts will allow them to execute arbitrary code on each other. Please be careful."), + cur_machine == "." ? "localhost" : cur_machine); id("multihost-get-me-there").addEventListener("click", redirect_to_current_machine); show('#multihost-warning'); } @@ -442,7 +436,6 @@ import "./login.scss"; oauth_auto_login(); } } else if (logout_intent) { - window.localStorage.removeItem("current-machine"); show_login(logout_reason); } else if (need_host()) { show_login(); @@ -1018,7 +1011,7 @@ import "./login.scss"; } } - function setup_localstorage (response, machine) { + function setup_localstorage (response) { /* Clear anything not prefixed with * different application from sessionStorage */ @@ -1051,8 +1044,6 @@ import "./login.scss"; const ca_cert_url = environment.CACertUrl; if (ca_cert_url) window.sessionStorage.setItem('CACertUrl', ca_cert_url); - - window.localStorage.setItem('current-machine', machine || "localhost"); } function run(response) { @@ -1079,7 +1070,7 @@ import "./login.scss"; */ clear_storage(window.sessionStorage, application, false); - setup_localstorage(response, machine); + setup_localstorage(response); login_reload(wanted); } diff --git a/src/ws/cockpitauth.c b/src/ws/cockpitauth.c index 99f5c33ab2c6..f47beee377b9 100644 --- a/src/ws/cockpitauth.c +++ b/src/ws/cockpitauth.c @@ -1706,3 +1706,13 @@ cockpit_auth_empty_cookie_value (const gchar *path, gboolean secure) return cookie_line; } + +gchar * +cockpit_auth_cookie_name (const gchar *path) +{ + gchar *application = cockpit_auth_parse_application (path, NULL); + gchar *cookie = application_cookie_name (application); + g_free (application); + + return cookie; +} diff --git a/src/ws/cockpitauth.h b/src/ws/cockpitauth.h index 038d5a294c73..394414de848a 100644 --- a/src/ws/cockpitauth.h +++ b/src/ws/cockpitauth.h @@ -108,6 +108,8 @@ gchar * cockpit_auth_parse_application (const gchar *path, gchar * cockpit_auth_empty_cookie_value (const gchar *path, gboolean secure); +gchar * cockpit_auth_cookie_name (const gchar *path); + G_END_DECLS #endif diff --git a/src/ws/cockpithandlers.c b/src/ws/cockpithandlers.c index 29bb690cdf1a..686f4a201f32 100644 --- a/src/ws/cockpithandlers.c +++ b/src/ws/cockpithandlers.c @@ -277,8 +277,48 @@ add_page_to_environment (JsonObject *object, json_object_set_object_member (object, "page", page); } +static void +add_logged_into_to_environment (JsonObject *object, + const gchar *path, + GHashTable *request_headers) +{ + gchar *h = g_hash_table_lookup (request_headers, "Cookie"); + if (!h) + return; + + g_autofree gchar *self_cookie = cockpit_auth_cookie_name (path); + + JsonArray *logged_into = json_array_new (); + + while (*h) { + const gchar *start = h; + while (*h && *h != '=') + h++; + const gchar *equal = h; + while (*h && *h != ';') + h++; + if (*h) + h++; + while (*h && *h == ' ') + h++; + + if (g_str_has_prefix (equal, "=deleted")) + continue; + + g_autofree gchar *name = g_strndup (start, equal - start); + if (g_str_equal (name, self_cookie)) + ; + else if (g_str_equal (name, "cockpit")) + json_array_add_string_element(logged_into, "."); + else if (g_str_has_prefix (name, "machine-cockpit+")) + json_array_add_string_element(logged_into, name + strlen("machine-cockpit+")); + } + + json_object_set_array_member (object, "logged-into", logged_into); +} + static GBytes * -build_environment (GHashTable *os_release) +build_environment (GHashTable *os_release, const gchar *path, GHashTable *request_headers) { /* * We don't include entirety of os-release into the @@ -310,6 +350,7 @@ build_environment (GHashTable *os_release) json_object_set_boolean_member (object, "is_cockpit_client", is_cockpit_client); add_page_to_environment (object, is_cockpit_client); + add_logged_into_to_environment (object, path, request_headers); hostname = g_malloc0 (HOST_NAME_MAX + 1); gethostname (hostname, HOST_NAME_MAX); @@ -386,7 +427,7 @@ send_login_html (CockpitWebResponse *response, GBytes *po_bytes; CockpitWebFilter *filter3 = NULL; - environment = build_environment (ws->os_release); + environment = build_environment (ws->os_release, path, headers); filter = cockpit_web_inject_new (marker, environment, 1); g_bytes_unref (environment); cockpit_web_response_add_filter (response, filter); @@ -455,6 +496,7 @@ send_login_html (CockpitWebResponse *response, "Content-Security-Policy", content_security_policy, "Set-Cookie", cookie_line, NULL); + cockpit_web_response_set_cache_type (response, COCKPIT_WEB_RESPONSE_NO_CACHE); if (cockpit_web_response_queue (response, bytes)) cockpit_web_response_complete (response);