1

Relevant (unanswered) questions I've asked on StackOverflow:

For reference, I am working in a legacy application, that also must work on browsers with JavaScript disabled, so between those two constraints the easier/better solutions to this problem are unavailable to me. However, I am trying to evaluate if the following solution provides adequate security (understanding that the user experience is less than ideal).

First the problem. This application involves filling out large web forms, which often take the users a significant amount of time. During this time, a user's session may expire and the user loses their work. A mechanism is needed to allow users to renew their session prior to submitting the form. See my linked questions for more detail.

The solution I have come up with, which I am questioning the security of, is as follows:

  1. The user logs in as normal and begins filling out a form
  2. Users without JavaScript see a message indicating when their session is set to expire, with a button that will open a login page in a new tab to allow them to renew their session before clicking the form's submit button.
  3. The user's session expires. Since the CSRF token is associated with the session, the CSRF token is now also invalid.
  4. The user clicks the "renew session" button, which opens a new tab with a login page. The user's now-invalid CSRF token is also forwarded to the login page.
  5. The client sends their username and password (along with the old invalid CSRF token in a hidden field) to the server.
  6. The server checks the username and password. If they are valid, the server re-associates that CSRF token with the user's new session, making the token valid again.
  7. The user submits their original form (which has the original CSRF token as a hidden field). The server sees that the CSRF token matches the one given by the client at login, and accepts the form submission.
  8. The server issues a new CSRF token to use on the next request.

Put more simply, if the server receives a string controlled by the client during the user's initial authentication, alongside the user's username and password, is it safe for the server to treat that string as a valid CSRF token for a subsequent request from that same client? Or is there a way that this could be exploited?

It seems to me that, considering the request which sets the CSRF token is only accepted if accompanied by a valid username and password, it should still prevent CSRF. After all, if an attacker has that information, they have already compromised security. However, there may be things I have not considered; hence this question.

1

2 Answers 2

0

After a lot of thought and research, I thought of an exploit to this method: evil.com hosts a phishing website with a form set to post an attacker-controlled "old" CSRF token to the session renewal endpoint. The user logs in like normal on the session renewal endpoint. Now the CSRF token has been set to an attacker-controlled value. Then the attacker can proceed with a normal CSRF attack. Basically, the attacker has to do not one but two CSRF attacks, but it's exploitable nonetheless.

So, bad idea, don't do this. For new code, obviously there would be no reason to do this anyway, there are better solutions. Jury is still out on what to do instead for my particular situation where those solutions would require basically re-writing thousands of forms, but regardless this is not the way.

1
  • If the attacker can get the victim to enter their credentials into a phishing page, CSRF is irrelevant and it really doesn't matter where the page would hypothetically send the credentials anyhow. The attacker can just log in as the victim directly! The credentials can be harvested via JS even if the user never actually clicks a button to submit them.
    – CBHacking
    Feb 4 at 7:14
0

This scheme is safe if the only way for the client to supply a CSRF token is in the same request as the user's credentials (username/password, or other authentication credentials that an attacker won't know and can't send directly). It's a complex and not very user-friendly flow, but it's not particularly unsafe.

The main risk is in how you send the CSRF token to the login page; if it's in the URL then that's reasonably likely to end up in server or proxy logs as well as browser history, and there are difficult but theoretically possible ways for a malicious third-party site or script to deduce it (though browsers try to prevent this). You can prevent this by sending the token some other way when requesting the login page, such as in a POST body or even a cookie (double-submit cookie pattern, except it sounds like you're storing the values on the server too).

You could also just change the way that the CSRF protection works, make it user-specific instead of session-specific. This is less secure, because if the session isn't expunged from the browser when the user logs off from a shared machine, a subsequent user could discover and steal the token and use it to CSRF the victim in the future. However, it should be possible (e.g. by not allowing the page to be cached) to prevent this.

There are also ways to make the whole process easier for the user. For example, you could allow the user to submit their credentials as they submit the form (have username/password fields on the form, which submit along with all the other user data), and automatically reauthenticate the user without worrying about CSRF protection if the credentials are valid; this is potentially obnoxious from a UX perspective but, frankly, less so than a site that times out while users fill in form data. You could also return rejected-because-session-expired form submissions to the user's browser when redirecting them to the login page, and propagate those values through until the user logs in again and gets redirected back to the form they were trying to submit, at which point you fill it in with what the user submitted before (assuming that the user who logs in again is the same one whose session expired before submitting; you don't want to carry that kind of data between users!).


It has to be asked: have you considered just not having a site that times out in the time it takes a user to fill out a form?! That kind of thing is the bane of web UX, and there's really no justification for it. If you absolutely must both verify "liveness" on such a short timeframe and can't count on JS, there are other options (split the form into small pieces that submit individually and each is short enough to avoid the problem, have an iframe with a meta refresh that demands the user click a CSRF-protected button to keep their session alive every X minutes, etc.) but also you should really reconsider your threat model.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .