Настройка ASP.NET Core для работы с прокси-серверами и подсистемами балансировки нагрузки

Автор: Крис Росс (Chris Ross)

В рекомендуемой конфигурации для ASP.NET Core приложение размещается с использованием модуля ASP.NET Core для IIS, Nginx или Apache. Прокси-серверы, подсистемы балансировки нагрузки и другие сетевые устройства часто скрывают сведения о запросах, еще не достигших целевого приложения.

  • Если прокси-сервер передает HTTPS-запросы через протокол HTTP, исходная схема (HTTPS) теряется и ее нужно дополнительно передать в заголовке.
  • Так как приложение получает запрос от прокси-сервера, а не от правильного источника в Интернете или корпоративной сети, исходный IP-адрес клиента также нужно передать в заголовке.

Эти сведения могут быть важны при обработке запросов, например для перенаправления, аутентификации, создания ссылок, оценки политик и геолокации клиентов.

Перенаправленные заголовки

По действующему соглашению прокси-серверы передают данные в заголовках HTTP.

Header Описание
X-Forwarded-For Содержит сведения о клиенте, который создал этот запрос, и обо всех предыдущих узлах в цепочке прокси-серверов. Этот параметр может содержать IP-адреса (и номера портов, если потребуется). В цепочке прокси-серверов первый параметр обозначает клиента, отправившего исходный запрос. За ним следуют идентификаторы всех последующих прокси-серверов. В списке параметров отсутствует последний прокси-сервер в цепочке. IP-адрес последнего прокси-сервера (и номер порта, если нужно) поступает в формате удаленного IP-адреса на транспортном уровне.
X-Forwarded-Proto Значение исходной схемы передачи данных (HTTP/HTTPS). Здесь может быть указан целый список схем, если запрос прошел через несколько прокси-серверов.
X-Forwarded-Host Исходное значение поля Host в заголовке запроса. Обычно прокси-серверы не изменяют заголовок Host. В рекомендациях Макрософт по безопасности CVE-2018-0787 представлены сведения о связанной с повышением привилегий уязвимости, которая затрагивает системы, где прокси-сервер не проверяет заголовок Host и не ограничивает его значения известными допустимыми значениями.

ПО промежуточного слоя перенаправления заголовков (ForwardedHeadersMiddleware) считывает эти заголовки и заполняет соответствующие поля HttpContext.

Это ПО промежуточного слоя обновляет следующие сведения:

Дополнительные сведения об описанных выше обновлениях см. в этом описании проблемы на GitHub.

Для ПО промежуточного слоя перенаправления заголовков можно настроить параметры по умолчанию. Для параметров по умолчанию:

  • Существует только один прокси между приложением и источником запросов.
  • Для известных прокси-серверов и известных сетей настраиваются только петлевые адреса.
  • Перенаправленным заголовками заданы имена X-Forwarded-For и X-Forwarded-Proto.
  • Параметр ForwardedHeaders имеет значение ForwardedHeaders.None, в качестве значения этого параметра необходимо установить желаемые серверы пересылки, чтобы включить ПО промежуточного слоя.

Не все сетевые устройства добавляют заголовки X-Forwarded-For и X-Forwarded-Proto без дополнительной настройки. Если запросы, поступающие в приложение через прокси-сервер, не содержат эти заголовки, обратитесь к руководствам изготовителя устройства. Если устройство использует имена заголовков, отличные от X-Forwarded-For и X-Forwarded-Proto, задайте параметрам ForwardedForHeaderName и ForwardedProtoHeaderName значения, совпадающие с именами заголовков, используемыми устройством. Дополнительные сведения см. в разделах Параметры ПО промежуточного слоя перенаправления заголовков и Конфигурация для прокси-сервера, использующего другие имена заголовков.

IIS и IIS Express с модулем ASP.NET Core

ПО промежуточного слоя интеграции IIS по умолчанию включает ПО промежуточного слоя перенаправления заголовков при размещении приложения вне процесса за IIS и модулем ASP.NET Core. При использовании модуля ASP.NET Core ПО промежуточного слоя перенаправления заголовков настраивается так, чтобы оно запускалось первым в конвейере ПО промежуточного слоя и использовало специальную ограниченную конфигурацию, так как перенаправленные заголовки создают дополнительные проблемы с доверием (например, проблему IP-спуфинга). В ПО промежуточного слоя настраивается пересылка заголовков X-Forwarded-For и X-Forwarded-Proto, и оно может работать только с одним локальным прокси-сервером. Если вам нужны другие настройки, изучите раздел Параметры ПО промежуточного слоя перенаправления заголовков.

Другие сценарии использования прокси-сервера и подсистемы балансировки нагрузки

Если интеграция IIS не используется при размещении вне процесса, ПО промежуточного слоя перенаправления заголовков не включается по умолчанию. ПО промежуточного слоя перенаправления заголовков нужно включить, чтобы приложение могло обрабатывать перенаправленные заголовки с UseForwardedHeaders. После включения ПО промежуточного слоя, если не задан параметр ForwardedHeadersOptions, для свойства ForwardedHeadersOptions.ForwardedHeaders по умолчанию устанавливается значение ForwardedHeaders.None.

В ПО промежуточного слоя с ForwardedHeadersOptions настройте перенаправление заголовков X-Forwarded-For и X-Forwarded-Proto в Startup.ConfigureServices.

Порядок ПО промежуточного слоя перенаправления заголовков

