Что такое состояние просмотра?
Состояние просмотра — это информация, которая циклически переключается между страницами WebForms (.aspx) в приложении ASP.NET. Разметка HTML для поля __VIEWSTATE выглядит следующим образом:
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="..." />
Одним из примеров элемента, который может храниться в поле __VIEWSTATE, является текст элемента управления "Кнопка". Если пользователь нажмет кнопку, обработчик событий Button_Click сможет извлечь текст кнопки из поля состояния просмотра. См. раздел ASP.NET Обзор состояния просмотра на веб-сайте Microsoft Developer Network (MSDN) для получения более подробной информации о состоянии просмотра ASP.NET.
Так как поле __VIEWSTATE содержит важные сведения, которые используются для восстановления страницы при обратной передаче, убедитесь, что злоумышленник не сможет подделать это поле. Если злоумышленник отправит вредоносные __VIEWSTATE полезных данных, он потенциально может обманом заставить приложение выполнить действие, которое в противном случае оно не выполнило бы.
Чтобы предотвратить этот вид атак с целью взлома, поле __VIEWSTATE защищено кодом проверки подлинности сообщения (MAC). ASP.NET проверяет MAC, отправляемый вместе с полезными данными __VIEWSTATE при выполнении обратной передачи. Ключ, который используется для вычисления MAC, указывается в элементе приложения в Web.config файле. Поскольку злоумышленник не может угадать содержимое элемента machineKey<>, он не может предоставить действительный MAC-адрес, если он попытается изменить полезные данные __VIEWSTATE. ASP.NET обнаружит, что действительный MAC-адрес не был предоставлен, и ASP.NET отклонит вредоносный запрос.
Почему возникают ошибки проверки MAC?
Ошибка проверки MAC будет выглядеть примерно так:
Примечание
Ошибка сервера в приложении '/'.
Ошибка проверки MAC-адреса viewstate. Если это приложение размещено в веб-ферме или кластере, убедитесь, что <конфигурация machineKey> указывает один и тот же алгоритм проверки и проверки. Автогенерацию нельзя использовать в кластере.
Описание. Во время выполнения текущего веб-запроса возникло необработанное исключение. Дополнительные сведения об ошибке и месте ее возникновения в коде см. в трассировке стека.
Сведения об исключении: System.Web.HttpException: ошибка проверки MAC-адреса состояния просмотра. Если это приложение размещено на веб-ферме или в кластере, убедитесь, что <конфигурация machineKey> указывает один и тот же алгоритм проверки и проверки. Автогенерацию нельзя использовать в кластере.
Ошибка источника: [Нет соответствующих строк источника]
Исходный файл: ... Линия: 0
Трассировка стека:
[ViewStateException: недопустимое состояние просмотра.
IP-адрес клиента: ::1
Порт: 40653
Реферер: http://localhost:40643/MyPage.aspx
Путь: /MyPage.aspx
User-Agent: Mozilla/5.0 (совместимый; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
ViewState: ...]
[HttpException (0x80004005): ошибка проверки MAC состояния просмотра. Если это приложение размещено на веб-ферме или в кластере, убедитесь, что <конфигурация machineKey> указывает один и тот же алгоритм проверки и проверки. Автогенерацию нельзя использовать в кластере.
Дополнительные сведения см. в http://go.microsoft.com/fwlink/?LinkID=314055.]
System.Web.UI.ViewStateException.ThrowError(Inner, String persistedState, String errorPageMessage, Boolean macValidationError) +190
System.Web.UI.ViewStateException.ThrowMacValidationError(внутреннее исключение, строка persistedState) +46
System.Web.UI.ObjectStateFormatter.Deserialize(String inputString, Purpose Purpose) +861
System.Web.UI.ObjectStateFormatter.System.Web.UI.IStateFormatter2.Deserialize(String serializedState, Purpose purpose) +51
System.Web.UI.Util.DeserializeWithAssert(IStateFormatter2 formatter, String serializedState, Purpose purpose) +67
System.Web.UI.HiddenFieldPageStatePersister.Load() +444
System.Web.UI.Page.LoadPageStateFromPersistenceMedium() +368
System.Web.UI.Page.LoadAllState() +109
System.Web.UI.Page.ProcessRequestMain(логическое значение includeStagesBeforeAsyncPoint, логическое значение includeStagesAfterAsyncPoint) +7959
System.Web.UI.Page.ProcessRequest(логическое значение includeStagesBeforeAsyncPoint, логическое значение includeStagesAfterAsyncPoint) +429
System.Web.UI.Page.ProcessRequest() +125
System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext context) +48
System.Web.UI.Page.ProcessRequest(HttpContext context) +234
ASP.mypage_aspx. ProcessRequest(контекст HttpContext) в ... :0
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +1300
System.Web.HttpApplication.ExecuteStep(шаг IExecutionStep, логическое& completedSynchronously) +140
Причина 1. Веб-приложение выполняется в ферме (многосерверной среде)
ASP.NET автоматически создает криптографический ключ для каждого приложения и сохраняет ключ в кусте реестра HKCU. Этот автоматически создаваемый ключ используется, если в конфигурации приложения нет явного <элемента machineKey> . Однако, поскольку этот автоматически созданный ключ является локальным для компьютера, создавшего ключ, этот сценарий вызывает проблемы для приложений, работающих в ферме. Каждый сервер фермы генерирует собственный локальный ключ, и ни один из серверов фермы не может договориться о том, какой ключ использовать. В результате, если один сервер генерирует __VIEWSTATE полезных данных, потребляемых другим сервером, у потребителя возникнет ошибка проверки MAC.
Решение 1a. Создание явного <элемента machineKey>
Добавляя явный <элемент machineKey> в файл Web.config приложения, разработчик говорит ASP.NET не использовать автоматически созданный криптографический ключ. Инструкции по созданию <элемента machineKey> см. в приложении A. После добавления этого элемента в файл Web.config повторно разверните приложение на каждом сервере фермы.
Примечание. Некоторые службы размещения веб-сайтов, такие как веб-сайты Microsoft Azure, выполняют действия по синхронизации автоматически созданного ключа каждого приложения на своих внутренних серверах. Это позволяет приложениям, для которых не указан явный <элемент machineKey> , продолжать работу в этих средах, даже если приложение выполняется в ферме. Если ваше приложение работает в сторонней службе размещения, обратитесь к поставщику услуг размещения, чтобы определить, относится ли к вам эта ситуация.
Решение 1b. Включить сходство в балансировщике нагрузки
Если сайты работают за балансировщиком нагрузки, вы можете включить сопоставление серверов, чтобы временно обойти проблему. Это гарантирует, что данный клиент будет взаимодействовать только с одним физическим сервером за балансировщиком нагрузки, так что все криптографические полезные данные будут создаваться и использоваться одним сервером.
Это не следует рассматривать как долгосрочное решение проблемы. Даже если включено сопоставление серверов, большинство балансировщиков нагрузки перенаправят клиента на другой физический сервер, если исходный сервер, к которому были привязаны балансировщики нагрузки, переходит в автономный режим. Это приводит к тому, что новый сервер отклоняет криптографические полезные данные (такие как __VIEWSTATE, формы, билеты проверки подлинности, MVC, маркеры защиты от подделки и другие службы), которые в настоящее время есть у клиента.
Использование явного <элемента machineKey> и повторное развертывание приложения следует предпочесть включению сопоставления серверов.
Причина 2. Рабочий процесс использует удостоверение пула приложений IIS 7.0
Службы IIS 7.0 (Windows Vista Windows Server 2008) представили удостоверения пула приложений — новый механизм изоляции, помогающий обеспечить повышенную безопасность серверов, на которых запущены ASP.NET приложения. Однако сайты, работающие под удостоверением пула приложений, не имеют доступа к реестру HKCU. Именно здесь среда выполнения ASP.NET сохраняет свои автоматически сгенерированные <ключи machineKey> . В результате ASP.NET не сможете сохранить автоматически созданный ключ при сбросе пула приложений. Поэтому при каждом сбросе w3wp.exe создается новый временный ключ.
Примечание. Эта проблема не наблюдается в IIS 7.5 (Windows 7, Windows Server 2008 R2) и более поздних версиях. В этих версиях IIS ASP.NET может сохранять свои автоматически созданные ключи в другом расположении, которое переживет сброс пула приложений.
Решение 2a. Использование служебной программы aspnet_regiis
ASP.NET установки содержат служебную программу, aspnet_regiis.exe. Эта служебная программа позволяет ASP.NET взаимодействовать с IIS для выполнения конфигураций, необходимых для запуска управляемого приложения. Одна из этих конфигураций создает необходимые разделы в кусте реестра для обеспечения сохранения автоматически созданных ключей компьютера.
Во-первых, необходимо определить, какой пул приложений использует ваш сайт. Это можно определить с помощью служебной программы inetmgr, поставляемой с IIS. Выберите свой сайт в древовидном представлении слева, щелкните правой кнопкой мыши «Управление Website», а затем выберите пункт «Дополнительные параметры». В появившемся диалоговом окне будет показано имя пула приложений.
Чтобы сформировать необходимые разделы реестра для пула приложений ASP.NET 4.0, выполните следующие действия.
Откройте командную строку с правами администратора.
Найдите соответствующий каталог в зависимости от того, является ли пул приложений 32- или 64-разрядным:
- Пул 32-разрядных приложений: cd /d %windir%\Microsoft.NET\Framework\v4.0.30319
- Пул 64-разрядных приложений: cd /d %windir%\Microsoft.NET\Framework64\v4.0.30319
Перейдите в каталог, введите следующую команду и нажмите клавишу ВВОД:
aspnet_regiis -ga "IIS APPPOOL\app-pool-name"
Если пул приложений представляет собой пул приложений ASP.NET 2.0 или 3.5, выполните следующие действия:
Откройте командную строку с правами администратора.
Найдите соответствующий каталог в зависимости от того, является ли пул приложений 32- или 64-разрядным:
- Пул 32-разрядных приложений: cd /d %windir%\Microsoft.NET\Framework\v2.0.50727
- Пул 64-разрядных приложений: cd /d %windir%\Microsoft.NET\Framework64\v2.0.50727
Перейдите в каталог, введите следующую команду и нажмите клавишу ВВОД:
aspnet_regiis -ga "IIS APPPOOL\app-pool-name"
Например, если ваш пул приложений называется "Мой пул приложений" (как показано на предыдущем рисунке), выполните следующую команду:
aspnet_regiis -ga "IIS APPPOOL\My App Pool"Примечание. Системные службы APPHOSTSVC и WAS, возможно, должны быть запущены, чтобы служебная программа aspnet_regiis правильно разрешала имена IIS APPPOOL\*.
Решение 2b. Создание явного <элемента machineKey>
Добавляя явный <элемент machineKey> в файл Web.config приложения, разработчик говорит ASP.NET не использовать автоматически созданный криптографический ключ. Инструкции по созданию <элемента machineKey> см. в приложении A.
Причина 3. Пул приложений настроен с помощью LoadUserProfile=false
Если пул приложений выполняется с пользовательским удостоверением, службы IIS, возможно, не загрузили профиль пользователя для удостоверения. Это приводит к тому, что реестр HKCU становится недоступным для ASP.NET для сохранения автоматически созданного <machineKey>. Поэтому при каждом перезапуске приложения будет создаваться новый автоматически созданный ключ. Дополнительные сведения см. в разделе "Профиль пользователя " на веб-сайте Майкрософт.
Решение 3a. Использование служебной программы aspnet_regiis
Инструкции для этого такие же, как и в Резолюции 2a. Дополнительные сведения см. в этом разделе.
Разрешение 3b: используйте явный <machineKey>
Добавляя явный <элемент machineKey> в файл Web.config приложения, разработчик говорит ASP.NET не использовать автоматически созданный криптографический ключ. Инструкции по созданию <элемента machineKey> см. в приложении A.
Решение 3c. Подготовка необходимых разделов реестра HKCU вручную
Если не удается запустить служебную программу aspnet_regiis, можно использовать сценарий Windows PowerShell для подготовки соответствующих разделов реестра в HKCU. Дополнительную информацию см. в приложении B .
Решение 3d: Установите LoadUserProfile=true для этого пула приложений
Вы также можете разрешить загрузку профиля пользователя в этом пуле приложений. Это делает куст реестра HKCU, временную папку и другие пользовательские места хранения доступными для приложения. Однако это может привести к увеличению использования диска или памяти рабочим процессом. Дополнительные сведения о том, как включить этот параметр, см. в элементе .
Причина 4. Неверное значение свойства Page.ViewStateUserKey
Разработчики программного обеспечения могут использовать свойство Page.ViewStateUserKey для добавления защиты от подделки межсайтовых запросов в поле __VIEWSTATE. Если вы используете свойство Page.ViewStateUserKey, ему обычно присваивается такое значение, как имя пользователя текущего пользователя или идентификатор сеанса пользователя. Шаблоны проектов для приложений WebForms в Microsoft Visual Studio 2012 и более поздних версиях содержат примеры, использующие это свойство. Дополнительные сведения см. в разделе "Свойство Page.ViewStateUserKey" на веб-сайте Microsoft Developer Network (MSDN).
Если указано свойство ViewStateUserKey, его значение записывается в __VIEWSTATE во время создания. Когда поле __VIEWSTATE используется, сервер проверяет свойство ViewStateUserKey текущей страницы и проверяет его на соответствие значению, использованному для создания поля __VIEWSTATE. Если значения не совпадают, запрос отклоняется как потенциально вредоносный.
Примером сбоя, связанного с ViewStateUserKey, может служить клиент, у которого в браузере открыты две вкладки. Клиент входит в систему как пользователь A, и на первой вкладке отображается страница с __VIEWSTATE, свойство ViewStateUserKey которого содержит значение "Пользователь A". На второй вкладке клиент выходит из системы, а затем снова входит как пользователь B. Клиент возвращается на первую вкладку и отправляет форму. Свойство ViewStateUserKey может содержать имя "Пользователь B" (поскольку именно об этом говорит файл cookie проверки подлинности клиента). Однако поле __VIEWSTATE, отправленное клиентом, содержит "Пользователь A". Это несоответствие приводит к сбою.
Решение 4a. Убедитесь, что параметр ViewStateUserKey задан правильно
Если приложение использует свойство ViewStateUserKey, убедитесь, что значение свойства одинаково как при создании состояния представления, так и при его использовании. Если используется имя текущего вошедшего в систему пользователя, убедитесь, что он по-прежнему выполнил вход в систему и что удостоверение пользователя не изменилось на момент обратной записи. Если используется идентификатор сеанса текущего пользователя, убедитесь, что время ожидания сеанса не истекло.
Если вы работаете в среде фермы, убедитесь, что <элементы machineKey> совпадают. Инструкции по созданию этих элементов см. в приложении A .
Приложение A. Создание <элемента machineKey>
Примечание
Предупреждение системы безопасности
Существует множество веб-сайтов, которые генерируют <элемент machineKey> для вас одним нажатием кнопки. Никогда не используйте <элемент machineKey,> полученный с одного из этих сайтов. Невозможно узнать, были ли эти ключи созданы безопасно или они записываются в секретную базу данных. Используйте только <те элементы конфигурации machineKey> , которые вы создали сами.
Чтобы самостоятельно создать <элемент machineKey>, можно использовать следующий сценарий Windows PowerShell:
# Generates a <machineKey> element that can be copied + pasted into a Web.config file.
function Generate-MachineKey {
[CmdletBinding()]
param (
[ValidateSet("AES", "DES", "3DES")]
[string]$decryptionAlgorithm = 'AES',
[ValidateSet("MD5", "SHA1", "HMACSHA256", "HMACSHA384", "HMACSHA512")]
[string]$validationAlgorithm = 'HMACSHA256'
)
process {
function BinaryToHex {
[CmdLetBinding()]
param($bytes)
process {
$builder = new-object System.Text.StringBuilder
foreach ($b in $bytes) {
$builder = $builder.AppendFormat([System.Globalization.CultureInfo]::InvariantCulture, "{0:X2}", $b)
}
$builder
}
}
switch ($decryptionAlgorithm) {
"AES" { $decryptionObject = new-object System.Security.Cryptography.AesCryptoServiceProvider }
"DES" { $decryptionObject = new-object System.Security.Cryptography.DESCryptoServiceProvider }
"3DES" { $decryptionObject = new-object System.Security.Cryptography.TripleDESCryptoServiceProvider }
}
$decryptionObject.GenerateKey()
$decryptionKey = BinaryToHex($decryptionObject.Key)
$decryptionObject.Dispose()
switch ($validationAlgorithm) {
"MD5" { $validationObject = new-object System.Security.Cryptography.HMACMD5 }
"SHA1" { $validationObject = new-object System.Security.Cryptography.HMACSHA1 }
"HMACSHA256" { $validationObject = new-object System.Security.Cryptography.HMACSHA256 }
"HMACSHA385" { $validationObject = new-object System.Security.Cryptography.HMACSHA384 }
"HMACSHA512" { $validationObject = new-object System.Security.Cryptography.HMACSHA512 }
}
$validationKey = BinaryToHex($validationObject.Key)
$validationObject.Dispose()
[string]::Format([System.Globalization.CultureInfo]::InvariantCulture,
"<machineKey decryption=`"{0}`" decryptionKey=`"{1}`" validation=`"{2}`" validationKey=`"{3}`" />",
$decryptionAlgorithm.ToUpperInvariant(), $decryptionKey,
$validationAlgorithm.ToUpperInvariant(), $validationKey)
}
}
Для приложений ASP.NET 4.0 вы можете просто вызвать Generate-MachineKey без параметров, чтобы сгенерировать <элемент machineKey> следующим образом:
PS> Generate-MachineKey
<machineKey decryption="AES" decryptionKey="..." validation="HMACSHA256" validationKey="..." />
Приложения ASP.NET 2.0 и 3.5 не поддерживают HMACSHA256. Вместо этого можно указать SHA1 для создания совместимого <элемента machineKey> следующим образом:
PS> Generate-MachineKey -validation sha1
<machineKey decryption="AES" decryptionKey="..." validation="SHA1" validationKey="..." />
Как только у вас есть <элемент machineKey> , вы можете поместить его в файл Web.config. Элемент <machineKey> допустим только в Web.config файле в корне приложения и недопустим на уровне вложенных папок.
<configuration>
<system.web>
<machineKey ... />
</system.web>
</configuration>
Полный список поддерживаемых алгоритмов см. в справке Generate-MachineKey из Windows PowerShell запроса.
Приложение Б. Подготовка реестра для сохранения автоматически созданных разделов
По умолчанию, поскольку ASP. NET Автоматически созданные ключи сохраняются в реестре HKCU. Эти разделы могут быть потеряны, если профиль пользователя не был загружен в рабочий процесс IIS, а затем пул приложений перезапускается. Этот сценарий может повлиять на поставщиков общего размещения, которые запускают пулы приложений как стандартные учетные записи пользователей Windows.
Чтобы обойти эту ситуацию, ASP.NET включает сохранение автоматически созданных ключей в реестре HKLM, а не в реестре HKCU. Обычно это выполняется с помощью служебной программы aspnet_regiis (см. инструкции в разделе "Решение 2a: использование служебной программы aspnet_regiis"). Однако для администраторов, которые не хотят запускать эту служебную программу, вместо нее можно использовать следующий сценарий Windows PowerShell:
# Provisions the HKLM registry so that the specified user account can persist auto-generated machine keys.
function Provision-AutoGenKeys {
[CmdletBinding()]
param (
[ValidateSet("2.0", "4.0")]
[Parameter(Mandatory = $True)]
[string] $frameworkVersion,
[ValidateSet("32", "64")]
[Parameter(Mandatory = $True)]
[string] $architecture,
[Parameter(Mandatory = $True)]
[string] $upn
)
process {
# We require administrative permissions to continue.
if (-Not (new-object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Error "This cmdlet requires Administrator permissions."
return
}
# Open HKLM with an appropriate view into the registry
if ($architecture -eq "32") {
$regView = [Microsoft.Win32.RegistryView]::Registry32;
} else {
$regView = [Microsoft.Win32.RegistryView]::Registry64;
}
$baseRegKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $regView)
# Open ASP.NET base key
if ($frameworkVersion -eq "2.0") {
$expandedVersion = "2.0.50727.0"
} else {
$expandedVersion = "4.0.30319.0"
}
$aspNetBaseKey = $baseRegKey.OpenSubKey("SOFTWARE\Microsoft\ASP.NET\$expandedVersion", $True)
# Create AutoGenKeys subkey if it doesn't already exist
$autoGenBaseKey = $aspNetBaseKey.OpenSubKey("AutoGenKeys", $True)
if ($autoGenBaseKey -eq $null) {
$autoGenBaseKey = $aspNetBaseKey.CreateSubKey("AutoGenKeys")
}
# Get the SID for the user in question, which will allow us to get his AutoGenKeys subkey
$sid = (New-Object System.Security.Principal.WindowsIdentity($upn)).User.Value
# SYSTEM, ADMINISTRATORS, and the target SID get full access
$regSec = New-Object System.Security.AccessControl.RegistrySecurity
$regSec.SetSecurityDescriptorSddlForm("D:P(A;OICI;GA;;;SY)(A;OICI;GA;;;BA)(A;OICI;GA;;;$sid)")
$userAutoGenKey = $autoGenBaseKey.OpenSubKey($sid, $True)
if ($userAutoGenKey -eq $null) {
# Subkey didn't exist; create and ACL appropriately
$userAutoGenKey = $autoGenBaseKey.CreateSubKey($sid, [Microsoft.Win32.RegistryKeyPermissionCheck]::Default, $regSec)
} else {
# Subkey existed; make sure ACLs are correct
$userAutoGenKey.SetAccessControl($regSec)
}
}
}
В следующем примере показано, как подготовить соответствующие записи реестра HKLM для пула приложений, работающего от имени пользователя (это имя участника-пользователя Windows). example@contoso.com Этот пул приложений представляет собой 32-разрядный пул приложений, работающий под управлением CLR версии 2.0 (ASP.NET 2.0 или 3.5).
PS> Provision-AutoGenKeys -FrameworkVersion 2.0 -Architecture 32 -UPN "example@contoso.com"
Если же пул приложений является 64-разрядным пулом приложений под управлением CLR версии 4.0 (ASP.NET 4.0 или 4.5), команда будет следующей:
PS> Provision-AutoGenKeys -FrameworkVersion 4.0 -Architecture 64 -UPN "example@contoso.com"
Несмотря на то, что автоматически созданные ключи хранятся в HKLM, подраздел реестра, в котором хранится секретный криптографический материал каждой учетной записи пользователя, добавляется в список управления доступом (ACL), чтобы криптографические материалы не могли быть прочитаны другими учетными записями пользователей.
Приложение C. Шифрование элемента machineKey> в файлах <конфигурации
Администраторы серверов могут не захотеть, чтобы конфиденциальные данные, такие как материал ключа machineKey<>, отображались в файлах конфигурации в виде открытого текста. В этом случае администраторы могут решить воспользоваться функцией платформы .NET Framework, известной как «защищенная конфигурация». Эта функция позволяет шифровать определенные разделы файлов .config. Если содержимое этих конфигурационных файлов когда-либо будет раскрыто, содержимое этих разделов все равно останется секретным.
Краткий обзор защищенной конфигурации можно найти на веб-сайте MSDN. В ней также содержится руководство о защите <элементов connectionStrings> и <machineKey> файла Web.config.