Cookieless sessions and cross-site form posts - asp.net-mvc

Here's the situation...
Site 1) ASP.NET MVC application for customers to login, view and pay bills, etc. This site is using cookieless ASP sessions to allow multiple sessions to run in tabbed browsers.
Site 2) Corporate web site with standard static content. This will have a small form at the top of each page with a username/password form that will post to the MVC application.
When posting to the MVC app, a new session is being generated and the site is returning a 302 redirect with the session ID in the URL (as expected). The controller has two Login methods, one for handling a GET, one for handling a POST. Because of the redirect, it's hitting the GET method and it loses the form values.
By changing the method on the corporate site to do a form GET rather than a POST, the username and password are preserved in the query string after the redirect and I could handle the requests that way, but I would rather do a POST and not pass that data around in the URL.
My gut says that implementing some sort of custom HttpHandler would allow me to do this, but I'm not sure where I'd be able to tie into the session creation. A breakpoint at Session_Start in global.asax shows that the session ID has already been crated and the redirect already happened at that point.

I believe I found where the redirect was happening as well as a possible solution to it. The ISessionIDManager interface has a SaveSessionID method with an "out bool redirected" parameter on it. It appears that when a new session is created, the default session manager rewrites the URL inside this method and does a redirect. I played around with implementing my own ISessionIDManager and I was able to suppress the redirect, but there's no way (that I could tell) of inserting the session ID into the URL for the cookieless session without doing the redirect. Server.Transfer is not allowed at that point in the request lifecycle. I tried that but received an "Error Executing Child Request" message.
Later on, I found this article on Enabling POST in cookieless ASP.NET Applications. Basically by tying in to the Application_EndRequest event, you can detect the redirect, clear the response and fake a form post to the new URL that contains the session ID.
void MvcApplication_EndRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
if (application.Request.Form["CorpLogin"] == "1"
&& application.Response.RedirectLocation != null
&& application.Response.IsRequestBeingRedirected)
{
StringBuilder build = new StringBuilder();
build.Append("<html>\n<body>\n<form name='Redirect' method='post' action='");
build.Append(application.Response.RedirectLocation);
build.Append("' id='Redirect' >");
foreach (string key in application.Request.Form)
{
if (key != "CorpLogin")
build.Append(string.Format("\n<input type='hidden' name='{0}' value = '{1}'>", (string)key, application.Request.Form[(string)key]));
}
build.Append("\n<noscript><h2>Object moved <input type='submit' value='here'></h2></noscript>");
build.Append(#"</form>
<script language='javascript'>
<!--
document.Redirect.submit();
// -->
</script>
");
build.Append("</body></html>");
application.Response.Clear();
application.Response.ClearHeaders();
application.Response.Output.Flush();
application.Response.Write(build.ToString());
}
}
This isn't an ideal solution IMHO but it might be workable enough to meet the requirements.

When the MVC site issues the redirect are you handling that yourself with a controller or is it being routed automatically by your membership provider?
If you can intercept the request before the redirect you could place the username and password in the TempData variable and process it after the redirect.

You're looking for a Server.Transfer equivalent in MVC (see Simon's answer.)
It's like Request.Redirect but simply transfers the .NET processing to another page. It also allows for maintaining the form variables even on POST between transfers (in this case a RouteValueDictionary).
EDIT: (response to your comment #1)
Here is an example of Forms Authentication with MVC. Without knowing the internals of your authorization mechanism, I'm not entirely sure. At any point of the "DoLogin" process, you would be able to do a transfer instead of a redirect.

Related

This webpage has a redirect loop in Global.asax

The Goal is to redirect all users to my custom Security Page on each httpRequest as long as the Session variable is null or empty.
In Global.asax
The method causes an error as follows:
This webpage has a redirect loop
The webpage has resulted in too many redirects. Clearing your cookies for this site or allowing third-party cookies may fix the problem. If not, it is possibly a server configuration issue and not a problem with your computer.
protected void Application_PostAuthorizeRequest()
{
if ((Session["SecurityCodeApproved"] == null || !(bool)Session["SecurityCodeApproved"]))
{
Response.RedirectToRoute("Security");
}
}
It looks like you are redirecting to the same page, checking if the session cookie is set, and if not, redirecting it again. Its going to continuously redirect itself, until a session cookie is set, and you aren't setting one. You need to either redirect elsewhere or set the cookie to break the loop.
Do not do this in your Global.asax because as you have noticed, you will not always have a session every time that event fires. Also your code will fire on ALL requests, including those for resources like css, images, and JavaScript. What you want to do is use a Global Filter Attribute to perform your logic or if you are using WebForms, you want to do this at the Page Level.
When Filters are executed you are garaunteed a FilterContext that will have the request and the session objects you need.
You can then redirect to your security page, but make sure your filter attribute is ignored on your security point.

ASP.Net MVC: Check if URL is Authorized

I'd like to simply check from a Controller whether another URL is authorized.
So for example, I'd like to call into a Controller like so:
[HttpPost]
public ActionResult IsUrlAuthorized(string url)
{
bool isAuthorized = // What do I put here?
return Json(isAuthorized);
}
So I'd like to know what I could call to check on whether the current user is authorized for the passed-in URL or not. I'm guessing the answer has something to do with Routes, which sit a little bit outside MVC?
This is a somewhat similar question but not quite the same thing:
ASP.NET MVC. Check if user is authorized from JavaScript
Since the user may or may not be authorized in general, but may not have the right permissions or role assignments to see a specific URL.
Ideas?
Update: I use standard MVC authorization attributes to lock down my app, so I'll just give an example of what that looks like here. In MVC Routes map to Controllers. A single method on a Controller can be restricted to one or more Roles:
public class HomeController : Controller
{
[Authorize(Roles = "User, Moderator")]
public ActionResult ListRecentPosts()
{
. . .
}
}
Or, an entire Controller can be restricted to one or more roles:
[Authorize(Roles = "Admin")]
public class AdminController : Controller
. . .
The actual URL that any of these controller methods responds to is based on a default mapping in a standard MVC app:
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
But, you can be nice to your users and make URLs guessable by adding a lot more Routes - as a result, a Controller method can have many names that point to it. You can't just assume and infer the controller name from the URL (even if it maps out that way for half the URLs in the site).
So presumably I either need a way to ask the Routing engine directly whether a URL is authorized for the current user, or a 2-step of asking the Routing engine for which Controller and Method, then ask if those are authorized - hopefully not by using Reflection and matching Roles directly as that again would appear to assume too much.
Update 2: The way this came up is I have an Account strip at the top of my app. Its state can change by selecting one of several accounts you're authorized as. Depending on where you are in the app, the account you chose might have authorization to view this page - and you might be in the middle of filling out a form you don't want to lose. So the naive approach - just refresh when they pick another account - is harmful, and a waste of the user's time even if there is no form and they're just reading a page that's all text.
While that convenience to the user is nice, the user is going to fairly assume that pages they can't see as a user who shouldn't have permission really are denied (and, it would be harmful to leave them on a page that's forbidden - actions taken from it will fail). So I need to know whether to redirect away based on their new permissions.
One of the things I love about .Net is the way many of its best libraries decompose so well, so you can easily recompose things that are part of its normal functionality, or a new twist. Both the Routing module and MVC appear to be very well constructed, so I have to suspect this can be done.
The cheap hack is to ensure that my authorization module returns a consistent redirect status code when a user isn't authorized, and when the user changes their account in the account strip, fire 2 AJAX calls: One to change account, and then a second to the current page over AJAX just to check the HTTP Status Code. 200 OK means leave the page as is, Redirect means follow the redirect. Obviously this is a little ugly, involves an extra HTTP call, creates a false hit in the logs, and makes an assumption about how authorization is handled across the app.
There could be a secondary concern - the page might be authorized, but just change how it works or looks. This particular app has no change in look based on account (besides the account strip itself), and I can handle functionality changes by just providing a custom event that forms listen to - they can reload any relevant data from the server in response to it.
Using UrlAuthorization.CheckUrlAccessForPrincipal only works if you're only using URL authorization. But for MVC using Routing, we highly recommend that you don't use URL authorization to secure an app.
Instead, we recommend using Authorization attributes on the controller class. The reason is there could be multiple URLs that call the same controller action. It's always better to secure the resource at the the resource and not just at the entry ways.
In this particular case, you'd have to get an instance of the controller given the URL. THat's a little tricky as you'll basically have to run the MVC pipeline from the point where you have the URL to the point where you have the controller. It's possible, but seems heavyweight.
I wonder if there isn't a better and simpler way to accomplish your goals. What is it you're really trying to do?
UPDATE: Based on your scenario, it sounds like this is an initial check just for UI purposes. Perhaps all you need to do is make an asynchronous Ajax request to the URL and check the HTTP Status code. If it's a 401 status code, you know the user is not authorized. That seems like the safest bet.
How about UrlAuthorizationModule.CheckUrlAccessForPrincipal method.
UrlAuthorizationModule.CheckUrlAccessForPrincipal Method (System.Web.Security)

Best way of passing session between 2 sub-domains with MVC

During the buy process of my ecommerce site that I'm developing in MVC I need to pass the "cart" session to a different subdomain (e.g. http : //www.abc.com to https : //secure.abc.com).
I guess I have to perform the same trick as with webforms where I save all the variables associated with the session to a database, then pass the ID of the database record to the secured subdomain and reload the session using the id supplied.
However with MVC I have a couple of options (I think):
1) Render a form in the non-secured page that posts an ID to the controller (but call the controller using an hard-coded absolute URL (e.g. https://secure.abc.com/nextstep
2) Post back the ID to my non-secured controller and then have the controller return a view that is secured (is that possible).
Is there a better way?
Even an easier way if you make sure it won't affect the security, is to allow the cookie to be shared on the domain, instead of the subdomain only. I come from a PHP background, wouldn't know how to do this in .net
I did it through cookies in order to solve the sub domian problem
You can save your data into cookies something like this:
HttpCookie cookies = new HttpCookie("Name");
cookies["data"] = yourdata;
cookies.Domian = ".abc.com"
Then your data info can be shared across sub domains

How to avoid open-redirect vulnerability and safely redirect on successful login (HINT: ASP.NET MVC 2 default code is vulnerable)

Normally, when a site requires that you are logged in before you can access a certain page, you are taken to the login screen and after successfully authenticating yourself, you are redirected back to the originally requested page. This is great for usability - but without careful scrutiny, this feature can easily become an open redirect vulnerability.
Sadly, for an example of this vulnerability, look no further than the default LogOn action provided by ASP.NET MVC 2:
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid) {
if (MembershipService.ValidateUser(model.UserName, model.Password)) {
FormsService.SignIn(model.UserName, model.RememberMe);
if (!String.IsNullOrEmpty(returnUrl)) {
return Redirect(returnUrl); // open redirect vulnerability HERE
} else {
return RedirectToAction("Index", "Home");
}
} else {
ModelState.AddModelError("", "User name or password incorrect...");
}
}
return View(model);
}
If a user is successfully authenticated, they are redirected to "returnUrl" (if it was provided via the login form submission).
Here is a simple example attack (one of many, actually) that exploits this vulnerability:
Attacker, pretending to be victim's bank, sends an email to victim containing a link, like this: http://www.mybank.com/logon?returnUrl=http://www.badsite.com
Having been taught to verify the ENTIRE domain name (e.g., google.com = GOOD, google.com.as31x.example.com = BAD), the victim knows the link is OK - there isn't any tricky sub-domain phishing going on.
The victim clicks the link, sees their actual familiar banking website and is asked to logon
Victim logs on and is subsequently redirected to http://www.badsite.com which is made to look exactly like victim's bank's website, so victim doesn't know he is now on a different site.
http://www.badsite.com says something like "We need to update our records - please type in some extremely personal information below: [ssn], [address], [phone number], etc."
Victim, still thinking he is on his banking website, falls for the ploy and provides attacker with the information
Any ideas on how to maintain this redirect-on-successful-login functionality yet avoid the open-redirect vulnerability?
I'm leaning toward the option of splitting the "returnUrl" parameter into controller/action parts and use "RedirectToRouteResult" instead of simply "Redirect". Does this approach open any new vulnerabilities?
Update
By limiting myself to controller/action routes, I can't redirect to custom routes (e.g. /backend/calendar/2010/05/21). I know that by passing more parameters to the LogOn action, I could get it to work, but I feel like I'll always be revisiting this method -- keeping it up to date with our routing scheme. So, instead of splitting the returnUrl into its controller/action parts, I am keeping returnUrl as-is and parsing it to make sure it contains only a relative path (e.g., /users/1) and not an absolute path (e.g., http://www.badsite.com/users/1). Here is the code I'm using:
private static bool CheckRedirect(string url) {
try {
new Uri(url, UriKind.Relative);
}
catch (UriFormatException e) {
return false;
}
return true;
}
Side note: I know this open-redirect may not seem to be a big deal compared to the likes of XSS and CSRF, but us developers are the only thing protecting our customers from the bad guys - anything we can do to make the bad guys' job harder is a win in my book.
Thanks, Brad
Jon Galloway wrote up an article with a solution for MVC 2 (and 1).
Here's the snippet that should help with your issue:
SECURED (original article updated 2014)
private bool IsLocalUrl(string url)
{
return System.Web.WebPages.RequestExtensions.IsUrlLocalToHost(
RequestContext.HttpContext.Request, url);
}
Yes this is a vulnerability. Before redirecting you need to inspect the returnUrl string parameter by passing it to a Uri object and make sure that the target domain is the same as the requesting domain. You should also take into account the case when returnUrl is a relative address like /admin. No problem in this case as the redirect will be to the same application.
You could always keep a record of the previous page with TempData when the user is not authenticated and use that to redirect to the previous page instead of a url parameter.
As long as you use one of the variants of Redirect that uses controller and action parameters or a route name, you should be alright, provided you have adequate security controls on your controller methods.
The concept being, whatever you use for your redirect must go through the routing engine and be validated by matching a route.
But I suspect that the real vulnerability is Cross-Site Scripting. Unless your malicious user can inject some Javascript into the page, they have no way of manipulating the return Url, or any of its parameters (since you otherwise control all of the server and browser code).

ASP.NET MVC Authorization and hyperlinks

I am using successfully custom authorization in ASP.NET MVC. It simply involves a comparison between User.Identity and the owner of the object in context.
It works like a charm when used in simple conditions. It becomes more complicated when I try to call 2 actions in one web request.
Lets say I want to display an image which would be generated on-the-fly by my application. This image is generated by a controller, thus, it can be referenced by an URL even if it doesn't exist physically. I have decided that the user must be signed in and be the owner to view it, so I apply my authorization mechanizm to it.
Example: <img src="http://myapplication.com/images/generate/3" />
When I include such an image in a page via its action hyperlink, I expect that the authenticated user will still be in context on server side when the image is generating. This is not the case in my tests. The image never displays because my authorization check doesn't work. In the image controller, User.Identity is empty as if the user has not signed it.
In the meantime, the same user is still signed in to the website and can continue to browse with his identity in context... without those images working properly.
I wonder how to make this process work securely...
Thank you very much!
Marc Lacoursiere
RooSoft Computing inc.
Just wondering if you've checked if
Thread.CurrentPrincipal
is also empty in the controller? It should contain the same value.
Another suggestion would be to store the User.Identity value in a session?
You need to set up your identity in global.asax on every request. I'm using a custom Principal and Identity for this.
private void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (!Request.IsAuthenticated)
{
SetIdentity(new MyIdentity
{ Type = UserType.Inactive, Id = int.MinValue });
}
else
{
HttpCookie authCookie = Request.Cookies[
FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket =
FormsAuthentication.Decrypt(authCookie.Value);
var identity = Repository.GetIdentity
(authTicket.Name, new HttpRequestWrapper(Request));
SetIdentity(identity);
}
}
}
private void SetIdentity(MyIdentity identity)
{
Context.User = new MyPrincipal { Identity = identity };
Thread.CurrentPrincipal = Context.User;
}
This works, but I don't guarantee it to be secure. You should review this article on FormsAuthentication vulnerabilities prior to going live with this code. You have to understand that this code assumes the cookie is valid and hasn't been hijacked. There are some additional security measures that can be taken to reduce these vulnerabilities which this code doesn't show.
This may be when the site link in browser is http:\www.mysite.com (or http:\subdomain.mysite.com ) and you are using http:\mysite.com\image\5 in your application. Form authentication uses cookies. And these cookies may belong to domains and subdomains.
To find out what is going on I suggest to use FireFox with FireBug installed. Enable Net and Console tab for your site and make a complete refresh of the page. After you'll see requests in one of these tabs (Net tab exactly). At the left of the request you can see 'a plus' button, after you click it you'll see Headers and Response tabs (more detailed description of firebug). Have a look at Headers tab and try to find something like FORMAUTH (or what you've set in config as a forms cookie name). If you not see it - the problem is in domains.

Resources