ПО промежуточного слоя перенаправления заголовков должно выполняться до другого ПО промежуточного слоя. Такой порядок гарантирует, что ПО промежуточного слоя, полагающееся на сведения о перенаправленных заголовках, может использовать значения заголовков для обработки. ПО промежуточного слоя перенаправления заголовков может выполняться после диагностики и обработки ошибок, однако его необходимо выполнить перед вызовом UseHsts.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();
        services.Configure<ForwardedHeadersOptions>(options =>
        {
            options.ForwardedHeaders =
                ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseForwardedHeaders();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseForwardedHeaders();
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

Кроме того, можно вызвать UseForwardedHeaders перед диагностикой:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseForwardedHeaders();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

Примечание

Если класс ForwardedHeadersOptions не указан в Startup.ConfigureServices или непосредственно в методе расширения с UseForwardedHeaders, по умолчанию будут перенаправляться заголовки ForwardedHeaders.None. Свойство ForwardedHeaders должно быть настроено с заголовками для перенаправления.

Конфигурация Nginx

Сведения о перенаправлении заголовков X-Forwarded-For и X-Forwarded-Proto см. в разделе Среда размещения ASP.NET Core в операционной системе Linux с Nginx. Дополнительные сведения см. в статье об использовании заголовка Forwarded в NGINX.

Конфигурация Apache

X-Forwarded-For добавляется автоматически (см. документацию по заголовкам обратных прокси-запросов в модуле Apache mod_proxy). Сведения о перенаправлении X-Forwarded-Proto заголовка см. в разделе Размещение ASP.NET Core в операционной системе Linux с Apache.

Параметры ПО промежуточного слоя перенаправления заголовков

Параметр ForwardedHeadersOptions позволяет управлять поведением ПО промежуточного слоя перенаправления заголовков. В следующем примере показано изменение значений по умолчанию.

  • Ограничьте количество записей в перенаправленных заголовках до 2.
  • Добавьте известный адрес прокси сервера 127.0.10.1.
  • Измените имя перенаправленного заголовка с заданного по умолчанию X-Forwarded-For на X-Forwarded-For-My-Custom-Header-Name.
services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardLimit = 2;
    options.KnownProxies.Add(IPAddress.Parse("127.0.10.1"));
    options.ForwardedForHeaderName = "X-Forwarded-For-My-Custom-Header-Name";
});
Параметр Описание
AllowedHosts Ограничивает узлы в заголовке X-Forwarded-Host списком указанных значений.
  • Значения сравниваются по порядковым номерам без учета регистра.
  • Номера портов указывать не нужно.
  • Если список пуст, разрешены все узлы.
  • Подстановочный знак верхнего уровня * разрешает все непустые значения узлов.
  • Подстановочные знаки поддоменов допускаются, но не могут указывать на корневой домен. Например, *.contoso.com соответствует поддомену foo.contoso.com, но не корневому домену contoso.com.
  • Имена узлов в Юникоде разрешены, но для сопоставления они преобразуются в Punycode.
  • IPv6-адреса должны включать ограничивающие квадратные скобки и иметь стандартный формат (например, [ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]). IPv6-адреса не приводятся к специальным правилам регистра для логического соответствия разных форматов, и к ним не применяется канонизация.
  • Если список разрешенных узлов не будет ограничен, злоумышленник сможет подделать ссылки, создаваемые службой.
Значение по умолчанию — пустой объект IList<string>.
ForwardedForHeaderName Заголовок, заданный этим свойством, используется вместо заголовка, указанного в параметре ForwardedHeadersDefaults.XForwardedForHeaderName. Этот параметр применяется в случае, если для перенаправления данных прокси-сервер или сервер пересылки не используют заголовок X-Forwarded-For, а используют другой заголовок.

Значение по умолчанию — X-Forwarded-For.
ForwardedHeaders Определяет, какие серверы пересылки будут обрабатываться. В разделе Перечисление ForwardedHeaders приведен список применимых полей. Обычно для этого свойства задаются такие значения: ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto.

Значение по умолчанию — ForwardedHeaders.None.
ForwardedHostHeaderName Заголовок, заданный этим свойством, используется вместо заголовка, указанного в параметре ForwardedHeadersDefaults.XForwardedHostHeaderName. Этот параметр применяется в случае, если для перенаправления данных прокси-сервер или сервер пересылки не используют заголовок X-Forwarded-Host, а используют другой заголовок.

Значение по умолчанию — X-Forwarded-Host.
ForwardedProtoHeaderName Заголовок, заданный этим свойством, используется вместо заголовка, указанного в параметре ForwardedHeadersDefaults.XForwardedProtoHeaderName. Этот параметр применяется в случае, если для перенаправления данных прокси-сервер или сервер пересылки не используют заголовок X-Forwarded-Proto, а используют другой заголовок.

Значение по умолчанию — X-Forwarded-Proto.
ForwardLimit Ограничивает количество записей в обрабатываемых заголовках. Установите значение null, чтобы отключить это ограничение, но только в том случае, если настроено свойство KnownProxies или KnownNetworks. Установленное значение, отличное от null, послужит мерой предосторожности (но не гарантией) для защиты от неправильной настройки прокси-серверов и вредоносных запросов, поступающих по сторонним каналам сети.

ПО промежуточного слоя перенаправления заголовков обрабатывает их в обратном порядке: справа налево. Если используется значение по умолчанию (1), обрабатывается только крайнее правое значение из заголовков, если не увеличить значение ForwardLimit.

Значение по умолчанию — 1.
KnownNetworks Диапазоны адресов известных сетей, от которых можно принимать перенаправленные заголовки. Диапазоны IP-адресов указываются в нотации CIDR.

Если сервер использует сокеты с двумя режимами, IPv4-адреса указываются в формате IPv6 (например, IPv4-адрес 10.0.0.1 в формате IPv6 выглядит так: ::ffff:10.0.0.1). См. статью о методе IPAddress.MapToIPv6. Определите, нужно ли использовать этот формат, ознакомившись со статьей о свойстве HttpContext.Connection.RemoteIpAddress.

Значение по умолчанию — IList<IPNetwork> с одной записью для IPAddress.Loopback.
KnownProxies Адреса известных прокси-серверов, от которых можно принимать перенаправленные заголовки. Используйте KnownProxies, чтобы указать точные IP-адреса.

Если сервер использует сокеты с двумя режимами, IPv4-адреса указываются в формате IPv6 (например, IPv4-адрес 10.0.0.1 в формате IPv6 выглядит так: ::ffff:10.0.0.1). См. статью о методе IPAddress.MapToIPv6. Определите, нужно ли использовать этот формат, ознакомившись со статьей о свойстве HttpContext.Connection.RemoteIpAddress.

Значение по умолчанию — IList<IPAddress> с одной записью для IPAddress.IPv6Loopback.
OriginalForHeaderName Заголовок, заданный этим свойством, используется вместо заголовка, указанного в параметре ForwardedHeadersDefaults.XOriginalForHeaderName.

Значение по умолчанию — X-Original-For.
OriginalHostHeaderName Заголовок, заданный этим свойством, используется вместо заголовка, указанного в параметре ForwardedHeadersDefaults.XOriginalHostHeaderName.

Значение по умолчанию — X-Original-Host.
OriginalProtoHeaderName Заголовок, заданный этим свойством, используется вместо заголовка, указанного в параметре ForwardedHeadersDefaults.XOriginalProtoHeaderName.

Значение по умолчанию — X-Original-Proto.
RequireHeaderSymmetry Требует синхронизации некоторых значений заголовков во всех обрабатываемых ForwardedHeadersOptions.ForwardedHeaders.

Значение по умолчанию в ASP.NET Core 1.x — true. Значение по умолчанию в ASP.NET Core 2.0 или более поздней версии — false.

Сценарии и варианты использования

Отсутствие возможности добавить перенаправленные заголовки, и все запросы считаются безопасными

В некоторых случаях нет возможности добавлять перенаправленные заголовки в запросы, передаваемые приложению через прокси-сервер. Если прокси-сервер требует схему HTTPS для всех внешних запросов, ее можно задать вручную в Startup.Configure перед использованием любого ПО промежуточного слоя:

app.Use((context, next) =>
{
    context.Request.Scheme = "https";
    return next();
});

В тестовой среде или среде разработки этот код можно отключить с помощью переменной среды или другого параметра конфигурации.

Базовый путь и прокси-серверы, которые изменяют путь запроса

Некоторые прокси-серверы передают путь, но добавляют к нему базовый путь приложения, который нужно удалять для правильной работы маршрутизации. ПО промежуточного слоя UsePathBaseExtensions.UsePathBase разделяет путь на два сегмента: HttpRequest.Path и базовый путь приложения HttpRequest.PathBase.

Если /foo является базовым путем приложения в пути /foo/api/1, полученном от прокси-сервера, это ПО промежуточного слоя устанавливает для Request.PathBase значение /foo, а для Request.Path значение /api/1 с помощью следующей команды:

app.UsePathBase("/foo");

Исходный путь и базовый путь подставляются в прежнем виде, когда создается обратный запрос к ПО промежуточного слоя. Дополнительные сведения о порядке обработки для ПО промежуточного слоя см. в статье ПО промежуточного слоя ASP.NET Core.

Если прокси-сервер усекает путь (например, перенаправляет /foo/api/1 в /api/1), такие перенаправления и ссылки можно исправить, задав для запроса свойство PathBase:

app.Use((context, next) =>
{
    context.Request.PathBase = new PathString("/foo");
    return next();
});

Если прокси-сервер добавляет к пути данные, лишнюю часть можно отсечь с помощью StartsWithSegments и присвоить правильное значение свойству Path:

app.Use((context, next) =>
{
    if (context.Request.Path.StartsWithSegments("/foo", out var remainder))
    {
        context.Request.Path = remainder;
    }

    return next();
});

Конфигурация для прокси-сервера, использующего другие имена заголовков

Если для перенаправления адреса или порта прокси-сервера и сведений об исходной схеме прокси-сервер не использует заголовки с именами X-Forwarded-For и X-Forwarded-Proto, задайте параметрам ForwardedForHeaderName и ForwardedProtoHeaderName значения, совпадающие с именами заголовков, используемыми прокси-сервером:

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedForHeaderName = "Header_Name_Used_By_Proxy_For_X-Forwarded-For_Header";
    options.ForwardedProtoHeaderName = "Header_Name_Used_By_Proxy_For_X-Forwarded-Proto_Header";
});

Переадресация схемы для Linux и обратных прокси-серверов не IIS

Приложения, вызывающие UseHttpsRedirection и UseHsts, помещают сайт в бесконечный цикл при развертывании в Службе приложений Azure Linux, виртуальной машине Linux в Azure или за любыми другими обратными прокси-серверами, помимо IIS. TLS завершается обратным прокси-сервером, и Kestrel не знает о правильной схеме запроса. OAuth и OIDC также не работают в этой конфигурации, поскольку создают неверные перенаправления. UseIISIntegration добавляет и настраивает ПО промежуточного слоя переадресованных заголовков при работе за IIS, но для Linux (интеграция с Apache или Nginx) нет аналогичной автоматической конфигурации.

Для переадресации схемы с прокси-сервера, когда используется не IIS, добавьте и настройте ПО промежуточного слоя перенаправленных заголовков. В Startup.ConfigureServices используйте следующий код:

// using Microsoft.AspNetCore.HttpOverrides;

if (string.Equals(
    Environment.GetEnvironmentVariable("ASPNETCORE_FORWARDEDHEADERS_ENABLED"), 
    "true", StringComparison.OrdinalIgnoreCase))
{
    services.Configure<ForwardedHeadersOptions>(options =>
    {
        options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | 
            ForwardedHeaders.XForwardedProto;
        // Only loopback proxies are allowed by default.
        // Clear that restriction because forwarders are enabled by explicit 
        // configuration.
        options.KnownNetworks.Clear();
        options.KnownProxies.Clear();
    });
}

Переадресация сертификатов

Azure

Сведения о настройке Службы приложений Azure для переадресации сертификатов см. в статье Configure TLS mutual authentication for Azure App Service (Настройка взаимной проверки подлинности TLS для Службы приложений Azure). Приведенные ниже инструкции применимы к настройке приложения ASP.NET Core.

В Startup.Configure добавьте указанный ниже код перед вызовом app.UseAuthentication();:

app.UseCertificateForwarding();

Настройте ПО промежуточного слоя переадресации сертификатов, чтобы указать имя заголовка, который использует Azure. В Startup.ConfigureServices добавьте указанный ниже код, чтобы настроить заголовок, из которого ПО промежуточного слоя создает сертификат:

services.AddCertificateForwarding(options =>
    options.CertificateHeader = "X-ARR-ClientCert");

Другие веб-прокси

Если вы используете прокси-сервер не IIS или маршрутизацию запросов приложений (ARR) Службы приложений Azure, настройте прокси-сервер для переадресации сертификата, полученного в заголовке HTTP. В Startup.Configure добавьте указанный ниже код перед вызовом app.UseAuthentication();:

app.UseCertificateForwarding();

Настройте ПО промежуточного слоя переадресации сертификатов, чтобы указать имя заголовка. В Startup.ConfigureServices добавьте указанный ниже код, чтобы настроить заголовок, из которого ПО промежуточного слоя создает сертификат:

services.AddCertificateForwarding(options =>
    options.CertificateHeader = "YOUR_CERTIFICATE_HEADER_NAME");

Если прокси-сервер не выполняет шифрование сертификата в кодировке base64 (как в случае с Nginx), задайте параметр HeaderConverter. Рассмотрим следующий пример в Startup.ConfigureServices:

services.AddCertificateForwarding(options =>
{
    options.CertificateHeader = "YOUR_CUSTOM_HEADER_NAME";
    options.HeaderConverter = (headerValue) => 
    {
        var clientCertificate = 
           /* some conversion logic to create an X509Certificate2 */
        return clientCertificate;
    }
});

Устранение неполадок

Если заголовки перенаправляются не так, как ожидалось, включите ведение журнала. Если журналы не содержат достаточно информации для устранения неполадок, просмотрите список заголовков в запросе, полученном сервером. Используйте встроенное ПО промежуточного слоя для записи заголовков запроса в ответ приложения или для сохранения заголовков в журнал.

Чтобы записать заголовки в ответ приложения, используйте следующее встроенное ПО промежуточного слоя на терминале сразу после вызова UseForwardedHeaders в Startup.Configure:

app.Run(async (context) =>
{
    context.Response.ContentType = "text/plain";

    // Request method, scheme, and path
    await context.Response.WriteAsync(
        $"Request Method: {context.Request.Method}{Environment.NewLine}");
    await context.Response.WriteAsync(
        $"Request Scheme: {context.Request.Scheme}{Environment.NewLine}");
    await context.Response.WriteAsync(
        $"Request Path: {context.Request.Path}{Environment.NewLine}");

    // Headers
    await context.Response.WriteAsync($"Request Headers:{Environment.NewLine}");

    foreach (var header in context.Request.Headers)
    {
        await context.Response.WriteAsync($"{header.Key}: " +
            $"{header.Value}{Environment.NewLine}");
    }

    await context.Response.WriteAsync(Environment.NewLine);

    // Connection: RemoteIp
    await context.Response.WriteAsync(
        $"Request RemoteIp: {context.Connection.RemoteIpAddress}");
});

Можно сделать запись в журналы, а не в текст ответа. Запись в журналы позволяет сайту нормально работать во время отладки.

Для записи в журналы, а не в текст ответа, сделайте следующее:

app.Use(async (context, next) =>
{
    // Request method, scheme, and path
    _logger.LogDebug("Request Method: {Method}", context.Request.Method);
    _logger.LogDebug("Request Scheme: {Scheme}", context.Request.Scheme);
    _logger.LogDebug("Request Path: {Path}", context.Request.Path);

    // Headers
    foreach (var header in context.Request.Headers)
    {
        _logger.LogDebug("Header: {Key}: {Value}", header.Key, header.Value);
    }

    // Connection: RemoteIp
    _logger.LogDebug("Request RemoteIp: {RemoteIpAddress}", 
        context.Connection.RemoteIpAddress);

    await next();
});

После обработки значения X-Forwarded-{For|Proto|Host} перемещаются в X-Original-{For|Proto|Host}. Если в некотором заголовке содержится несколько значений, ПО промежуточного слоя перенаправления заголовков обрабатывает их в обратном порядке: справа налево. По умолчанию ForwardLimit имеет значение 1 (один), а значит обрабатывается только крайнее правое значение из заголовков, если не увеличить значение ForwardLimit.

Удаленный IP-адрес из исходного запроса должен совпадать с записью в списке KnownProxies или KnownNetworks, чтобы происходила обработка заголовков переадресации. Это позволяет избежать некоторых методов спуфинга, отклоняя запросы от ненадежных прокси-серверов пересылки. В журнале указываются адреса неизвестных обнаруженных прокси-серверов:

September 20th 2018, 15:49:44.168 Unknown proxy: 10.0.0.100:54321

