Anti-Forgery Token + Web API (- MVC)

Как использовать Anti-Forgery Token с ASP.NET Web API без ASP.NET MVC?

У Стивена Вальтера есть статья "Предотвращение подделок подпроса с помощью ASP.NET MVC" в http://stephenwalther.com/archive/2013/03/05/security-issues-with-single-page-apps... но его решение включает MVC/Бритва и в моем интерфейсе я не планирую включать его. И есть такие статьи изобилия, которые решение добавляет @Html.AntiForgeryToken(), но это не может быть моим решением.

Позже я решил еще один вопрос: "Политика одного и того же происхождения": http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api. Может ли это быть решением для предотвращения CSRF? Я так не думаю.

Ответ 1

Моя проблема заключалась в том, что я не хотел использовать MVC и просто обслуживал статические html файлы, поддерживаемые WebApi. Вот что я сделал (будет ли это работать?) Создайте модуль Http, который устанавливает случайное значение cookie при обслуживании любого статического файла. Например:

public class XSRFModule : IHttpModule {
    ...

    void context_EndRequest(object sender, EventArgs e) {
        if (Path.GetExtension(HttpContext.Current.Request.Path) == ".html") {
            HttpContext.Current.Response.Cookies.Add(new HttpCookie("XSRF-TOKEN", Guid.NewGuid().ToString()));
        }
    }
}

Затем на странице html используйте javascript для добавления значения cookie в заголовок при вызове api:

function callApi() {
        xhr = new XMLHttpRequest();
        xhr.open("GET", "api/data", true);
        var regex = /\b(?:XSRF-TOKEN=)(.*?)(?=\s|$)/
        var match = regex.exec(document.cookie);
        xhr.setRequestHeader("X-XSRF-TOKEN", match[1]);
        xhr.send();
    }

Наконец, в HttpModule убедитесь, что cookie соответствует заголовку перед обработкой любых вызовов на ваш api:

void context_BeginRequest(object sender, EventArgs e)
    {
        if (HttpContext.Current.Request.Path.StartsWith("/api"))
        {
            string fromCookie = HttpContext.Current.Request.Cookies.Get("XSRF-TOKEN").Value;
            string fromHeader = HttpContext.Current.Request.Headers["X-XSRF-TOKEN"];
            if (fromCookie != fromHeader)
            {
                HttpContext.Current.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                HttpContext.Current.Response.End();
            }
        }
    }

Вам нужно установить флаг HttpOnly на FALSE, чтобы javascript из вашего домена мог прочитать cookie и установить заголовок. Я не эксперт по безопасности, поэтому мне хотелось бы получить некоторые отзывы об этом решении от некоторых других членов сообщества.

ИЗМЕНИТЬ

Если вы используете OWIN, вы можете использовать глобальный фильтр действий вместе с плагином промежуточного программного обеспечения:

Startup.cs

app.UseStaticFiles(new StaticFileOptions {
            OnPrepareResponse = (responseContext) => {
                responseContext.OwinContext.Response.Cookies.Append("XSRF-TOKEN", Guid.NewGuid().ToString());
            },
            FileSystem = "wwwroot"
        });

XsrfFilter.cs

public class XsrfFilter : ActionFilterAttribute {
    public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext) {

        string fromCookie = actionContext.Request.Headers.GetCookies("XSRF-TOKEN").FirstOrDefault()["XSRF-TOKEN"].Value;
        string fromHeader = actionContext.Request.Headers.GetValues("X-XSRF-TOKEN").FirstOrDefault();

        if (fromCookie == fromHeader) return;

        actionContext.Response = new HttpResponseMessage(HttpStatusCode.OK);
        actionContext.Response.ReasonPhrase = "bad request";
    }
}