Огляд коду для забезпечення безпеки є критичним процесом для виявлення та пом'якшення вразливостей у вашому програмному забезпеченні. Поглиблений аналіз коду дозволяє інженерам з безпеки виявити недоліки, які можуть бути непомічені автоматизованими інструментами, тим самим забезпечуючи безпеку та стійкість додатків. У цій статті ми розглянемо, як проводити огляд коду для виявлення будь-яких вразливостей.
Далі в статті ми поглиблено розглянемо одну цікаву вразливість, що називається Небезпечна десеріалізація (Insecure Deserialization), і побачимо, як можна використовувати помилки огляду коду для експлуатації вразливостей небезпечної десеріалізації в різних форматах серіалізації.
Але спочатку треба зрозуміти ці два терміни: Source та Sink.
Source (Джерело): Джерело — це точка, звідки надходить ненадійний або потенційно шкідливий вхід. Це може бути введення користувача, дані з зовнішніх систем чи будь-який інший вхід, що потрапляє в межі програми.
Приклади джерел (Sources):
- Дані, надані користувачем (
$_GET
,$_POST
,$_COOKIE
в PHP). - Параметри запиту у веб-запитах.
- Завантаження файлів.
- Дані з зовнішніх API або сторонніх сервісів.
- Вхідні дані з командного рядка чи змінних середовища.
Приклад у PHP:
$username = $_GET['username']; // Джерело: Вхід користувача з рядка запиту URL
Приклад у Python:
search_term = input("Enter search term: ") # Джерело: Вхід, наданий користувачем через консоль
Sink (Приймач): Приймач — це місце, де використовується ненадійні дані, що може призвести до вразливості безпеки, якщо дані не були належним чином очищені або перевірені. Приймачі представляють собою операції або функції, які виконують дії з даними, наприклад, рендеринг виводу, виконання системних команд чи взаємодія з базами даних.
Приклади приймачів (Sinks):
- SQL запити.
- Рендеринг HTML.
- Операції з файлами (запис, видалення або переміщення файлів).
- Системні команди (через функції
exec
,system
чи subprocess). - Функції десеріалізації.
- Динамічна оцінка коду (
eval
,exec
).
Приклад у PHP:
$query = "SELECT * FROM users WHERE username = '$username'"; // Приймач: Виконання SQL запиту
$result = $conn->query($query);
Приклад у Python:
os.system(f"grep {search_term} /var/log/syslog") # Приймач: Виконання команди з ненадійними даними
Потік даних від джерела до приймача (Source-to-Sink Flow)
Термін потік від джерела до приймача описує шлях, яким ненадійні дані потрапляють від точки введення (джерела) до потенційно небезпечної операції (приймача). Розуміння цього потоку є критичним для виявлення та пом'якшення вразливостей.
Приклад у PHP:
$username = $_GET['username']; // Джерело: Вхід користувача
$query = "SELECT * FROM users WHERE username = '$username'"; // Приймач: Виконання SQL запиту
$result = $conn->query($query);
- Джерело (Source):
$_GET['username']
- Приймач (Sink): Виконання SQL запиту через
$conn->query($query)
Приклад у Python:
search_term = input("Enter search term: ") # Джерело: Вхід, наданий користувачем
os.system(f"grep {search_term} /var/log/syslog") # Приймач: Виконання команди
- Джерело (Source): Функція
input()
- Приймач (Sink): Функція
os.system()
Як захистити потік від джерела до приймача
- Перевірка введених даних: Переконайтесь, що дані з джерел відповідають очікуваним форматам, типам або діапазонам.
- Очищення даних: Видаляйте або екрануйте потенційно небезпечні символи до того, як дані потраплять до приймача.
- Використання безпечних функцій: Вибирайте безпечні функції (наприклад, підготовлені запити для SQL).
- Обмеження обсягу: Мінімізуйте кількість даних, що підконтрольні користувачеві, які передаються до критичних операцій.
Тепер, коли ми розглянули поняття джерела та приймача, давайте детальніше розглянемо кілька прикладів вразливостей у коді:
Десеріалізація в .NET
Вразливість: Небезпечна десеріалізація з використанням BinaryFormatter
.
// Уразливий код: Десеріалізація без перевірки
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
public class Program
{
public static void Main(string[] args)
{
// Прийом вхідних даних від користувача
byte[] input = Convert.FromBase64String(args[0]);
// Десеріалізація вхідних даних (уразливо)
BinaryFormatter formatter = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream(input))
{
var obj = formatter.Deserialize(ms); // Точка для експлуатації
Console.WriteLine("Десеріалізований об’єкт: " + obj.ToString());
}
}
}
Проблема з BinaryFormatter
полягає в його вроджених вразливостях, які роблять його небезпечним при роботі з вхідними даними, контрольованими користувачем.
Ключові проблеми з BinaryFormatter
- Відсутність перевірки введених даних:
BinaryFormatter
десеріалізує будь-які дані, які йому надаються, без перевірки типу або вмісту.- При обробці вхідних даних, контрольованих користувачем, шкідливі дані можуть використати цю відсутність перевірки для виклику непередбачених поведінок.
2. Виконання довільного коду:
BinaryFormatter
може викликати конструктори, методи або інший код під час процесу десеріалізації.- Зловмисники можуть створювати серіалізовані дані для виконання довільних команд або маніпулювання станом додатку.
3. Сплутування типів:
- Десеріалізація дозволяє створювати об’єкти довільних типів, що призводить до вразливостей сплутування типів.
- Наприклад, зловмисники можуть вставляти непередбачувані типи об’єктів в логіку програми.
4. Широка площа для атак:
BinaryFormatter
у .NET підтримує складні типи та об’єкти, що розширює площу для атак.- Багато класів у .NET Framework можуть бути використані для атаки, якщо десеріалізувати їх неправильно.
5. "Гаджети" для десеріалізації:
- Зловмисники можуть використовувати "ланцюги гаджетів" (експлуатовані класи, що існують у бібліотеках .NET) для виконання шкідливих дій під час десеріалізації.
- Інструменти на кшталт
ysoserial.net
генерують корисне навантаження, яке експлуатує ці гаджети для виконання довільного коду.
Експлуатація
Створіть корисне навантаження за допомогою ysoserial.net
, орієнтуючись на BinaryFormatter
.
ysoserial -f BinaryFormatter -g ObjectDataProvider -c "calc.exe" > payload.bin
Це корисне навантаження виконує calc.exe
на машині жертви після десеріалізації.
Вплив
- Виконання довільного коду на сервері.
Виправлення
- Уникайте використання
BinaryFormatter
для даних, контрольованих користувачем. Використовуйте безпечніші альтернативи, такі якSystem.Text.Json
, яка має строгий механізм перевірки схеми. - Реалізуйте перевірку введених даних і автентифікацію перед обробкою даних.
Приклад безпечного коду
// Безпечний код: Використовуйте безпечні методи серіалізації
using System;
using System.Text.Json;
public class Program
{
public static void Main(string[] args)
{
try
{
// Десеріалізація з використанням Json з перевіркою
var obj = JsonSerializer.Deserialize
**Дозволяє десеріалізацію довільних типів**:
- На відміну від `DataContractSerializer`, який серіалізує лише явно визначені типи, `NetDataContractSerializer` включає повну інформацію про тип у серіалізовані дані.
- Це означає, що процес десеріалізації не обмежує, які типи об'єктів можуть бути відновлені з серіалізованих даних.
**2. Дає змогу атаки через сплутування типів**:
- Зловмисники можуть створити корисне навантаження з шкідливими типами або об'єктами, щоб експлуатувати вразливості в програмі або її залежностях.
- Якщо ці типи містять небезпечну поведінку (наприклад, виконання команд, зміна даних або виклик винятків), програма може бути скомпрометована.
**3. Довіряє інформації про тип у корисному навантаженні**:
- Десеріалізатор припускає, що інформація про тип у корисному навантаженні є легітимною.
- Це довір'я може бути використано для десеріалізації об'єктів у непередбачувані або шкідливі типи, що дозволяє віддалене виконання коду (RCE) або зміну даних.
## Експлуатація
Зловмисник створює шкідливе корисне навантаження з довільним типом, наприклад, за допомогою інструментів, таких як `ysoserial.net`. Це корисне навантаження може виконувати шкідливі дії, наприклад, викликати системні команди.
1. **Створення корисного навантаження**: Створіть корисне навантаження за допомогою `ysoserial.net` або вручну, використовуючи ланцюг гаджетів для десеріалізації:
ysoserial -f NetDataContractSerializer -g ObjectDataProvider -c "calc.exe" > payload.bin
```
2. Надсилання корисного навантаження: Перетворіть корисне навантаження на Base64 і надішліть його у вразливий метод:
string base64Payload = Convert.ToBase64String(File.ReadAllBytes("payload.bin")); NetDataContractExample.DeserializePayload(base64Payload);
3. Результат: Процес десеріалізації виконує корисне навантаження, наприклад, запускає calc.exe
або виконує команди, вказані зловмисником.
Вплив
- Виконання довільного коду за допомогою гаджетів десеріалізації.
Виправлення
- Уникайте використання
NetDataContractSerializer
:
- Замість нього використовуйте безпечніші альтернативи, такі як
DataContractSerializer
абоSystem.Text.Json
.
2. Обмежте дозволені типи:
- Якщо використання
NetDataContractSerializer
є необхідним, явно обмежте десеріалізацію лише до відомих безпечних типів.
NetDataContractSerializer serializer = new NetDataContractSerializer(); serializer.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
3. Перевірка введених даних:
- Санітуйте та перевіряйте всі серіалізовані дані перед десеріалізацією.
4. Використовуйте підписані корисні навантаження:
- Вимагайте, щоб корисні навантаження були підписані та перевірені криптографічно для забезпечення їх цілісності та автентичності.
5. Увімкніть безпечну конфігурацію:
- Якщо можливо, вимкніть інформацію про типи в серіалізованих корисних навантаженнях.
Приклад безпечного коду
Використання DataContractSerializer
для обробки серіалізації з явними визначеннями типів:
using System;
using System.IO;
using System.Runtime.Serialization;
[DataContract]
public class SafeObject
{
[DataMember]
public string Data { get; set; }
}
public class SecureDeserializationExample
{
public static void DeserializePayloadSecurely(string base64Payload)
{
byte[] payload = Convert.FromBase64String(base64Payload);
DataContractSerializer serializer = new DataContractSerializer(typeof(SafeObject));
using (MemoryStream ms = new MemoryStream(payload))
{
// Безпечна десеріалізація
var deserializedObject = (SafeObject)serializer.Deserialize(ms);
Console.WriteLine("Десеріалізовані дані: " + deserializedObject.Data);
}
}
}
Неправильне оброблення десеріалізації JSON
## Вразливий код:
using System;
using Newtonsoft.Json;
public class JsonDeserialization
{
public static void DeserializeJson(string jsonInput)
{
// Десеріалізація JSON без перевірки введених даних
var obj = JsonConvert.DeserializeObject(jsonInput);
Console.WriteLine("Десеріалізований об'єкт: " + obj.ToString());
}
}
```
Експлуатація:
- Експлуатація динамічного JSON для впровадження непередбачених структур:
{
"$type": "System.Windows.Data.ObjectDataProvider, PresentationFramework",
"MethodName": "Start",
"ObjectInstance": {
"$type": "System.Diagnostics.Process, System",
"StartInfo": {
"FileName": "calc.exe"
}
}
}
Вплив:
- Виконання довільних команд за допомогою розв'язування типів.
Виправлення:
- Вимкніть вказівки типів, налаштувавши серіалізатор
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.None
};
var obj = JsonConvert.DeserializeObject(jsonInput, settings);
4. Експлуатація інтерфейсу IFormatter
Вразливий код:
using System;
using System.IO;
using System.Runtime.Serialization;
public class IFormatterExample
{
public static void DeserializeInput(string base64Input)
{
byte[] inputBytes = Convert.FromBase64String(base64Input);
IFormatter formatter = new BinaryFormatter();
using (MemoryStream stream = new MemoryStream(inputBytes))
{
// Вразлива десеріалізація
var obj = formatter.Deserialize(stream);
Console.WriteLine("Десеріалізований об'єкт: " + obj.ToString());
}
}
}
Експлуатація:
- Зловмисник використовує реалізацію
BinaryFormatter
для виконання ланцюгів гаджетів.
Вплив:
- Той самий, що і в першому прикладі (довільне виконання коду).
Виправлення:
- Уникайте використання інтерфейсу
IFormatter
з ненадійними даними.
Кращі практики для мінімізації вразливостей десеріалізації
- Уникайте небезпечних серіалізаторів:
- Уникайте використання
BinaryFormatter
,NetDataContractSerializer
та подібних серіалізаторів для введених користувачем даних.
2. Використовуйте альтернативи:
- Використовуйте
System.Text.Json
,DataContractSerializer
абоXmlSerializer
з явними схемами.
3. Забезпечте перевірку типів:
- Обмежте дозволені типи під час десеріалізації, щоб запобігти ланцюгам гаджетів.
4. Реалізуйте перевірку введених даних:
- Санітуйте та перевіряйте всі введені дані перед обробкою.
5. Аудит і забезпечення безпеки залежностей:
- Перевіряйте сторонні бібліотеки на наявність потенційних вразливостей десеріалізації.
Cybersecurity #ApplicationSecurity #OWASP #SecureCoding #DevSecOps #WebSecurity #MobileSecurity #SourceCodeReview
Перекладено з: Secure Source Code Review 1 : Insecure Deserialisation + Source & Sink