В приведенном выше примере указан прокси-сервер с адресом 10.0.0.100. Если сервер является доверенным прокси-сервером, добавьте его IP-адрес в KnownProxies (или добавьте доверенную сеть в KnownNetworks) в файле Startup.ConfigureServices. Дополнительные сведения см. в разделе о параметрах ПО промежуточного слоя перенаправления заголовков.

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});

Важно!

Переадресацию заголовков следует разрешить только доверенным прокси-серверам и сетям. В противном случае будут возможны атаки подмены IP-адресов.

Дополнительные ресурсы

В рекомендуемой конфигурации для ASP.NET Core приложение размещается с использованием модуля ASP.NET Core для IIS, Nginx или Apache. Прокси-серверы, подсистемы балансировки нагрузки и другие сетевые устройства часто скрывают сведения о запросах, еще не достигших целевого приложения.

  • Если прокси-сервер передает HTTPS-запросы через протокол HTTP, исходная схема (HTTPS) теряется и ее нужно дополнительно передать в заголовке.
  • Так как приложение получает запрос от прокси-сервера, а не от правильного источника в Интернете или корпоративной сети, исходный IP-адрес клиента также нужно передать в заголовке.

Эти сведения могут быть важны при обработке запросов, например для перенаправления, аутентификации, создания ссылок, оценки политик и геолокации клиентов.

Перенаправленные заголовки

По действующему соглашению прокси-серверы передают данные в заголовках HTTP.

Header Описание
X-Forwarded-For Содержит сведения о клиенте, который создал этот запрос, и обо всех предыдущих узлах в цепочке прокси-серверов. Этот параметр может содержать IP-адреса (и номера портов, если потребуется). В цепочке прокси-серверов первый параметр обозначает клиента, отправившего исходный запрос. За ним следуют идентификаторы всех последующих прокси-серверов. В списке параметров отсутствует последний прокси-сервер в цепочке. IP-адрес последнего прокси-сервера (и номер порта, если нужно) поступает в формате удаленного IP-адреса на транспортном уровне.
X-Forwarded-Proto Значение исходной схемы передачи данных (HTTP/HTTPS). Здесь может быть указан целый список схем, если запрос прошел через несколько прокси-серверов.
X-Forwarded-Host Исходное значение поля Host в заголовке запроса. Обычно прокси-серверы не изменяют заголовок Host. В рекомендациях Макрософт по безопасности CVE-2018-0787 представлены сведения о связанной с повышением привилегий уязвимости, которая затрагивает системы, где прокси-сервер не проверяет заголовок Host и не ограничивает его значения известными допустимыми значениями.

ПО промежуточного слоя перенаправления заголовков, входящее в пакет Microsoft.AspNetCore.HttpOverrides, считывает эти заголовки и заполняет соответствующие поля HttpContext.

Это ПО промежуточного слоя обновляет следующие сведения:

Для ПО промежуточного слоя перенаправления заголовков можно настроить параметры по умолчанию. По умолчанию используются следующие параметры:

  • Существует только один прокси между приложением и источником запросов.
  • Для известных прокси-серверов и известных сетей настраиваются только петлевые адреса.
  • Перенаправленным заголовками заданы имена X-Forwarded-For и X-Forwarded-Proto.

Не все сетевые устройства добавляют заголовки X-Forwarded-For и X-Forwarded-Proto без дополнительной настройки. Если запросы, поступающие в приложение через прокси-сервер, не содержат эти заголовки, обратитесь к руководствам изготовителя устройства. Если устройство использует имена заголовков, отличные от X-Forwarded-For и X-Forwarded-Proto, задайте параметрам ForwardedForHeaderName и ForwardedProtoHeaderName значения, совпадающие с именами заголовков, используемыми устройством. Дополнительные сведения см. в разделах Параметры ПО промежуточного слоя перенаправления заголовков и Конфигурация для прокси-сервера, использующего другие имена заголовков.

IIS и IIS Express с модулем ASP.NET Core

ПО промежуточного слоя интеграции IIS по умолчанию включает ПО промежуточного слоя перенаправления заголовков при размещении приложения вне процесса за IIS и модулем ASP.NET Core. При использовании модуля ASP.NET Core ПО промежуточного слоя перенаправления заголовков настраивается так, чтобы оно запускалось первым в конвейере ПО промежуточного слоя и использовало специальную ограниченную конфигурацию, так как перенаправленные заголовки создают дополнительные проблемы с доверием (например, проблему IP-спуфинга). В ПО промежуточного слоя настраивается пересылка заголовков X-Forwarded-For и X-Forwarded-Proto, и оно может работать только с одним локальным прокси-сервером. Если вам нужны другие настройки, изучите раздел Параметры ПО промежуточного слоя перенаправления заголовков.

Другие сценарии использования прокси-сервера и подсистемы балансировки нагрузки

Если интеграция IIS не используется при размещении вне процесса, ПО промежуточного слоя перенаправления заголовков не включается по умолчанию. ПО промежуточного слоя перенаправления заголовков нужно включить, чтобы приложение могло обрабатывать перенаправленные заголовки с UseForwardedHeaders. После включения ПО промежуточного слоя, если не задан параметр ForwardedHeadersOptions, для свойства ForwardedHeadersOptions.ForwardedHeaders по умолчанию устанавливается значение ForwardedHeaders.None.

