Morgbn / nuxt-csurf

Nuxt Cross-Site Request Forgery (CSRF) Prevention
https://nuxt-csurf.vercel.app
MIT License
81 stars 16 forks source link

Doesn't work on serverless #41

Closed moshetanzer closed 2 weeks ago

moshetanzer commented 4 months ago

Hi,

As explained here at length this module does NOT work on serverless since state is not saved and if server isn't warm/hot, cold restarts happen all the time which requires page reloads (on user side to update token) basically any time you use a form that has CSRF protection.

This is discussed at length in the nuxt security package https://github.com/Baroshem/nuxt-security/issues/477

Unless this issue is sorted or a different method is used, using this will create a really bad ux in your project. Author is aware of this but has not responded to queries.

Morgbn commented 4 months ago

Hi, can you please share your nuxt.config ? I've hosted the playground on cloudfare, it seems to work

bojanrajh commented 3 months ago

We've been debuggin similar issue with Vercel last month, here are our findings (and example config): https://github.com/Morgbn/nuxt-csurf/issues/28#issuecomment-2194360017

moshetanzer commented 3 months ago

Yip. Not sure why maintainer doesn’t seem to see issue. There are issues open also in the nuxt security package. See the origin solution I posted in Nuxt Security it works well and basically is supported by every browser and is suggested by owasp

https://github.com/Baroshem/nuxt-security/issues/477

Morgbn commented 3 months ago

Yip. Not sure why maintainer doesn’t seem to see issue. There are issues open also in the nuxt security package. See the origin solution I posted in Nuxt Security it works well and basically is supported by every browser and is suggested by owasp

the aim is to understand where the problem comes from. Not to rewrite this module with another solution. But the lack of reproduction doesn't help. I even hosted the playground on Cloudfare, and it works...

moshetanzer commented 3 months ago

Hey @Morgbn the playground is the repro. Just deploy on Vercel. Not exactly sure how cloudflare infra works.

ccwebgroup commented 3 months ago

Playground, with CSRF header, not working. check it out.

Barbapapazes commented 2 months ago

Refresh the page and then retry, it works but indeed, the cookie with the csrf token is not set at the first request.

Barbapapazes commented 2 months ago

In the mean time, if nothing happens in the first 40 seconds, it seems the CSRF token is invalid

https://github.com/user-attachments/assets/449a69e5-2033-4144-9c85-111030dfd318

Barbapapazes commented 2 months ago

See these 2 requests, they fails despite having the same CSRF token

Screenshot 2024-08-20 at 17 13 03 Screenshot 2024-08-20 at 17 13 46
Barbapapazes commented 2 months ago

This could come from the encryptSecret that change from one request to another?

Barbapapazes commented 2 months ago

I deploy the playground on my Cloudflare account to check and the key change between two requests (no redeploy). The logs above are the result of the following console.log

import * as csrf from "uncsrf";
import { defineEventHandler, getCookie, getHeader, createError } from "h3";
import { useRuntimeConfig, getRouteRules } from "#imports";
import { useSecretKey } from "../helpers";
import { defuReplaceArray } from "../../utils";

const baseConfig = useRuntimeConfig().csurf;

export default defineEventHandler(async (event) => {
  const { csurf } = getRouteRules(event);
  if (csurf === false || csurf?.enabled === false) {
    return;
  } // csrf protection disabled for this route

  const csrfConfig = defuReplaceArray(csurf ?? {}, baseConfig);
  const method = event.node.req.method ?? "";
  const methodsToProtect = csrfConfig.methodsToProtect ?? [];
  if (!methodsToProtect.includes(method)) {
    return;
  }

  const secret = getCookie(event, csrfConfig.cookieKey!) ?? "";
  const token = getHeader(event, baseConfig.headerName!) ?? "";

  const secretKey = await useSecretKey(csrfConfig);

  console.log(secret, token, secretKey); // <------ Log in the screenshot above.

  // verify the incoming csrf token
  const isValidToken = await csrf.verify(
    secret,
    token,
    secretKey,
    csrfConfig.encryptAlgorithm
  );
  if (!isValidToken) {
    throw createError({
      statusCode: 403,
      name: "EBADCSRFTOKEN",
      statusMessage: "CSRF Token Mismatch",
    });
  }
});
Screenshot 2024-08-20 at 17 29 42 Screenshot 2024-08-20 at 17 30 19

@Morgbn is there a way to make it stable?

Morgbn commented 2 months ago

Hello @Barbapapazes, thanks for the detailed question! I think I've figured out why after about ten seconds the token changes: useSecretKey store secretKey in a global var, that not compatible with serverless functions... I'll look for a solution to this problem

atinux commented 2 months ago

It may need some rewrite for the config, if the secretKey should be change after the build, I suggest to generate it in the module at build time and store it in the runtimeConfig

bojanrajh commented 2 months ago

@atinux Wouldn't that break cross-deploy form submissions? And is there something wrong with encryptSecret being statically defined?

atinux commented 2 months ago

Well it's already the case @bojanrajh

Very good question, I don't see any issue for having the same encryptSecret but @Morgbn may have more insights about this

MineDrum commented 4 weeks ago

I've just come across this issue while using nuxt-security with NuxtHub (cloudflare pages)

Is there some sort of work around or a fix on the horizon?

github-actions[bot] commented 2 weeks ago

:tada: This issue has been resolved in version 1.6.4 :tada:

The release is available on:

Your semantic-release bot :package::rocket: