Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to verify referrer inside a MVC or Web Api ajax call

my MVC app has common ajax methods (in web api and regular controller). I'd like to authorize these calls based on which area (view) of my app the call is coming from. The problem I am facing is how to verify the origin of the ajax call.

I realize that this is not easily possible since ajax calls are easy to spoof, but since I have full control of how the view gets rendered (full page source) perhaps there is a way to embed anti-forgery type tokens that could later be verified to a Url Referrer.

Authentication is already handled and I can safely verify the identity of the call, the only problem is verifying which URL (MVC route) the call came from. More specifically, preventing the user from being able to spoof the origin of the ajax call.

I tried creating a custom authorization header and passing it between view render and ajax calls, and that works, but still easy to spoof (since a user could sniff the headers from another part of the site and re-use those). In the end I am not sure how to safely verify that the header has not been spoofed. The only thing that comes to mind is encoding some info about the original context inside the token, and validating it somehow against incoming call context (the one that's passing the token in ajax call).

I see that MVC has AntiForgery token capabilities, but I am not sure if that can solve my problem. If so I'd like to know how it could be used to verify that /api/common/update was called from /home/index vs /user/setup (both of these calls are valid).

Again, i'd like a way to verify which page an ajax call is coming from, and user identity is not the issue.

update

as per @Sarathy recommended I tried implementing anti-forgery token. As far as I can tell this works by adding a hidden field with token on each page, and comparing it to a token set in a cookie. Here is my implementation of custom action filter attribute that does token validation:

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var req = filterContext.RequestContext.HttpContext.Request;
        var fToken = req.Headers["X-Request-Verification-Token"];
        var cookie = req.Cookies[AntiForgeryConfig.CookieName];
        var cToken = cookie != null
            ? cookie.Value
            : "null";

        log.Info("filter \ntoken:{0} \ncookie:{1}", fToken, cToken);
        AntiForgery.Validate(cToken, fToken);
        base.OnActionExecuting(filterContext);
    }

then my anti forgery additional data provider looks like this:

public class MyAntiForgeryProvider : IAntiForgeryAdditionalDataProvider
{
    public string GetAdditionalData(System.Web.HttpContextBase context)
    {
        var ad  = string.Format("{0}-{1}",context.Request.Url, new Random().Next(9999));
        log.Info("antiforgery AntiForgeryProvider.GetAdditionalData Request.AdditionalData: {0}", ad);
        log.Info("antiforgery AntiForgeryProvider.GetAdditionalData Request.UrlReferrer: {0}", context.Request.UrlReferrer);
        return ad;
    }

    public bool ValidateAdditionalData(System.Web.HttpContextBase context, string additionalData)
    {
        log.Info("antiforgery AntiForgeryProvider.ValidateAdditionalData Request.Url: {0}", context.Request.Url);
        log.Info("antiforgery AntiForgeryProvider.ValidateAdditionalData additionalData: {0}", additionalData);
        return true;
    }

this works, in that i can see correct pages logged in the provider, and anti forgery breaks w/out the tokens.

however, unless i did something wrong, this seems trivial to spoof. for example if i go to pageA and copy the token form pageB (just the form token, not even the cookie token), this still succeeds, and in my logs i see pageB while executing ajax method from pageA

confirmed that this is pretty easy to spoof.

I am using csrf to generate ajax tokens like this:

    public static string MyForgeryToken(this HtmlHelper htmlHelper)
    {
        var c = htmlHelper.ViewContext.RequestContext.HttpContext.Request.Cookies[AntiForgeryConfig.CookieName];
        string cookieToken, formToken;
        AntiForgery.GetTokens(c != null ? c.Value : null, out cookieToken, out formToken);
        return formToken;
    }

I then pass the form token back with each ajax call and have a custom actionfilterattribute where I read/validate it along with cookie token

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var req = filterContext.RequestContext.HttpContext.Request;
        var fToken = req.Headers[GlobalConstants.AntiForgeKey];
        var cookie = req.Cookies[AntiForgeryConfig.CookieName];
        var cToken = cookie != null
            ? cookie.Value
            : "null";