В ПО промежуточного слоя с ForwardedHeadersOptions настройте перенаправление заголовков X-Forwarded-For и X-Forwarded-Proto в Startup.ConfigureServices. Вызовите метод UseForwardedHeaders в Startup.Configure, прежде чем вызывать другое ПО промежуточного слоя:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.Configure<ForwardedHeadersOptions>(options =>
    {
        options.ForwardedHeaders = 
            ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseForwardedHeaders();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();
    // In ASP.NET Core 1.x, replace the following line with: app.UseIdentity();
    app.UseAuthentication();
    app.UseMvc();
}

Примечание

Если класс ForwardedHeadersOptions не указан в Startup.ConfigureServices или непосредственно в методе расширения с UseForwardedHeaders, по умолчанию будут перенаправляться заголовки ForwardedHeaders.None. Свойство ForwardedHeaders должно быть настроено с заголовками для перенаправления.

Конфигурация Nginx

Сведения о перенаправлении заголовков X-Forwarded-For и X-Forwarded-Proto см. в разделе Среда размещения ASP.NET Core в операционной системе Linux с Nginx. Дополнительные сведения см. в статье об использовании заголовка Forwarded в NGINX.

Конфигурация Apache

X-Forwarded-For добавляется автоматически (см. документацию по заголовкам обратных прокси-запросов в модуле Apache mod_proxy). Сведения о перенаправлении X-Forwarded-Proto заголовка см. в разделе Размещение ASP.NET Core в операционной системе Linux с Apache.

Параметры ПО промежуточного слоя перенаправления заголовков

Параметр ForwardedHeadersOptions позволяет управлять поведением ПО промежуточного слоя перенаправления заголовков. В следующем примере показано изменение значений по умолчанию.

  • Ограничьте количество записей в перенаправленных заголовках до 2.
  • Добавьте известный адрес прокси сервера 127.0.10.1.
  • Измените имя перенаправленного заголовка с заданного по умолчанию X-Forwarded-For на X-Forwarded-For-My-Custom-Header-Name.
services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardLimit = 2;
    options.KnownProxies.Add(IPAddress.Parse("127.0.10.1"));
    options.ForwardedForHeaderName = "X-Forwarded-For-My-Custom-Header-Name";
});
Параметр Описание
AllowedHosts Ограничивает узлы в заголовке X-Forwarded-Host списком указанных значений.
  • Значения сравниваются по порядковым номерам без учета регистра.
  • Номера портов указывать не нужно.
  • Если список пуст, разрешены все узлы.
  • Подстановочный знак верхнего уровня * разрешает все непустые значения узлов.
  • Подстановочные знаки поддоменов допускаются, но не могут указывать на корневой домен. Например, *.contoso.com соответствует поддомену foo.contoso.com, но не корневому домену contoso.com.
  • Имена узлов в Юникоде разрешены, но для сопоставления они преобразуются в Punycode.
  • IPv6-адреса должны включать ограничивающие квадратные скобки и иметь стандартный формат (например, [ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]). IPv6-адреса не приводятся к специальным правилам регистра для логического соответствия разных форматов, и к ним не применяется канонизация.
  • Если список разрешенных узлов не будет ограничен, злоумышленник сможет подделать ссылки, создаваемые службой.
Значение по умолчанию — пустой объект IList<string>.
ForwardedForHeaderName Заголовок, заданный этим свойством, используется вместо заголовка, указанного в параметре ForwardedHeadersDefaults.XForwardedForHeaderName. Этот параметр применяется в случае, если для перенаправления данных прокси-сервер или сервер пересылки не используют заголовок X-Forwarded-For, а используют другой заголовок.

Значение по умолчанию — X-Forwarded-For.
ForwardedHeaders Определяет, какие серверы пересылки будут обрабатываться. В разделе Перечисление ForwardedHeaders приведен список применимых полей. Обычно для этого свойства задаются такие значения: ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto.

Значение по умолчанию — ForwardedHeaders.None.
ForwardedHostHeaderName Заголовок, заданный этим свойством, используется вместо заголовка, указанного в параметре ForwardedHeadersDefaults.XForwardedHostHeaderName. Этот параметр применяется в случае, если для перенаправления данных прокси-сервер или сервер пересылки не используют заголовок X-Forwarded-Host, а используют другой заголовок.

Значение по умолчанию — X-Forwarded-Host.
ForwardedProtoHeaderName Заголовок, заданный этим свойством, используется вместо заголовка, указанного в параметре ForwardedHeadersDefaults.XForwardedProtoHeaderName. Этот параметр применяется в случае, если для перенаправления данных прокси-сервер или сервер пересылки не используют заголовок X-Forwarded-Proto, а используют другой заголовок.

Значение по умолчанию — X-Forwarded-Proto.
ForwardLimit Ограничивает количество записей в обрабатываемых заголовках. Установите значение null, чтобы отключить это ограничение, но только в том случае, если настроено свойство KnownProxies или KnownNetworks. Установленное значение, отличное от null, послужит мерой предосторожности (но не гарантией) для защиты от неправильной настройки прокси-серверов и вредоносных запросов, поступающих по сторонним каналам сети.

ПО промежуточного слоя перенаправления заголовков обрабатывает их в обратном порядке: справа налево. Если используется значение по умолчанию (1), обрабатывается только крайнее правое значение из заголовков, если не увеличить значение ForwardLimit.

Значение по умолчанию — 1.
KnownNetworks Диапазоны адресов известных сетей, от которых можно принимать перенаправленные заголовки. Диапазоны IP-адресов указываются в нотации CIDR.

Если сервер использует сокеты с двумя режимами, IPv4-адреса указываются в формате IPv6 (например, IPv4-адрес 10.0.0.1 в формате IPv6 выглядит так: ::ffff:10.0.0.1). См. статью о методе IPAddress.MapToIPv6. Определите, нужно ли использовать этот формат, ознакомившись со статьей о свойстве HttpContext.Connection.RemoteIpAddress. Дополнительные сведения см. в разделе Конфигурация для IPv4-адреса, представленного в формате IPv6.

