Tuesday, May 2, 2017

Is it safe to store my 'next' url in a signed cookie and redirect to it carefree?

Leave a Comment

I'm using Flask and it's occurred to me it could be a rather elegant solution to redirect back to the user's last page after login/logout by simply placing a session['next'] = request.url at each endpoint of my application and to just have my login/logout functions redirect right to session.get('next'). This is even similar to an option in the Flask-Login extension if you enable USE_SESSION_FOR_NEXT.

I would like to confirm this is a safe workflow but am not security-savvy to recognize if there are any ways to spoof the request.url or if I should still be validating the next url prior to redirecting, as is specified here:

http://flask.pocoo.org/snippets/62/

Is there a reason this method is not more commonly deployed? It seems like a nice, clean, easy solution that keeps URL's clean, minimizes for fields/processing, and removes a vulnerability to open redirect attacks if you are not taking the extra steps to validate the next url. What's the catch?

4 Answers

Answers 1

TL;DR

  1. Yes, it is safe.
  2. Don't worry about hiding the url, or preventing "spoofing". In reality, you can do neither, so you prepare for both.
  3. Potential bug: once you set the redirect on the session, the next login will follow it no matter what, until the session expires. A way to remedy this is just save the redirect path as a GET parameter instead (see bottom of answer).
  4. Here is a nice cheat sheet for redirect and forwarding security concerns.

This is a safe workflow, assuming your server authenticates incoming requests to all secure resources.

When you ask "if I should still be validating the next url prior to redirecting", the answer to that is no, but you need to validate all requests to that url (after redirecting) to make sure they are logged in.

In your question, it sounds like you are trying to keep the url hidden from the user until they are logged in. That shouldn't be necessary.

For example, let's say you have two urls:

url 1:  "/login"  # anyone can access this url 2:  "/secure_page"  # only a logged in user can access this 

Let's say a user is not logged in, and tries to navigate to yoursite.com/secure_page. They should be redirected to your login page every time. If they know the secure_page url, that shouldn't compromise your security at all. In fact, they must already know that url because they navigated to that page in the first place, so either they typed it in, clicked a link, or it was saved in a bookmark or a browsing history. The important thing is that when you handle requests to secure_page, you require them to be logged in.

You ask if they can "spoof" the url that they are redirected to. They cannot, when you save it in the session like that. However, they can hit any url they want after they log in, so it doesn't matter if they "spoof" that url.

So since they already know that url and they can hit any url they want after they log in, you don't need to keep that url secret from the user. The only advantage of doing that is an aesthetically cleaner url. This is why you see many login pages that look like this:

http://yoursite.com/login?next=secure_page 

What is happening there is they are saving that next url as an HTTP GET parameter. While it is less "clean", it is more explicit, so it has its pros and cons. And if they were to revisit the login page later, that redirect would no longer occur, which may be your desired behavior. With your current code, once that redirect is on the session, this next login after that will follow the redirect until the session expires, which could be after the user walks away and someone else uses that web browser.

Many sites do it the way I showed above. Django does it that way. In this case, a malicious link could provide the "next" url and have it redirect to some phishing site, but that could be easily prevented by ensuring the "next" url doesn't navigate away from your domain.

Here is a basic cheat sheet for redirect/forward security (you are redirecting, but others landing here may be considering forwarding requests).

Answers 2

To be honest, I can't think of any practical way to exploit this. Rather, I can't think of any practical way to exploit this, assuming you are properly validating incoming requests on other pages, and have properly implemented authentication and access control. That being said, I know nothing about your application, and there are certainly attackers out there a lot brighter than me, so take that with a grain of salt.

That being said, I don't see any reason why you shouldn't validate the URL, at the very least to make sure you're redirecting to a page on the same domain. The performance cost is trivial, and I'm of the opinion that anything related to the login/logout flow deserves an extra level of scrutiny. I would also make sure that you have an acceptable default case for when you receive a request without this field in the cookie.

Answers 3

As @brendan commented, this solution will work fine, use a @login_required decorator to protect the view.

Two cases:

Loggin in

You are at /index (non-protected view) >> login successfully to a @login_protected view, in this case, if you go back, as /index is accessible for everybody, you really do not care

Logging out

You are at /profile view (protected view) >> successful logout to a non-protected view, if you go back, the decorator will not allow you to enter the view

Answers 4

There is one small issue though. If you have multiple frames in your html there is an exploit and is called as clickjacking. You will need to set the following headers so that browser takes care of the remaining.

<X-Frame-Options','DENY'> 

Though it does not concern redirection but it can be used here. Be careful.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment