I just wanted to gauge opinions on how I should approach this problem and ultimately looking for a quick win (wrong way to think about things nut time pressures mean I have to think and act quickly!
I've been given a website that has a bit of an issue.
I login using standard forms authentication as User1234 and my url is as follows:
www.mywebsite.co.uk/1234/Contact.
This will take me to User1234's details.
You can put two and two together and correctly assume that 1234 is a user id of some sort.
Once authenticated, I can access the views with [Authorize] attribute present, any anonymous/unathenticated users get redirected.
However, once logged in as User1234, I can then tinker with the url like so:
www.mywebsite.co.uk/1235/Contact.
So I am authenticated as User1234 but can see User1235's data. This is BAD for obvious reasons.
When I log in, I actively set the login ID in session so in theory, I could do a check whenever a user hits an ActionResult, I could cross check the ID present in the URL against the session login ID. However, it is a rather project with lots of action results and as such, I'm reluctant to spend my Saturday afternoon adding something to each and every ActionResult.
Is there an event in the global.asax I could use that is hit on each ActionResult request where I can compare Session login ID with url ID?
Alternatively, can anyone offer some suggestions about how I can achieve this or restrict URL tampering?
You can try and do a base controller
public class BaseController : Controller
{
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Do your stuff here
base.OnActionExecuted(filterContext);
}
}
I assume that you don't want to change your URL routes, as you could retrieve the user id also from the session. A quick solution would be to use an ActionFilter which you can place on the affected controllers or action methods:
public class VerifyUserIdAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var sessionUserId = filterContext.HttpContext.Session["UserId"];
var routeUserId = filterContext.RouteData.Values["UserId"];
if (routeUserId != null && sessionUserId == routeUserId)
filterContext.Result = new RedirectResult("<<url to redirect to>>");
}
}
I don't understand why the URL contains a data entry point. This appears to be a design flaw. I would remove all code that uses a URL parameter and instead make sure the controller looks up what the ID is based on the logged in user.
Related
I'm trying to figure out how to do this and I can't seem to arrive at an idea to try.
I have already created a filter to redirect authenticated requests, so an authenticated user cannot revisit the login page for instance. That's all fine and dandy.
However there is one role in the system that only has access to a single area within the application, any requests to other controllers in the main area or other areas should redirect the user to this area.
I don't just want to show an unauthorised message to these users if they attempt to view anything outside of their area I would prefer to just redirect them back to their area.
what are the different ways to achieve this goal (please include pros and cons)?
EDIT
Just to be clear about this the reason I'm looking to do this is mop up potential edge cases where a user of a particular role enters a url from browser history or manually which coule take them to another area of the application they shouldn't be accessing.
For the default area of the application where the base role is User it is sufficient to just use the basic AuthorizeAttribute which just ensures requests are authorized (as most users will be granted a user role on registration.
For other areas there are specific roles which will correctly show a 401 unarthorized page to a user without the given role.
Where in this special case a certain type of user is created with a specific role and NOT provided with the basic User role but at the same time their requests will be deemed as Authorized.
Probably the easiest and most effective way is to create a custom AuthorizationAttribute that is specific to your needs:
public class ThatSpecificRoleAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// not sure what is EXACTLY needed so here is all the stuff I
// think someone might need
var rd = httpContext.Request.RequestContext.RouteData;
string currentAction = rd.GetRequiredString("action");
string currentController = rd.GetRequiredString("controller");
string currentArea = rd.Values["area"] as string;
if (httpContext.User != null
&& httpContext.User.Identity != null
&& httpContext.User.Identity.IsAuthenticated
&& httpContext.User.IsInRole("ThatSpecificRole")
&& currentController != "Home")
{
// alternatively you could also...
// httpContext.Controller.TempData["ThatSpecificRoleError401"] = true;
return false;
}
return true
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToAction("Index", Home);
}
}
Then register it globally (making sure it is the first auth attribute added):
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ThatSpecificRoleAttribute());
}
}
I know someone else already mentioned something like this, but this is very specific (and multiple authorization attributes are supported by MVC, fyi).
Pros:
You don't need to worry if someone added this to controllers or methods, it is global
It does only what you need, everyone else (anonymous and people without the role) use the system normally.
This will redirect them, and you could see if the TempData exists and give them a nice message letting them know they were redirected (option).
Cons:
Every request will check this authorization. It should be cached so the performance hit is very minimal but still exists.
I've had a similar problem, and I ended up using a little hack.
When you return a HttpUnauthorized result, the user is automatically redirected to the Login-page, defined in the Web.config (in the section
However, in stead of redirecting to the login-page, I made a redirect to an different action. In this action, I check if the user is actually logged in. If so, I check what area he can view, and redirect the user to that area. If no area is found, or if the user is not logged in at all, I manually redirect to the login-page.
Hope this helps!
Background:
MVC3 intranet application using windows authentication. After windows authentication completes, a HttpModule looks up the user's network id from an HR database and returns the user's employee information and sets it in HttpContext.Items. I have a base controller that looks for this information and sets a ViewBag property by overriding OnActionExecuting.
My question is that this HttpContext.Items["UserInfo"] information only seems to be available on Home/Index only and not available when I click to Home/About or Home/Help although HomeController inherits BaseController. Can anyone shed light on why this is happening?
protected override void OnActionExecuting(ActionExecutingContext ctx)
{
if (this.HttpContext.Items["UserInfo"] != null)
{
UserInfo User = (UserInfo)this.HttpContext.Items["UserInfo"];
ViewBag.CurrentUser = User;
}
base.OnActionExecuting(ctx);
}
HttpContext.Items is per request only; it is not retained when you redirect to another view or even post back within the current view. So you need to use Session or something else to persist it.
I´m have a Car View with a list of car... So, I have a Create button that opens a Modal (UI JQuery Dialog) with Site/Car/Create content...
All works fine... But I´d like to block direct access to : Site/Car/Create...
Is that possible? How?
Thanks
It's not really possible to block it completely, but you can do some things to make it more difficult. First, require that it come from a POST request. That will prevent someone from simply entering the URL with request parameters. Second, use the antiforgery token helper. That will help prevent a third-party from doing a POST to the url since they will also require both the token input and the token cookie. Third, you could potentially check if the request has the X-HTTP-REQUESTED-WITH header and only do the POST (or GET) via AJAX. It's not that hard to get around but it would prevent an accidental access if you do use GET. Fourth, and it probably should have been first, make sure that only authorized users have access to the action using the AuthorizeAttribute. Fifth, use SSL to prevent unauthorized access using FireSheep and protect your cookies and data from snooping.
In short, you won't be able to prevent a determined person with legitimate authorization from crafting a request to the action if they a really want to without using your interface. They can always craft a request that will look exactly like the one you would send. You can make it more difficult and prevent accidental access, though, using the above methods.
[AttributeUsage(AttributeTargets.Method)]
public class AjaxOnlyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 404;
filterContext.Result = new HttpNotFoundResult();
}
else
{
base.OnActionExecuting(filterContext);
}
}
}
And then stick on your Create action like this:
[AjaxOnly]
public ActionResult Create()
{
...etc
}
I realise that I can prevent unauthenticated users from accessing views at controller level by applying the [Authorize] attribute and can also filter views down to individual users or roles using this. However, my question is regarding doing the opposite... Is there a way to deny authenticated users from certain views without having to manually add in checks to see if they're authenticated in the opening lines of the controller code? Ideally an [Unauthorized] attribute or an equivalent if such a thing exists?
The reason for this is that I don't want authenticated users to be able to visit the account creation pages of the site I'm working on, as well as other resources. I realise I could check them in the controller explicitly but I'd prefer to decorate the controller methods if at all possible.
Thanks :)
This is along the lines of what LukLed was referring to:
public class UnAuthorizedAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
bool excludeCondition = false;
if (excludeCondition)
filterContext.Result = new HttpUnauthorizedResult();
else
base.OnAuthorization(filterContext);
}
}
Simply put in the logic for your excludeCondition. You can also to choose to do things like redirect to other views. Just mark your code with [UnAuthorized]
You can write your own authorization filter. Inherit from FilterAttribute and implement IAuthorizationFilter. Call it UnauthorizedAttibute and you will be able to use it like [Authorize].
Hear You can read about filters:
http://www.asp.net/LEARN/mvc/tutorial-14-cs.aspx
A simple way to accomplish this? Just leave the action untagged, and start with:
If(Request.IsAuthenticated)
// redirect somewhere, or return another view...
this could also be accomplished fairly simply if you are already using a roleprovider. then your actions would just need to be filtered by the appropriate role:
[Authorize(Roles = "Admin, Editor")]
This seems like a pretty stupid question, but I'm trying to figure out the best way to do this. Would you simply redirect to a /Logout page and have the controller call the FormsAuthentication.SignOut function?
That was my first thought, but then I wondered if it could be abused by third party websites. Let's say someone just decides to post a link to your /Logout page. The user would get signed out of your application. Is there a good way to prevent that?
If you are concerned about a user getting accidentally logged out of you application through the use of a malicious link, you can check the Referrer to make sure that the logout is coming from your site (or is NULL in the case where the user simply types the URL in).
I actually don't worry about this since logging someone out is annoying but not necessarily a security risk.
Such a malicious link would be an example of a class of security vulnerabilities known as cross site request forgery, CSRF. A logout link is relatively harmless, but a remote site could set up a number of hidden forms and post them to your site to perform any action possible through POST.
The most common counter-measure is to include a challenge, a random hidden value in each form, and then check for that value. Checking the referer header could work, but note that some browsers don't send referer at all.
Read more: http://en.wikipedia.org/wiki/Cross-site_request_forgery
This is an old question, but here is a modern example with MVC:
[Authorize]
public RedirectResult Logout()
{
FormsAuthentication.SignOut();
return this.Redirect("/");
}
You can ensure that the Logout action is only able to be called by somebody who is logged in by applying the Authorize attribute to it.
This is what I use.
public ActionResult Logout()
{
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
Seems to work fine.
Third party websites are only going to log themselves out. So they wouldn't be achieving anything different from actually clicking Logout.
The new ASP.net MVC Beta contains an AccountController, which may be worth looking at, as it essentially implements everything from Registration to Login/Logout to Forgot Password functionality. Not sure how good it is, but a good starting Point for sure.
Derive from ActionResult
public class LogoutResult : ActionResult
{
private readonly IAuthenticationService _authenticationService;
private readonly IWebContext _context;
public LogoutResult(IAuthenticationService authenticationService, IWebContext context)
{
_authenticationService = authenticationService;
_context = context;
}
public override void ExecuteResult(ControllerContext context)
{
_authenticationService.Logout();
_context.Abandon();
_context.Redirect("~/");
}
}
You should look for a cookie or something that identifies the client as the true user.