CVE-2023-3294: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site Scripting (XSS) - DOM in GitHub repository saleor/react-storefront prior to c29aab226f07ca980cc19787dcef101e11b83ef7.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2023-06-16T12:15Z
Repository: https://github.com/saleor/react-storefront
Language: Other
Files changed: apps/storefront/components/AccountLayout/AccountLayout.tsx, apps/storefront/pages/[channel]/[locale]/account/login.tsx
Commit date: 2023-06-16 12:05:07 +0200
Commit message: Merge pull request from GHSA-wq85-q492-8vxv

* Fix open redirect

* Code REview
Diff: diff --git a/apps/storefront/components/AccountLayout/AccountLayout.tsx b/apps/storefront/components/AccountLayout/AccountLayout.tsx
index be65820..da3c66e 100644
--- a/apps/storefront/components/AccountLayout/AccountLayout.tsx
+++ b/apps/storefront/components/AccountLayout/AccountLayout.tsx
@@ -21,12 +21,14 @@ export function AccountLayout({ children }: AccountLayoutProps) {
         <Spinner />
       </Layout>
     );
   }
 
-  if (!authenticated && typeof window !== "undefined") {
-    void router.push(paths.account.login.$url({ query: { next: router?.pathname } }));
+  if (!authenticated) {
+    if (router.isReady && typeof window !== "undefined") {
+      void router.replace(paths.account.login.$url({ query: { next: router?.asPath } }));
+    }
     return null;
   }
 
   return (
     <Layout>
diff --git a/apps/storefront/pages/[channel]/[locale]/account/login.tsx b/apps/storefront/pages/[channel]/[locale]/account/login.tsx
index 30929a3..94a0997 100644
--- a/apps/storefront/pages/[channel]/[locale]/account/login.tsx
+++ b/apps/storefront/pages/[channel]/[locale]/account/login.tsx
@@ -38,12 +38,10 @@ function LoginPage() {
     formState: { errors: errorsForm },
     setError: setErrorForm,
   } = useForm<LoginFormData>({ defaultValues });
 
   const routerQueryNext = router.query.next?.toString() || "";
-  const isExternalUrl = /^\w+:\/\//.test(routerQueryNext);
-  const redirectURL = !routerQueryNext || isExternalUrl ? paths.$url() : routerQueryNext;
 
   const handleLogin = handleSubmitForm(async (formData: LoginFormData) => {
     const { data } = await signIn({
       email: formData.email,
       password: formData.password,
@@ -52,10 +50,13 @@ function LoginPage() {
     if (data?.tokenCreate?.errors?.length) {
       setErrorForm("email", { message: "Invalid credentials" });
       return;
     }
 
+    const redirectURL =
+      (routerQueryNext && new URL(routerQueryNext, window.location.toString()).pathname) ||
+      paths.$url();
     void router.push(redirectURL);
   });
 
   return (
     <div className="min-h-screen bg-no-repeat bg-cover bg-center bg-gradient-to-r from-blue-100 to-blue-500">

Vulnerable code:   if (!authenticated && typeof window !== "undefined") {
    void router.push(paths.account.login.$url({ query: { next: router?.pathname } }));
  const isExternalUrl = /^\w+:\/\//.test(routerQueryNext);
  const redirectURL = !routerQueryNext || isExternalUrl ? paths.$url() : routerQueryNext;
Fixed code:   if (!authenticated) {
    if (router.isReady && typeof window !== "undefined") {
      void router.replace(paths.account.login.$url({ query: { next: router?.asPath } }));
    }
    const redirectURL =
      (routerQueryNext && new URL(routerQueryNext, window.location.toString()).pathname) ||
      paths.$url();