        log.Info("MyAntiForgeryAttribute.OnActionExecuting. \ntoken:{0} \ncookie:{1}", fToken, cToken);
        AntiForgery.Validate(cToken, fToken);

this all works (changing anything about the token throws correct exception), then in my IAntiForgeryAdditionalDataProvider I can see what it thinks it's processing.

as soon as i override the csrf token from another view, it thinks it's that view. I don't even have to tamper with the UrlReferrer to break this :/

one way this could work if i could force the cookie to be different on every page load

like image 781
Sonic Soul Avatar asked Feb 11 '15 23:02

Sonic Soul


3 Answers

I am assuming you can use IAntiForgeryAdditionalDataProvider for this.

public class CustomDataProvider : IAntiForgeryAdditionalDataProvider
{
   public string GetAdditionalData(HttpContextBase context)
   {
     // Return the current request url or build a route or create a hash from a set of items from the current context.
     return context.Request.Url.ToString();
    }

    public bool ValidateAdditionalData(HttpContextBase context, string additionalData)
    {
       // Check whether the allowed list contains additional data or delegate the validation to a separate component.
       return false;
     }
}

Register the provider in App_Start like below.

AntiForgeryConfig.AdditionalDataProvider = new CustomDataProvider();

https://msdn.microsoft.com/en-us/library/system.web.helpers.iantiforgeryadditionaldataprovider(v=vs.111).aspx

Hope this helps in your scenario.

like image 62
Parthasarathy Avatar answered Oct 20 '22 03:10

Parthasarathy


You mentioned in your question that you're looking for Anti-forgery token capabilities.

Hence, I think what you're asking about is an anti-CSRF solution (CSRF=cross site request forgery).

One way to do this is to render a true random number (a one-time token) into your page, then passing it on each request, which can be done by adding a key/value pair to the request header and then checked at the backend (i.e. inside your controller). This is a challenge-response approach.

As you mentioned, in the server-side code you can use

var fToken = req.Headers["X-Request-Verification-Token"];

to get it from the requesting page.

To pass it along from each client AJAX request of the page, you can use

var tokenValue = '6427083747'; // replace this by rendered random token
$(document).ajaxSend(function (event, jqxhr, settings) {
        jqxhr.setRequestHeader('X-Request-Verification-Token', tokenValue);
});

or you can set it for each request by using

var tokenValue = '2347893735'; // replace this by rendered random token
$.ajax({
    url: 'foo/bar',
    headers: { 'X-Request-Verification-Token': tokenValue }
});

Note that tokenValue needs to contain the random number which was rendered by the web server when the web page was sent to the client.

I would not use cookies for this, because cookies don't protect you against CSRF - you need to ensure that the page, which is requesting is the same as the page which was rendered (and hence created by the web server). A page being on a different tab in the same browser window could use the cookie as well.

Details can be found on the OWASP project page, in the OWASP CSRF prevention cheat sheet.

like image 38
Matt Avatar answered Oct 20 '22 04:10

Matt


My quick interim solution was to use custom tokens created on each page load (guid which i keep track of in my token cache), which are passed as headers in all ajax calls. Additionally i create a original url hash and combine it into the custom auth token. in my ajax methods I then extract the hash and compare it with UrlReferrer hash to ensure that hasn't been tampered with. since the custom token is always different it's less obvious to guess what's going on as token appears to be different on every page load. however this is not secure because with enough effort the url hash can be uncovered. The exposure is somewhat limited because user identity is not the problem so worst case is a given user would gain write access to another section of the site but only as himself. My site is internal and i am auditing every move so any temper attempts would be caught quickly.

I am using both jQuery and angular so appending tokens with all requests like this:

var __key = '@Html.GetHeaderKey()' //helper method to get key from http header
//jQuery
$.ajaxSetup({
    beforeSend: function (xhr, settings) {
    xhr.setRequestHeader('X-Nothing-To-See-Here', __key); // totally inconspicuous 
})

//angular
app.config(['$httpProvider', function ($httpProvider) {
  $httpProvider.defaults.headers.common['X-Nothing-To-See-Here'] = __key;
});

update

the downside of this approach is that custom tokens need to be persisted across a web farm or app restarts. Based on @Sarathy's idea I am trying to side step this by leveraging MVC anti forgery framework. Basically add/remove my "salt" and let the framework manage the actual token validation. That way it's a bit less to manage for me. Will post more details once i verify that this is working.

like image 1
Sonic Soul Avatar answered Oct 20 '22 04:10

Sonic Soul