Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix AJAX login not refreshing CSRF token #13745

Open
wants to merge 1 commit into
base: 5.x
Choose a base branch
from

Conversation

nick-vanpraet
Copy link
Contributor

@nick-vanpraet nick-vanpraet commented May 13, 2024

Q A
Bug fix? (use the a.b branch) [ x ]
New feature/enhancement? (use the a.x branch) [ ]
Deprecations? [ ]
BC breaks? (use the c.x branch) [ ]
Automated tests included? [ x ]
Related user documentation PR URL mautic/mautic-documentation#...
Related developer documentation PR URL mautic/developer-documentation#...
Issue(s) addressed Partly fixes #13527

Description:

The current approach is to set the CSRF token in the HTML, which means that:

  1. If you log in using the AJAX embedded login form shown after you are logged out due to inactivity the CSRF token is not updated and you will see an error once you trigger any POSTs
  2. Any other tabs also do not receive the updated CSRF token and will see the same error once they trigger a POST.

This PR instead sets the CSRF token in a cookie, and the JS reads the value of that cookie and sets it as a header before making an AJAX POST call. We never read the cookie itself to actually validate any calls server side, as that would defeat the purpose of CSRF protection.

This ensures that the CSRF token available to the JS (in any tab) is always up to date as any request to the server ensures a correct value in the cookie (including a login request).

Important:

  • I'm not sure I put the cookie creation in the right spot. Ideally it only sets that cookie for HTML and Ajax requests, not for API calls but I couldn't find a decent spot that met those criteria.
  • Tests still need updating.
  • I haven't even fully tested every case myself manually.

Haven't found any edge cases so far, but if anyone can think of any please add them. I'm worried about generating a session for, say, leads being tracked. As far as I could find, all those tracking pages are public, but perhaps there is an exception somewhere.

Another option could be to use the INTERACTIVE_LOGIN event, which only fires upon successful login and to check for /s/ there as well which should limit it to only UI logins. I believe it even fires for remember me cookie logins. Unless there is a usecase where the csrf token has been invalidated somehow and needs refreshing but that event doesn't fire, then it would break not until a page refresh but until you log out and in, which seems a worse "worst case" than the current one.

Steps to test this PR:

Several ways to test this.

First, I will explain how to reproduce the error, which can be reproduce on any Mautic instance currently in the wild.

The quickest:

  1. Go to any running Mautic
  2. Open the site in several tabs
  3. Log out in one tab, immediately log back in.
  4. Go to one of your other tabs
  5. Sort something, or trigger a POST another way, such as saving a form.
  6. Receive a csrf error

The real world scenario (unpredictable due to garbage collection chance)

  1. Go to any running Mautic
  2. Open the site in several tabs
  3. Walk away for at least 24 minutes
  4. Come back, click a few links until you see an AJAX login form
  5. Log in
  6. Click around, all works
  7. Sort something, or save a form => csrf error
  8. Refresh that tab, try again => no csrf error
  9. Go to one of your other tabs
  10. Try the same, you will still see the csrf error until you also refresh that page.

Forcing garbage collection to force the real world scenario to occur sooner

  1. Spin up a Mautic locally (without this PR), install it if needed
  2. Add this to the top of index.php:
ini_set('session.gc_maxlifetime', 5); // or however many seconds you want.
session_start();
session_gc(); // forces garbage collection.
  1. Open the site in several tabs
  2. Wait 5 seconds
  3. Click 2 links. You will see the AJAX login form.
  4. Log in
  5. Click around, all works
  6. Sort something, or save a form => csrf error
  7. Refresh that tab, try again => no csrf error
  8. Go to one of your other tabs
  9. Try the same, you will still see the csrf error until you also refresh that page.

To test this PR,open this PR on Gitpod or pull down for testing locally (see docs on testing PRs here) and try any of the scenarios above. They should no longer occur.

@mallezie
Copy link
Contributor

First quick remark. The eventsubcriber should probably better live in https://github.com/mautic/mautic/blob/5.x/app/bundles/CoreBundle/EventListener/RequestSubscriber.php instead of Coresubscriber.

In that place there is already code for isAjaxPost and isSecurePath to leverage to not set for api calls, or add something similar there.

@escopecz escopecz added the WIP PR's that are not ready for review and are currently in progress label May 14, 2024
@abhisekmazumdar
Copy link

I get the following error over console when trying to edit an email template after applying these changes on my local.

