From af75cc18d9b4c0ada735478b2494cd93ad9c339d Mon Sep 17 00:00:00 2001 From: Hamid Reza Mohammadi Date: Fri, 25 Sep 2026 17:46:22 +0330 Subject: [PATCH] xss vulnerability fix --- GanjooRazor/Areas/User/Pages/DeleteAccount.cshtml.cs | 5 ++++- GanjooRazor/Areas/User/Pages/History.cshtml.cs | 9 +++++++++ GanjooRazor/Pages/Auth/ResetPassword.cshtml.cs | 7 +++++++ GanjooRazor/Pages/Auth/SignUp.cshtml.cs | 7 +++++++ GanjooRazor/Pages/GanjoorPage.cshtml | 9 ++++++++- GanjooRazor/Pages/GanjoorPage.cshtml.cs | 3 +++ GanjooRazor/Pages/LoginPartialEnabledPageModel.cs | 12 +++++++++++- GanjooRazor/Utils/GanjoorSessionChecker.cs | 7 +++++++ 8 files changed, 56 insertions(+), 3 deletions(-) diff --git a/GanjooRazor/Areas/User/Pages/DeleteAccount.cshtml.cs b/GanjooRazor/Areas/User/Pages/DeleteAccount.cshtml.cs index 8fe74c95..459b92b8 100644 --- a/GanjooRazor/Areas/User/Pages/DeleteAccount.cshtml.cs +++ b/GanjooRazor/Areas/User/Pages/DeleteAccount.cshtml.cs @@ -105,7 +105,10 @@ namespace GanjooRazor.Areas.User.Pages { var cookieOption = new CookieOptions() { - Expires = DateTime.Now.AddDays(-1) + Expires = DateTime.Now.AddDays(-1), + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Lax, }; foreach (var cookieName in new string[] { "UserId", "SessionId", "Token", "Username", "Name", "NickName", "CanEdit", "KeepHistory" }) { diff --git a/GanjooRazor/Areas/User/Pages/History.cshtml.cs b/GanjooRazor/Areas/User/Pages/History.cshtml.cs index 7cbc2a2e..a8cf9ac0 100644 --- a/GanjooRazor/Areas/User/Pages/History.cshtml.cs +++ b/GanjooRazor/Areas/User/Pages/History.cshtml.cs @@ -67,6 +67,9 @@ namespace GanjooRazor.Areas.User.Pages var cookieOption = new CookieOptions() { Expires = DateTime.Now.AddDays(365), + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Lax, }; Response.Cookies.Append("KeepHistory", $"{TrackingIsEnabled}", cookieOption); } @@ -206,6 +209,9 @@ namespace GanjooRazor.Areas.User.Pages var cookieOption = new CookieOptions() { Expires = DateTime.Now.AddDays(365), + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Lax, }; Response.Cookies.Append("KeepHistory", $"{false}", cookieOption); @@ -239,6 +245,9 @@ namespace GanjooRazor.Areas.User.Pages var cookieOption = new CookieOptions() { Expires = DateTime.Now.AddDays(365), + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Lax, }; Response.Cookies.Append("KeepHistory", $"{true}", cookieOption); diff --git a/GanjooRazor/Pages/Auth/ResetPassword.cshtml.cs b/GanjooRazor/Pages/Auth/ResetPassword.cshtml.cs index e67a7ef4..0d6d6583 100644 --- a/GanjooRazor/Pages/Auth/ResetPassword.cshtml.cs +++ b/GanjooRazor/Pages/Auth/ResetPassword.cshtml.cs @@ -235,9 +235,16 @@ namespace GanjooRazor.Pages LoggedOnUserModelEx loggedOnUser = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + // Authentication-related cookies are never read by client-side JavaScript + // (only server-side C# reads Request.Cookies[...]), so they can safely be + // marked HttpOnly to stop them being exfiltrated via document.cookie in the + // event of an XSS bug. Secure/SameSite=Lax provide additional defense-in-depth. var cookieOption = new CookieOptions() { Expires = DateTime.Now.AddDays(365), + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Lax, }; Response.Cookies.Append("UserId", loggedOnUser.User.Id.ToString(), cookieOption); diff --git a/GanjooRazor/Pages/Auth/SignUp.cshtml.cs b/GanjooRazor/Pages/Auth/SignUp.cshtml.cs index 014f5b61..8d5cfc7e 100644 --- a/GanjooRazor/Pages/Auth/SignUp.cshtml.cs +++ b/GanjooRazor/Pages/Auth/SignUp.cshtml.cs @@ -247,9 +247,16 @@ namespace GanjooRazor.Pages LoggedOnUserModelEx loggedOnUser = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + // Authentication-related cookies are never read by client-side JavaScript + // (only server-side C# reads Request.Cookies[...]), so they can safely be + // marked HttpOnly to stop them being exfiltrated via document.cookie in the + // event of an XSS bug. Secure/SameSite=Lax provide additional defense-in-depth. var cookieOption = new CookieOptions() { Expires = DateTime.Now.AddDays(365), + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Lax, }; Response.Cookies.Append("UserId", loggedOnUser.User.Id.ToString(), cookieOption); diff --git a/GanjooRazor/Pages/GanjoorPage.cshtml b/GanjooRazor/Pages/GanjoorPage.cshtml index ef736724..70f52bfe 100644 --- a/GanjooRazor/Pages/GanjoorPage.cshtml +++ b/GanjooRazor/Pages/GanjoorPage.cshtml @@ -165,11 +165,18 @@ else if (Model.ReadOnlyMode) @{ if (!string.IsNullOrEmpty(Model.PinterestUrl)) { + // PinterestUrl comes straight from the untrusted "pinterest_url" query string + // (see GanjoorPage.cshtml.cs). It must never be emitted with @Html.Raw into a + // ". Instead it is serialized + // as a JSON string literal (which escapes '<', '>', '&', quotes, etc.) and then + // URL-encoded again before being appended to href values.