Значение по умолчанию — IList<IPNetwork> с одной записью для IPAddress.Loopback.
KnownProxies Адреса известных прокси-серверов, от которых можно принимать перенаправленные заголовки. Используйте KnownProxies, чтобы указать точные IP-адреса.

Если сервер использует сокеты с двумя режимами, IPv4-адреса указываются в формате IPv6 (например, IPv4-адрес 10.0.0.1 в формате IPv6 выглядит так: ::ffff:10.0.0.1). См. статью о методе IPAddress.MapToIPv6. Определите, нужно ли использовать этот формат, ознакомившись со статьей о свойстве HttpContext.Connection.RemoteIpAddress. Дополнительные сведения см. в разделе Конфигурация для IPv4-адреса, представленного в формате IPv6.

Значение по умолчанию — IList<IPAddress> с одной записью для IPAddress.IPv6Loopback.
OriginalForHeaderName Заголовок, заданный этим свойством, используется вместо заголовка, указанного в параметре ForwardedHeadersDefaults.XOriginalForHeaderName.

Значение по умолчанию — X-Original-For.
OriginalHostHeaderName Заголовок, заданный этим свойством, используется вместо заголовка, указанного в параметре ForwardedHeadersDefaults.XOriginalHostHeaderName.

Значение по умолчанию — X-Original-Host.
OriginalProtoHeaderName Заголовок, заданный этим свойством, используется вместо заголовка, указанного в параметре ForwardedHeadersDefaults.XOriginalProtoHeaderName.

Значение по умолчанию — X-Original-Proto.
RequireHeaderSymmetry Требует синхронизации некоторых значений заголовков во всех обрабатываемых ForwardedHeadersOptions.ForwardedHeaders.

Значение по умолчанию в ASP.NET Core 1.x — true. Значение по умолчанию в ASP.NET Core 2.0 или более поздней версии — false.

Сценарии и варианты использования

Отсутствие возможности добавить перенаправленные заголовки, и все запросы считаются безопасными

В некоторых случаях нет возможности добавлять перенаправленные заголовки в запросы, передаваемые приложению через прокси-сервер. Если прокси-сервер требует схему HTTPS для всех внешних запросов, ее можно задать вручную в Startup.Configure перед использованием любого ПО промежуточного слоя:

app.Use((context, next) =>
{
    context.Request.Scheme = "https";
    return next();
});

В тестовой среде или среде разработки этот код можно отключить с помощью переменной среды или другого параметра конфигурации.

Базовый путь и прокси-серверы, которые изменяют путь запроса

Некоторые прокси-серверы передают путь, но добавляют к нему базовый путь приложения, который нужно удалять для правильной работы маршрутизации. ПО промежуточного слоя UsePathBaseExtensions.UsePathBase разделяет путь на два сегмента: HttpRequest.Path и базовый путь приложения HttpRequest.PathBase.

Если /foo является базовым путем приложения в пути /foo/api/1, полученном от прокси-сервера, это ПО промежуточного слоя устанавливает для Request.PathBase значение /foo, а для Request.Path значение /api/1 с помощью следующей команды:

app.UsePathBase("/foo");

Исходный путь и базовый путь подставляются в прежнем виде, когда создается обратный запрос к ПО промежуточного слоя. Дополнительные сведения о порядке обработки для ПО промежуточного слоя см. в статье ПО промежуточного слоя ASP.NET Core.

Если прокси-сервер усекает путь (например, перенаправляет /foo/api/1 в /api/1), такие перенаправления и ссылки можно исправить, задав для запроса свойство PathBase:

app.Use((context, next) =>
{
    context.Request.PathBase = new PathString("/foo");
    return next();
});

Если прокси-сервер добавляет к пути данные, лишнюю часть можно отсечь с помощью StartsWithSegments и присвоить правильное значение свойству Path:

app.Use((context, next) =>
{
    if (context.Request.Path.StartsWithSegments("/foo", out var remainder))
    {
        context.Request.Path = remainder;
    }

    return next();
});

Конфигурация для прокси-сервера, использующего другие имена заголовков

Если для перенаправления адреса или порта прокси-сервера и сведений об исходной схеме прокси-сервер не использует заголовки с именами X-Forwarded-For и X-Forwarded-Proto, задайте параметрам ForwardedForHeaderName и ForwardedProtoHeaderName значения, совпадающие с именами заголовков, используемыми прокси-сервером:

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedForHeaderName = "Header_Name_Used_By_Proxy_For_X-Forwarded-For_Header";
    options.ForwardedProtoHeaderName = "Header_Name_Used_By_Proxy_For_X-Forwarded-Proto_Header";
});

Конфигурация для IPv4-адреса, представленного в формате IPv6

Если сервер использует сокеты с двумя режимами, IPv4-адреса указываются в формате IPv6 (например, IPv4-адрес 10.0.0.1 в формате IPv6 будет выглядеть так: ::ffff:10.0.0.1, или так: ::ffff:a00:1). См. статью о методе IPAddress.MapToIPv6. Определите, нужно ли использовать этот формат, ознакомившись со статьей о свойстве HttpContext.Connection.RemoteIpAddress.

В следующем примере сетевой адрес, по которому предоставляются перенаправленные заголовки, добавляется в формате IPv6 в список KnownNetworks.

IPv4-адрес: 10.11.12.1/8

Преобразованный в IPv6-адрес: ::ffff:10.11.12.1
Длина преобразованного префикса: 104

Можно также указать адрес в шестнадцатеричном формате (10.11.12.1 в формате IPv6 выглядит как: ::ffff:0a0b:0c01). При преобразовании IPv4-адреса в IPv6-адрес добавьте 96 к длине префикса CIDR (8 в примере) с учетом дополнительного префикса IPv6 ::ffff: (8 + 96 = 104).

