Morgbn / nuxt-csurf

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

Doesn't work on serverless #41

Open moshetanzer opened 1 month ago

moshetanzer commented 1 month 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 1 month ago

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

bojanrajh commented 1 month 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 1 month 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 1 month 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 1 month ago

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

ccwebgroup commented 1 month ago

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

Barbapapazes commented 2 weeks 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 weeks 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 weeks 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 weeks ago

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

Barbapapazes commented 2 weeks 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 weeks 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 4 days 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 4 days ago

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

atinux commented 4 days 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