Анализатор обнаружил ситуацию, при которой данные, полученные из внешнего источника, могут быть использованы для создания объекта при десериализации. Подобный код может быть причиной различных уязвимостей.
Небезопасная десериализация выделена в отдельную категорию рисков в OWASP Top 10 Application Security Risks 2017: A8:2017-Insecure Deserialization.
Рассмотрим синтетический пример:
[Serializable]
public class User
{
....
public bool IsAdmin { get; private set; }
....
}
private static User GetUserFromFile(string filePath)
{
User user = null;
using (var fileStream = new FileStream(filePath, FileMode.Open))
{
var soapFormatter = new SoapFormatter();
user = (User) soapFormatter.Deserialize(fileStream);
}
return user;
}
static void Main(string[] args)
{
Console.WriteLine("Please provide the path to the file.");
var userInput = Console.ReadLine();
User user = GetUserFromFile(userInput);
if (user?.IsAdmin == true)
// Performs actions with elevated privileges
else
// Performs actions with limited privileges
}
При запуске метода 'Main' консольное приложение запросит у пользователя путь до файла. После указания пути до файла, содержимое файла будет десериализовано в объект типа 'User'. Если получилось провести десериализацию объекта из файла и его свойство 'IsAdmin' равно 'true', то дальше будут произведены действия с повышенными привилегиями. В противном случае будут произведены действия с ограниченными привилегиями. Учитывая то, что данные из файла десериализуются SOAP сериализатором в объект типа 'User', имеется возможность увидеть структуру объекта, находящегося в файле:
<SOAP-ENV:Envelope xmlns:xsi=....
xmlns:xsd=....
xmlns:SOAP-ENC=....
xmlns:SOAP-ENV=....
xmlns:clr=....
SOAP-ENV:encodingStyle=....>
<SOAP-ENV:Body>
<a1:Program_x002B_User id="ref-1" xmlns:a1=....>
<_x003C_UserId_x003E_k__BackingField>1</_x003C_UserId_x003E_k__BackingField>
<_x003C_UserName_x003E_k__.... id="ref-3">Name</_x003C_UserName_x003E_k__....>
<_x003C_IsAdmin_x003E_k__....>false</_x003C_IsAdmin_x003E_k__....>
</a1:Program_x002B_User>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Благодаря этой информации злоумышленник может, например, изменить значение свойства 'IsAdmin' с приватным сеттером на 'true' вместо 'false':
<_x003C_IsAdmin_x003E_k__....>true</_x003C_IsAdmin_x003E_k__....>
Это позволит злоумышленнику при десериализации объекта из файла получить повышенные привилегии для десериализованного объекта. В результате, в программе будут выполнены действия, которые изначально не были доступны объекту из файла. Например, злоумышленник сможет украсть конфиденциальные данные или совершить вредоносные действия, которые до модификации объекта из файла были ему не доступны.
Чтобы избавится от этой уязвимости, необходимо убедиться, что злоумышленник при получении доступа к файлу не сможет узнать структуру объекта. Для этого следует использовать шифрование данных, записывающихся в файл. В C# имеется класс 'CryptoStream', который поможет в этом:
private static void SerializeAndEncryptUser(User user,
string filePath,
byte[] key,
byte[] iv)
{
using (var fileStream = new FileStream(filePath, FileMode.CreateNew))
{
using (Rijndael rijndael = Rijndael.Create())
{
rijndael.Key = key;
rijndael.IV = iv;
var encryptor = rijndael.CreateEncryptor(rijndael.Key, rijndael.IV);
using (var cryptoStream = new CryptoStream(fileStream,
encryptor,
CryptoStreamMode.Write))
{
var soapFormatter = new SoapFormatter();
soapFormatter.Serialize(cryptoStream, user);
}
}
}
}
Этот код зашифрует данные, полученные при сериализации объекта 'User', перед тем, как записать их в файл. При обработке содержимого файла в методе 'GetUserFromFile' перед десериализацией необходимо будет дешифровать данные, также используя 'CryptoStream':
private static User GetUserFromFile(string filePath, byte[] key, byte[] iv)
{
User user = null;
using (var fileStream = new FileStream(filePath, FileMode.Open))
{
using (Rijndael rijndael = Rijndael.Create())
{
rijndael.Key = key;
rijndael.IV = iv;
var decryptor = rijndael.CreateDecryptor(rijndael.Key,
rijndael.IV);
using (var cryptoStream = new CryptoStream(fileStream,
decryptor,
CryptoStreamMode.Read))
{
var soapFormatter = new SoapFormatter();
user = (User) soapFormatter.Deserialize(cryptoStream);
}
}
}
return user;
}
Таким образом структура и содержимое объекта из файла останутся неизвестны злоумышленнику, и он не сможет получить повышенные привилегии при помощи изменения значения свойства 'IsAdmin' в файле. Следовательно, в описанном примере будет устранена проблема небезопасной десериализации.
Для более надежной защиты от уязвимостей этого типа в дополнение к шифрованию сериализованных данных стоит придерживаться еще нескольких правил, которые перечислены в соответствующем разделе OWASP Top 10.
Анализатор также считает источниками небезопасных данных параметры методов, доступных из других сборок. Более подробно эта тема раскрыта в заметке "Почему важно проверять значения параметров общедоступных методов".
Рассмотрим пример:
public class DeserializationHelper
{
public T DesrializeFromStream<T>(Stream stream)
{
T deserializedObject = default;
using(var streamReader = new StreamReader(stream))
{
deserializedObject = DeserializeXml<T>(streamReader);
}
return deserializedObject;
}
private T DeserializeXml<T>(StreamReader streamReader)
{
return (T) new XmlSerializer(typeof(T)).Deserialize(streamReader);
}
}
В данном случае анализатор выдаст предупреждение низкого уровня достоверности при анализе исходного кода метода 'DesrializeFromStream' на вызов метода 'DeserializeXml'. Анализатор отследил передачу небезопасных данных из параметра 'stream' в конструктор 'StreamReader', а передачу объекта 'streamReader' - в метод 'Deserialize'.
Защититься от небезопасной десериализации в этом коде возможно тем же способом, что и в примере ранее, при помощи класса 'CryptoStream':
public class DeserializationHelper
{
public T DesrializeFromFile<T>(Stream stream, ICryptoTransform transform)
{
T deserializedObject = default;
using (var cryptoStream = new CryptoStream(stream,
transform,
CryptoStreamMode.Read))
{
using (var streamReader = new StreamReader(cryptoStream))
{
deserializedObject = DeserializeXml<T>(streamReader);
}
}
return deserializedObject;
}
private T DeserializeXml<T>(StreamReader streamReader)
{
return (T) new XmlSerializer(typeof(T)).Deserialize(streamReader);
}
}
Выявляемые диагностикой ошибки классифицируются согласно ГОСТ Р 71207–2024 как критические и относятся к типу: Ошибки непроверенного использования чувствительных данных (ввода пользователя, файлов, сети и пр.). |
Данная диагностика классифицируется как:
|
Взгляните на примеры ошибок, обнаруженных с помощью диагностики V5611. |