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 issue exposed when saving the cookie on every request. #450
Conversation
Redirect routes were returning the same Result object every time and this object could be modified by the session or by filters. The fix is to make a copy of this Result on each request before it can be modified. Since setting the cookie on every request is prone to race conditions, dont do it in the default example and update the session docs to explain the issue.
@watsonmw Can you walk me thru a detailed example of of why "result.copy()" in FilterChainEnd fixes this issue? Something like |
@jjlauer If I understand correctly if you add this to Routes.java router.GET().route("/.").with(Results.redirect("/")); then the result is created only once when the route is being setup, each request that comes in gets the same result object. I added testRedirectResultNotShared() to test this, test might be a bit nosey of the the internals of FilterChainEnd, but it fails in the old codebase. |
I haven't had to dig into the filter chain much so please excuse my lack of On Fri, Jan 22, 2016 at 5:08 PM, Mark Watson notifications@github.com
|
Good point, I'll check around some more, to see if there's any routes that create instances on the fly. |
I guess the only way a copy would be unnecessary is if you don't ever add a header or cookie (or change anything else in a filter), which is the typical case to be fair. We could do copy on write semantics for Result, but sounds like something which should fall out of doing optimisation work. |
meaning the typical case is you dont need a copy, hence why this issue hasn't come up till now. |
Why not have a separate method like redirectWithNewResult(String) to use in this case? The existing codebase wouldn't change and no unnecessary copies would be created unless this new method is explicitly used. |
The problem is the issue can cause a web user to accidentally steal an others authentication token, if during a redirect cookies are saved. It's fairly easy for a web developer to get into this situation. I think by default ninja should try really hard to avoid this issue. Besides a special method to get around the problem is not needed as you can do it currently by specifying a controller method to do the redirect, like so:
|
I am a bit afraid using ninja-framework when reading that it could be that easy to "accidentally" run into such a major security issue as of "can cause a web user to accidentally steal an others authentication token". I did not read the whole previous posts in every detail. So my question is as follows: Does this described problem ONLY occur, if "Results.redirect("/")" is used in the "Routes.java" like the following line-of-code represents? router.GET().route("/.").with(Results.redirect("/")); Or can this problem generally occur, by setting cookie-data within a "ninja.Filter" (and NOT using this one line router.GET().route("/.").with(Results.redirect("/"));)? @watsonmw by saying that "It's fairly easy for a web developer to get into this situation.", could you please describe and summarize in short form what a web-developer "should-not-do" to not run into this problem. Thanks a lot. |
To run into this issue, both of the following must be true:
Then to reproduce, make a request that hits the redirect once as a logged in user and then in a logged out session. |
Something is strange here... I got the very same question as @jjlauer : "Redirect routes were returning the same Result object every time" does currently not make much sense to me. Redirect.results creates a new result... and the copy also makes no sense to me right now... That new result does not set any cookies... so nothing will change... Is there a testcase that tests against the bug? (Should not sound snappy :) I think it is very important to find the root cause of those seemingly strange issues - so good work :) ) |
Like a integration/Selenium test? I added a unit test above, and it does illustrate the issue I think. Can of course add an integration test if that would be useful. Anyway, here's the manual steps:
|
Another question is if the .with() method accepting a Result is a bad On Mon, Jan 25, 2016 at 11:09 AM, Mark Watson notifications@github.com
|
Is there another way to do redirection in Routes.java file ? router.GET().route("/.*").with(ApplicationController.class, "index"); is that it don't change the url path and you can have a "/wrongurl" that show the exact same result than / which is very bad for SEO etc.. I can understand that : router.GET().route("/.*").with(Results.redirect("/")); is a bad practice but Ninja actually allow this. Another thing that surprise me is that we are talking about Ninja which having, in a certain condition, a bug which grant user session to a random visitor. I mean it can't be more important security breach than that. This bug shouldn't be the most important thing to fix right now ? And the most important thing : there are peoples right now having Ninja in production and maybe having this use case occurred without even knowing it. I was expecting more implication in that issue from Ninja's creators / moderator. Fixing this should be the priority and a hot patch with new version release should be already released since i posted a test case to prove that actually there is a bug 7 days ago (cf : #416) |
Daniel, A very simple workaround that doesn't leave a weird url and avoid the issue router.GET().route("/.*").with(ApplicationController.class, "redirector"); In ApplicationController public Result redirector() { This is very much an edge case issue that we're happy to resolve as fast as -Joe On Tue, Jan 26, 2016 at 12:00 AM, Daniel SAWAN notifications@github.com
|
Mark & Raphael, I'm leaning more and more towards deprecating .with() in Routes.java. What do you guys think? -Joe On Tue, Jan 26, 2016 at 12:07 AM, Joe Lauer joe@lauer.bz wrote:
|
Thanks for this tips @jjlauer i will try this. This is only my point of view but you get the idea… I promise that I stop polluting this request anymore and let you work :) |
I agree the Result copy is definitely a short term solution, and deprecating is a good idea. I'm 50/50 on throwing an exception when initializating ninja .with(Result) routes, it's a bit unfriendly but could help with issues if a user is doing something funny with the result object. |
Alright, Let's take a middle road approach. Here's what I think: deprecate .with() accepting a Result object Won't break any existing apps, but should trigger enough warnings for now -Joe On Tue, Jan 26, 2016 at 1:16 AM, Mark Watson notifications@github.com
|
Hey... Sorry, I was a bit busy... I'll catch up with the discussion and the problem today fwiw... |
Wow. What a bug. Very good catch! Big compliments to everyone involved. It's very hard to find and isolate those kind of issues. Looking through the code I also can't see any good way how to make ..with(Result) work in a side-effect-free way. The obvious solution is to use Closures of Java 8 ()-> Result, later when we support Java8 officially... I think @jjlauer's approach sounds good. But if you want we can even take the shortcut and kill with(Result) immediately and intentionally break projects that use it. Better to break things as soon as possible than too late. |
I was digging into the code and I'm curious if there is a better way to It seems like the only reason Results cannot be reused is that The save() only seems to be there to write out the session and flash scopes I think Results could be reused then -- albeit carefully. We could also What do you guys think? On Thu, Jan 28, 2016 at 9:12 AM, Raphael A. Bauer notifications@github.com
|
That sounds good (and also why does the method return null when it should return ResponseStreams...). At the same time the root of the problem is not solved. If we eg at some point decide to establish global filters, then we run into the same issue. I'd favour deleting the with(Result) method and adding a testcase to make sure that issue does not resurface again... But I am open to suggestions... |
RouteBuilder.with(Result) style routes are now removed. If you are using any of these you will need to switch to a Controller route, like so: conf/Route.java: router.GET().route("/.*").with(ApplicationController.class, "redirector"); ApplicationController.java: public Result redirector() { return Results.redirect("/"); } Redirect routes were returning the same Result object every time and this object could be modified by the session or by filters. Also since setting the cookie on every request is prone to race conditions, dont do it in the default example and update the session docs to explain the issue.
Ok! I removed RouteBuilder.with(Result) style routes. I guess we need to add an explanation to the changelog.md or somewhere else for the breaking change? (Git really hates me tonight BTW, sorry about the crazy commit log) |
Looks good. There is a file called upgrade_guide.md where those breaking changes are described. |
Another take on the issue. Regarding closures. Well. We can simply define an interface
and use that like
That should work. Looks ugly. But will look less ugly in Java8 because it's a functional interface...
Just an idea. I think it's more important to kill the with(new Result) first... We can add the closure in another Pr... |
When this PR is finished and @jjlauer agrees I will cut a new release... (maybe even today) |
@raphaelbauer Based on the unit test I submitted with #460 on -- I believe the root cause is fixed by #459. I'd suggest doing a release as soon as you can and we can circle back on this PR next week. |
Yes. Will do that now. @ALL Joe fixed the issue by making sure that cookies are never written to the result, but only to the context. That way the session / flash cookies are never written to the single instance of the Result that caused trouble. Joe also added a unit test to make sure the bug is gone. We can therefore also keep with(Result result). I'll close this PR then. That does not mean that this PR was useless. Quite the contrary - it lead to a very good solution. Thanks to everyone involved. Great job! |
Mark -- kudos on digging into this. You rock! Daniel -- appreciate your On Fri, Jan 29, 2016 at 5:18 PM, Raphael A. Bauer notifications@github.com
|
No problem, glad to get to the bottom of it. I'll put together another pull request to cover the other changes to the docs and examples. |
Thanks for the bug fix :) I will test this asap ! |
Redirect routes were returning the same Result object every time and this
object could be modified by the session or by filters. The fix is to
make a copy of this Result on each request before it can be modified.
Since setting the cookie on every request is prone to race conditions,
dont do it in the default example and update the session docs to
explain the issue.
This pull request originates from this discussion:
#416