image

I'm trying to resolve the issue. I'll share the outcome if I'm successful.

@nick-vanpraet
Copy link
Contributor Author

I pushed a fix, apparently forgot to remove some usages in GrapesJS. Also removed 2 definitions from the GrapesJS Demo folder. Not sure if it relied on those specific values, couldn't find anything at least.

@nick-vanpraet nick-vanpraet added bug Issues or PR's relating to bugs code-review-needed PR's that require a code review before merging and removed WIP PR's that are not ready for review and are currently in progress labels May 15, 2024
@abhisekmazumdar
Copy link

abhisekmazumdar commented May 16, 2024

Hi @nick-vanpraet

I followed the issue steps mentioned at #13527 and by following "The quickest" steps I was able to reproduce the issue.

But for the real world steps with Forcing garbage collection enabled in index.php. I never saw the AJAX login form but I see the "csrf error".

Now when I'm testing the PR again, I feel the sorting button is not functioning properly and doesn't work at my end when I click on the header titles.

Before:
eda8ac38c5117a900705f63aebc90bbc

After

3ee8c2942773ba27a686972123cf2e35

I'm using ddev at my local for setting up Mautic.

@nick-vanpraet
Copy link
Contributor Author

@abhisekmazumdar Hmm, did you check the "remember me" when logging in? That may bypass the need to login manually when your session expires, but will still invalidate the csrf token so you'd still see that error without this PR.

@nick-vanpraet
Copy link
Contributor Author

@abhisekmazumdar as for the seemingly broken JS, any errors in console that you can see or something else? I'll try and reproduce the issue locally as well.

@abhisekmazumdar
Copy link

@abhisekmazumdar as for the seemingly broken JS, any errors in console that you can see or something else? I'll try and reproduce the issue locally as well.

Oh, I forgot to mention that. I don't see any console error.

@nick-vanpraet
Copy link
Contributor Author

gitpod is acting up and doesn't want to start properly, but if I try it locally I don't see any malformed sorted lists. To be fair, it doesn't seem to sort anything at all. even when I just spin up 5.1 without this PR sorting by name or email or anything does not change the order in any way shape or form so it is possible it's broken in some way in 5.1 itself.

Try sorting emails instead, that one still seems to work as it should in 5.1

@nick-vanpraet
Copy link
Contributor Author

@abhisekmazumdar There is apparently a redesign in 5.x, that isn't in 5.1. I'm assuming your before is on 5.x, this PR is against 5.1 as it is a bugfix. That's why it looks different.

When you sort the list on 5.1, does the sorting work or does nothing happen?

@nick-vanpraet
Copy link
Contributor Author

@abhisekmazumdar disregard all the things, the 5.1 branch is not an RC it's a leftover from a long time ago. So I definitely made my PR against the wrong branch, I'll rebase and update.

@nick-vanpraet nick-vanpraet force-pushed the bugfix/csrf-token-not-updating-after-ajax-login branch from 81e71d1 to 86c77e7 Compare May 16, 2024 16:14
@nick-vanpraet nick-vanpraet changed the base branch from 5.1 to 5.x May 16, 2024 16:15
Copy link

codecov bot commented May 16, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 61.63%. Comparing base (4883fa0) to head (86c77e7).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##                5.x   #13745   +/-   ##
=========================================
  Coverage     61.62%   61.63%           
- Complexity    34145    34148    +3     
=========================================
  Files          2245     2245           
  Lines        102077   102090   +13     
=========================================
+ Hits          62909    62919   +10     
- Misses        39168    39171    +3     
Files Coverage Δ
...les/CoreBundle/EventListener/RequestSubscriber.php 100.00% <100.00%> (ø)

... and 3 files with indirect coverage changes

@abhisekmazumdar
Copy link

I can confirm now that I don't see no csrf error and after Log in I was able to save form and do sorting action without any error.

Copy link

@abhisekmazumdar abhisekmazumdar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have conducted functional testing locally and was able to confirm that I do not see any CSRF errors.

I have reviewed the code and do not see any unexpected issues.

) {
}

public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['validateCsrfTokenForAjaxPost', 0],
KernelEvents::REQUEST => ['validateCsrfTokenForAjaxPost', 0],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just nitpicking an extra space here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Issues or PR's relating to bugs code-review-needed PR's that require a code review before merging
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Invalid CSRF token when logging back in after being logged out due to inactivty
4 participants