// To access IPNetwork and IPAddress, add the following namespaces:
// using System.Net;
// using Microsoft.AspNetCore.HttpOverrides;
services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    options.KnownNetworks.Add(new IPNetwork(
        IPAddress.Parse("::ffff:10.11.12.1"), 104));
});

Переадресация схемы для Linux и обратных прокси-серверов не IIS

Приложения, вызывающие UseHttpsRedirection и UseHsts, помещают сайт в бесконечный цикл при развертывании в Службе приложений Azure Linux, виртуальной машине Linux в Azure или за любыми другими обратными прокси-серверами, помимо IIS. TLS завершается обратным прокси-сервером, и Kestrel не знает о правильной схеме запроса. OAuth и OIDC также не работают в этой конфигурации, поскольку создают неверные перенаправления. UseIISIntegration добавляет и настраивает ПО промежуточного слоя переадресованных заголовков при работе за IIS, но для Linux (интеграция с Apache или Nginx) нет аналогичной автоматической конфигурации.

Для переадресации схемы с прокси-сервера, когда используется не IIS, добавьте и настройте ПО промежуточного слоя перенаправленных заголовков. В Startup.ConfigureServices используйте следующий код:

// using Microsoft.AspNetCore.HttpOverrides;

if (string.Equals(
    Environment.GetEnvironmentVariable("ASPNETCORE_FORWARDEDHEADERS_ENABLED"), 
    "true", StringComparison.OrdinalIgnoreCase))
{
    services.Configure<ForwardedHeadersOptions>(options =>
    {
        options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | 
            ForwardedHeaders.XForwardedProto;
        // Only loopback proxies are allowed by default.
        // Clear that restriction because forwarders are enabled by explicit 
        // configuration.
        options.KnownNetworks.Clear();
        options.KnownProxies.Clear();
    });
}

Устранение неполадок

Если заголовки перенаправляются не так, как ожидалось, включите ведение журнала. Если журналы не содержат достаточно информации для устранения неполадок, просмотрите список заголовков в запросе, полученном сервером. Используйте встроенное ПО промежуточного слоя для записи заголовков запроса в ответ приложения или для сохранения заголовков в журнал.

Чтобы записать заголовки в ответ приложения, используйте следующее встроенное ПО промежуточного слоя на терминале сразу после вызова UseForwardedHeaders в Startup.Configure:

app.Run(async (context) =>
{
    context.Response.ContentType = "text/plain";

    // Request method, scheme, and path
    await context.Response.WriteAsync(
        $"Request Method: {context.Request.Method}{Environment.NewLine}");
    await context.Response.WriteAsync(
        $"Request Scheme: {context.Request.Scheme}{Environment.NewLine}");
    await context.Response.WriteAsync(
        $"Request Path: {context.Request.Path}{Environment.NewLine}");

    // Headers
    await context.Response.WriteAsync($"Request Headers:{Environment.NewLine}");

    foreach (var header in context.Request.Headers)
    {
        await context.Response.WriteAsync($"{header.Key}: " +
            $"{header.Value}{Environment.NewLine}");
    }

    await context.Response.WriteAsync(Environment.NewLine);

    // Connection: RemoteIp
    await context.Response.WriteAsync(
        $"Request RemoteIp: {context.Connection.RemoteIpAddress}");
});

Можно сделать запись в журналы, а не в текст ответа. Запись в журналы позволяет сайту нормально работать во время отладки.

Для записи в журналы, а не в текст ответа, сделайте следующее:

app.Use(async (context, next) =>
{
    // Request method, scheme, and path
    _logger.LogDebug("Request Method: {Method}", context.Request.Method);
    _logger.LogDebug("Request Scheme: {Scheme}", context.Request.Scheme);
    _logger.LogDebug("Request Path: {Path}", context.Request.Path);

    // Headers
    foreach (var header in context.Request.Headers)
    {
        _logger.LogDebug("Header: {Key}: {Value}", header.Key, header.Value);
    }

    // Connection: RemoteIp
    _logger.LogDebug("Request RemoteIp: {RemoteIpAddress}", 
        context.Connection.RemoteIpAddress);

    await next();
});

После обработки значения X-Forwarded-{For|Proto|Host} перемещаются в X-Original-{For|Proto|Host}. Если в некотором заголовке содержится несколько значений, ПО промежуточного слоя перенаправления заголовков обрабатывает их в обратном порядке: справа налево. По умолчанию ForwardLimit имеет значение 1 (один), а значит обрабатывается только крайнее правое значение из заголовков, если не увеличить значение ForwardLimit.

Удаленный IP-адрес из исходного запроса должен совпадать с записью в списке KnownProxies или KnownNetworks, чтобы происходила обработка заголовков переадресации. Это позволяет избежать некоторых методов спуфинга, отклоняя запросы от ненадежных прокси-серверов пересылки. В журнале указываются адреса неизвестных обнаруженных прокси-серверов:

September 20th 2018, 15:49:44.168 Unknown proxy: 10.0.0.100:54321

В приведенном выше примере указан прокси-сервер с адресом 10.0.0.100. Если сервер является доверенным прокси-сервером, добавьте его IP-адрес в KnownProxies (или добавьте доверенную сеть в KnownNetworks) в файле Startup.ConfigureServices. Дополнительные сведения см. в разделе о параметрах ПО промежуточного слоя перенаправления заголовков.

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});

Важно!

Переадресацию заголовков следует разрешить только доверенным прокси-серверам и сетям. В противном случае будут возможны атаки подмены IP-адресов.

Дополнительные ресурсы