Можно ли отправлять XSRF-TOKEN из внутреннего интерфейса в веб-интерфейс в заголовке, а не сохранять его в виде файла cookie? - PullRequest
2 голосов
/ 29 мая 2019

Команда, над которой я работаю, имеет пользовательский интерфейс и API на двух разных доменах x-ui.app.example.com и x-api.app.example.com .Очевидно, что эта архитектура не работает с традиционными средствами XSRF в Angular / .NET Core.

Традиционный способ, которым она была описана:

  1. Когда x-api получаетэто первый запрос, или когда x-ui вызывает определенную конечную точку, x-api сгенерирует куки-файл 'XSRF-TOKEN', описанный здесь: https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-2.2
  2. x-ui будет читать этот куки-файл на следующих данныхманипулирует запросами, такими как POST, и будет включать содержимое файла cookie XSRF-TOKEN в заголовок запроса: X-XSRF-TOKEN

Это не работает для меня, так какЕсть два разных субдомена, что означает, что пользовательский интерфейс не может читать куки, созданные субдоменом x-api.Я знаю, что правильно сделать, это свернуть домен в один домен ui / api, и тогда все будет работать волшебно, однако, из-за нехватки времени или из-за того, что я не могу этого сделать, яможно обойти это, добавив файл cookie XSRF-TOKEN в домен app.example.com:

context.Response.Cookies.Append("XSRF-TOKEN", token, new CookieOptions
{
    HttpOnly = false,
    IsEssential = true,
    Secure = true,
    SameSite = SameSiteMode.None,
    Domain = "app.example.com"
});                   

. Это дает UI доступ к cookie, поскольку он находится в родительском домене.Однако я не хочу этого делать, поскольку это будет означать, что любые другие приложения, созданные на app.example.com, смогут также увидеть cookie, и это просто грубо.

Другой способ, которым я рассматривалдля этого нужно было отправить XSRF-TOKEN обратно в заголовок ответа от первоначального вызова, после чего пользовательский интерфейс будет отвечать за сохранение содержимого в виде файла cookie в своем собственном домене, а затем последующие запросы смогут использовать этот файл cookie.

Мой вопрос : это приемлемый способ выполнения XSRF?Есть ли какая-либо уязвимость, передавая обратно XSRF-TOKEN в заголовке ответа от API, а затем сохраняя его как cookie в пользовательском интерфейсе?

Это будет выглядеть примерно так:

.NET Core

app.Use(next => context =>
    {
        if (context.Request.Method == HttpMethods.Get)
        {
            var token = antiforgery.GetAndStoreTokens(context).RequestToken;
            context.Response.Headers.Append("XSRF-TOKEN", token);
        }

        return next(context);
    });
CorsPolicyBuilder corsBuilder = new CorsPolicyBuilder();
corsBuilder.AllowAnyHeader();
corsBuilder.AllowAnyMethod();
corsBuilder.WithExposedHeaders(new string[]{ "XSRF-TOKEN" });
corsBuilder.WithOrigins("http://localhost:4200", "null", "x-ui.app.example.com");
corsBuilder.AllowCredentials();
services.AddCors(options => { options.AddPolicy("AllowAll", corsBuilder.Build()); });

и угловой перехватчик: (Обратите внимание, что я добавил бы здесь больше проверок, чтобы посмотреть, был ли уже установлен cookie, чтобы он не происходил при каждом отдельном GET-запросе,но чтобы понять смысл).

import {
  HttpHandler,
  HttpInterceptor,
  HttpRequest,
  HttpEvent,
  HttpXsrfTokenExtractor, HttpResponse
} from '@angular/common/http';
import { Observable } from 'rxjs';
import {Injectable} from '@angular/core';
import { CookieService } from 'ngx-cookie-service';
import { tap } from 'rxjs/operators';

@Injectable()
export class HttpXsrfInterceptorService implements HttpInterceptor {
  private cookieValue: string;
  constructor(private tokenExtractor: HttpXsrfTokenExtractor, private cookieService: CookieService) {}
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (req.method === 'GET') {
      return next.handle(req).pipe(
        tap(evt => {
          if (evt instanceof HttpResponse) {
            console.log(evt.headers.get('XSRF-TOKEN'));
            this.cookieService.set( 'XSRF-TOKEN', evt.headers.get('XSRF-TOKEN'), null, '/', null, false, 'Lax' );
            this.cookieValue = this.cookieService.get('XSRF-TOKEN');
          }
        })
      );
    }

    // We will just forward HEAD, and OPTIONS requests on as usual, without
    // trying to append the X-XSRF-TOKEN header, since XSRF validation isn't done
    // on these methods anyways.
    if (req.method === 'HEAD' || req.method === 'OPTIONS' ) {
      return next.handle(req);
    }

    let requestToForward = req;
    const headerName = 'X-XSRF-TOKEN';

    // Try to extract the 'XSRF-TOKEN' cookie from the request.
    const token = this.tokenExtractor.getToken() as string;

    // If we successfully retrieved a token, we will set a header called 'X-XSRF-TOKEN' which
    // will be used for XSRF validation on the API.
    if (token !== null && !req.headers.has(headerName)) {
      requestToForward = req.clone({setHeaders: {'X-XSRF-TOKEN': token}, withCredentials: true });
    }
    return next.handle(requestToForward);
  }
}
...