Зловмисники атакують іменовані канали: як захистити міжпроцесну взаємодію в Windows

Зловмисники атакують іменовані канали: як захистити міжпроцесну взаємодію в Windows 1

Іменовані канали (Named pipes) є поширеним вибором для комунікації між додатками, що працюють на одному комп’ютері з Windows. Вони швидкі, підтримуються безпосередньо операційною системою і добре підходять для обміну даними між службами Windows, настільними програмами, фоновими агентами, утилітами командного рядка та процесами в системному треї.

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

Локальне не означає довірене

Іменовані канали часто вважаються приватними, оскільки використовуються для комунікації між програмами на одному комп’ютері. Однак таке припущення є небезпечним.

На робочій станції Windows можуть працювати процеси з правами LocalSystem, адміністраторів, стандартних користувачів, облікових записів служб, а також у окремих інтерактивних чи віддалених сесіях. Крім того, можуть бути встановлені сторонні програми, скрипти, діагностичні інструменти або шкідливе програмне забезпечення, що працює під компрометованим обліковим записом. Будь-який процес, знаючи ім’я каналу та маючи достатні права доступу, може спробувати підключитися. Windows не знає за замовчуванням, який виконуваний файл розробник мав на увазі для використання каналу.

З цієї причини іменований канал слід розглядати як відкритий локальний інтерфейс. Перед обробкою запиту програма повинна визначити, хто підключився, які дії дозволені цій особі, і чи є надані дані безпечними.

Межі ідентифікації, контролю доступу та привілеїв

Ризик є найбільшим, коли привілейована служба Windows спілкується з менш привілейованою настільною програмою.

Служба, що працює під LocalSystem, може модифікувати захищені файли та ключі реєстру, запускати процеси, змінювати конфігурацію системи, отримувати доступ до даних інших користувачів або спілкуватися з драйверами ядра. Коли ці операції доступні через іменований канал, він стає API для привілейованої функціональності.

Успішне підключення підтверджує лише те, що клієнту було дозволено відкрити канал. Це не доводить, що:

  • клієнт є очікуваною програмою;
  • підключений користувач авторизований;
  • запитувана операція дозволена;
  • надана команда безпечна.

Таким чином, дозволи для каналу мають бути визначені явно та обмежені найменшим відповідним набором ідентифікаторів. Широкі дозволи для Everyone, Authenticated Users або всіх інтерактивних користувачів можуть дозволити непов’язаним процесам отримати доступ до каналу.

Автентифікація та авторизація також повинні бути розділені. Користувач може мати дозвіл на запит статусу служби, але не на її зупинку, зміну захищених налаштувань, запуск процесів або доступ до довільних файлів. Чутливі команди слід авторизувати індивідуально.

Імітація (Impersonation) може допомогти, виконуючи операції в контексті безпеки клієнта, але її слід використовувати обережно. Сервер повинен перевіряти, чи успішно відбулася імітація, обмежувати дії, що виконуються під час імітації, і завжди відновлювати свою початкову ідентичність.

Недовірені сервери, команди та дані

Клієнт повинен перевіряти сервер так само, як сервер перевіряє клієнта.

Передбачуване ім’я каналу є лише ідентифікатором. Це не секрет і не доводить, який процес створив канал. Зловмисник може створити канал з очікуваним ім’ям до запуску легітимного сервера, що призведе до підключення клієнта до процесу, контрольованого зловмисником.

Опція `first-pipe-instance` може допомогти виявити, що ім’я вже зайнято, але вона не замінює належний контроль доступу або перевірку ідентичності сервера.

Повідомлення, отримані через канал, також слід розглядати як ненадійний ввід. Навіть автентифікований клієнт може надіслати:

  • пошкоджені або надмірно великі корисні навантаження;
  • недійсні шляхи до файлів або реєстру;
  • непідтримувані комбінації команд;
  • серіалізовані об’єкти з помилками;
  • значення, призначені для виклику умов помилок.

Привілейована служба, яка перетворює такий ввід безпосередньо у файлові, реєстрові, процесні операції або команди, може стати “заплутаним заступником”: зловмисник надає інструкцію, а служба надає привілеї.

Запити повинні використовувати суворе формування повідомлень, обмежені розміри, списки дозволених команд, валідацію схеми, нормалізацію шляхів, авторизацію для конкретних операцій та безпечну обробку помилок.

Доступність та віддалена доступність

Безпека іменованих каналів не обмежується лише ескалацією привілеїв та неавторизованими командами.

Шкідливий або несправний процес може багаторазово підключатися, утримувати з’єднання відкритими, надсилати неповні повідомлення або надсилати запити, що споживають надмірні ресурси процесора, пам’яті або ядра. Сервер повинен використовувати обмеження з’єднань, тайм-аути, скасування, обмежені розміри повідомлень, контрольовану паралельність та обмеження частоти запитів, де це доречно.

Також небезпечно припускати, що кожен іменований канал доступний лише з локального комп’ютера. Іменовані канали Windows можуть підтримувати віддалений доступ у деяких конфігураціях.

Канали, призначені виключно для локального міжпроцесного зв’язку (IPC), повинні явно блокувати мережеві ідентифікатори, такі як NT AUTHORITYNETWORK, або використовувати механізм, що гарантує виключно локальну комунікацію.

Правильна модель загроз є простою: кожне з’єднання іменованого каналу слід вважати потенційно ворожим, доки не будуть перевірені ідентифікатор клієнта або сервера, дозволи, запитувана операція та вміст повідомлення.

Коли іменований канал стає межею безпеки

Іменований канал стає межею безпеки, коли процеси на його кінцях працюють з різними привілеями або під різними рівнями довіри.

Поширеним прикладом є служба Windows, що працює під LocalSystem, і настільна програма, що працює під обліковим записом стандартного користувача. Служба може модифікувати захищені файли та ключі реєстру, запускати процеси, змінювати загальносистемну конфігурацію, отримувати доступ до даних, що належать іншим користувачам, або спілкуватися з драйвером ядра. Настільна програма зазвичай не може виконувати ці операції безпосередньо.

Коли служба приймає команди через іменований канал, канал стає інтерфейсом до цих привілейованих можливостей. Будь-яка слабкість у дозволах каналу, перевірках ідентифікації, валідації команд або логіці авторизації може дозволити ненадійному локальному процесу зловживати привілеями служби.

Успішне підключення не доводить, що клієнт є очікуваною програмою. Воно лише підтверджує, що процес, який підключився, мав достатньо дозволів для відкриття каналу. Інший процес, що працює під тим самим обліковим записом користувача, може мати точно такий самий доступ. Тому сервер повинен перевіряти безпекову ідентифікацію, що стоїть за підключенням, а не покладатися на ім’я процесу, шлях до виконуваного файлу або секретність імені каналу.

Сервер також повинен авторизувати кожну операцію окремо. Клієнту, якому дозволено запитувати стан служби, не повинно автоматично дозволятися зупиняти службу, модифікувати захищену конфігурацію, запускати процес або запитувати доступ до довільного файлу.

Це розходження особливо важливе, коли сервер обробляє шляхи, аргументи командного рядка, розташування реєстру, імена виконуваних файлів або серіалізовані команди, керовані клієнтом. Без суворої валідації служба може стати “заплутаним заступником”: клієнт обирає дію, але привілейована служба її виконує.

Наприклад, на вигляд нешкідливий запит на кшталт:

Read file: C:ProgramDataProductstatus.json

може стати небезпечним, якщо клієнт може замінити шлях на:

Read file: C:WindowsSystem32configSAM

Така ж проблема стосується запитів на запуск процесів, видалення файлів, оновлення значень реєстру, встановлення компонентів або спілкування з драйвером. Служба повинна не просто перевіряти, чи команда синтаксично правильна. Вона повинна перевірити, чи підключена ідентифікація має дозвіл виконувати цю конкретну операцію над цим конкретним ресурсом.

Таким чином, безпечний сервер іменованих каналів повинен застосовувати кілька перевірок перед виконанням привілейованого запиту:

  • перевірити ідентифікатор Windows підключеного клієнта;
  • обмежити доступ через явний дескриптор безпеки каналу;
  • авторизувати кожну команду незалежно;
  • валідувати всі шляхи, аргументи, ідентифікатори та розміри корисних навантажень;
  • відхиляти непідтримувані або неоднозначні операції;
  • уникати надання загальнодоступної привілейованої функціональності.

Останній пункт є критичним. Команда на кшталт “записати це значення в будь-який ключ реєстру” створює набагато більшу поверхню атаки, ніж вузько визначена команда “оновіть це конкретне налаштування програми”. Чим загальнішим стає протокол каналу, тим більше він нагадує локальний API з привілеями — і тим ретельніше його слід захищати.

Правильний принцип проектування простий: сервер каналу ніколи не повинен виконувати операцію лише тому, що її запитав підключений клієнт. Він повинен виконувати операцію тільки після підтвердження того, хто її запитав, чи авторизована ця ідентифікація, і чи залишається запит у вузько визначених межах безпеки.

Контроль доступу та авторизація клієнта

Сервер іменованих каналів повинен вирішувати, хто може підключатися, перш ніж він почне обробляти повідомлення. Це починається з явного дескриптора безпеки, який надає доступ лише до необхідних ідентифікаторів Windows, таких як конкретний SID користувача, обліковий запис служби, група адміністраторів або сеанс входу.

DACL (список контролю доступу) каналу контролює доступ до обох кінців іменованого каналу. Коли клієнт намагається підключитися, Windows порівнює токен доступу клієнта та запитувані права з цим DACL. Покладатися на стандартний дескриптор ризиковано, оскільки його дозволи можуть бути ширшими, ніж потребує програма.

Доступ до каналу не автоматично авторизує кожну доступну команду. Клієнту може бути дозволено отримувати інформацію про статус, але відмовлено в дозволі на модифікацію конфігурації, запуск процесів або доступ до захищених файлів. Тому авторизацію слід виконувати для кожної чутливої ​​операції, а не лише один раз під час встановлення з’єднання.

Для локального зв’язку між програмами програми також можуть перевіряти процес, пов’язаний з протилежним кінцем каналу:

  • сервер може викликати GetNamedPipeClientProcessId;
  • клієнт може викликати GetNamedPipeServerProcessId.

Ці API Windows повертають ідентифікатор процесу, пов’язаний з підключеним клієнтом або сервером. Їх слід викликати лише після встановлення з’єднання через канал.

Наступний допоміжний код на C# отримує PID піра за допомогою нативних API Windows:

[DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GetNamedPipeClientProcessId(SafePipeHandle pipe, out uint clientProcessId);  [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GetNamedPipeServerProcessId(SafePipeHandle pipe, out uint serverProcessId);

Ми також можемо викликати іншу функцію з kernel32.dll, QueryFullProcessImageName, щоб отримати шлях до виконуваного файлу з дескриптора процесу, відкритого з PROCESS_QUERY_INFORMATION або PROCESS_QUERY_LIMITED_INFORMATION. Потім повернутий шлях можна порівняти з очікуваним розташуванням виконуваного файлу як додатковий крок перевірки.

На стороні сервера перевірка повинна відбуватися відразу після прийому з’єднання і перед читанням або виконанням команд:

Очікуваний виконуваний файл повинен знаходитися в каталозі, який стандартні користувачі не можуть модифікувати. Інакше зловмисник може замінити файл, зберігаючи очікуваний шлях.

Для сильнішої перевірки програма може додатково перевірити цифрову підпису Authenticode виконуваного файлу або порівняти його з затвердженим криптографічним хешем. Windows надає WinVerifyTrust для перевірки підписаних виконуваних файлів.

Однак перевірка PID та шляху до виконуваного файлу повинна залишатися вторинним контролем, а не основним механізмом авторизації. Дослідження безпеки продемонстрували способи підробки PID, що повідомляється для клієнта іменованого каналу, та способи передачі підключеного дескриптора каналу іншому процесу. Повернутий PID може ідентифікувати процес, який відкрив з’єднання, не доводячи, який процес зараз надсилає кожне повідомлення.

Таким чином, безпечна реалізація повинна поєднувати кілька елементів контролю:

  • явний та обмежувальний DACL каналу;
  • перевірка ідентичності або SID клієнта Windows;
  • авторизація кожної привілейованої команди;
  • сувора валідація вмісту повідомлення;
  • опціональна перевірка PID, шляху до виконуваного файлу, підпису або хешу як захист у глибині (defense in depth).

З’єднання слід відхиляти, коли перевірка ідентичності не вдається або не може бути завершена. Привілейована служба ніколи не повинна повертатися до прийняття запиту лише тому, що саме з’єднання через канал успішно відбулося.

Імітація та привілейовані операції

Сервер іменованих каналів часто працює з більшими привілеями, ніж підключений до нього клієнт. Наприклад, служба Windows може працювати під LocalSystem, тоді як клієнтська програма працює під обліковим записом стандартного користувача. Якщо служба виконує кожну запитувану операцію під своєю власною ідентичністю, клієнт може опосередковано отримати доступ до файлів, ключів реєстру, процесів та системних ресурсів, до яких він не міг отримати доступ безпосередньо.

Імітація іменованого каналу дозволяє серверу тимчасово виконувати код у контексті безпеки підключеного клієнта. Windows тоді оцінює доступ до ресурсів, використовуючи токен клієнта, а не токен облікового запису служби.

У .NET, NamedPipeServerStream.RunAsClient надає контрольований спосіб імітувати підключеного клієнта:

server.WaitForConnection(); server.RunAsClient(() => {     string path = @"C:ProgramDataMyApplicationsettings.json";     // Доступ перевіряється з використанням ідентичності підключеного клієнта.     string content = File.ReadAllText(path);     ProcessClientData(content); });

Цей підхід корисний, коли клієнт повинен мати можливість виконати операцію лише за умови, що його власний обліковий запис Windows вже має дозвіл. Наприклад, імітація може використовуватися при читанні файлу, що належить користувачеві, отриманні доступу до ключа реєстру, специфічного для користувача, або перевірці, чи має клієнт доступ до захищеного ресурсу.

Однак імітація не є заміною авторизації. Сервер повинен все ще перевіряти, чи дозволено клієнту запитувати операцію. Імітація лише змінює контекст безпеки, під яким Windows виконує перевірки доступу; вона не визначає, чи є сама команда доцільною.

Привілейована служба також повинна уникати непотрібного перемикання між ідентичністю клієнта та ідентичністю служби. Розглянемо запит, який просить службу прочитати файл, а потім встановити його вміст як конфігурацію.

Файл може бути прочитаний під час імітації клієнта, але встановлення може відбутися пізніше під LocalSystem. У цьому випадку клієнт все ще може впливати на привілейовану операцію, навіть якщо частина запиту була оброблена під час імітації.

Безпечніший дизайн — розділити операцію на чітко визначені етапи:

  1. Автентифікувати та авторизувати клієнта.
  2. Валідувати всі шляхи, аргументи та дані, керовані клієнтом.
  3. Імітувати лише для операцій, які повинні використовувати дозволи клієнта.
  4. Повернутися до ідентичності служби перед виконанням вузько визначеної привілейованої роботи.
  5. Повторно валідувати будь-які дані, що переходять від імітованого етапу до привілейованого етапу.

Область дії імітації повинна бути якомога меншою. Тривала робота, зворотні виклики, асинхронні операції та непов’язана логіка служби не повинні виконуватися під ідентичністю клієнта.

Коли використовуються нативні API Windows, застосовується той самий шаблон:

[DllImport("advapi32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool ImpersonateNamedPipeClient(SafePipeHandle pipe);  [DllImport("advapi32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool RevertToSelf();

Сервер повинен перевірити, чи ImpersonateNamedPipeClient був успішним, і завжди повинен викликати RevertToSelf у блоці finally:

if (!ImpersonateNamedPipeClient(server.SafePipeHandle)) {     throw new Win32Exception(Marshal.GetLastWin32Error()); } try {     // Виконується під контекстом безпеки підключеного клієнта.     PerformClientScopedOperation(); } finally {     if (!RevertToSelf())     {         throw new Win32Exception(Marshal.GetLastWin32Error());     } }

Обробка збоїв є критичною. Якщо імітація не вдається, а служба продовжує обробку, операція може виконатися під початковою привілейованою ідентичністю служби. Тому невдала спроба імітації повинна призвести до відхилення запиту, а не до мовчазного повернення до облікового запису сервера.

Цей самий принцип застосовується після імітації. Програма повинна надійно відновити свою початкову ідентичність перед обробкою іншого клієнта або виконанням непов’язаної роботи. Інакше пізніші операції можуть випадково виконатися під контекстом попереднього клієнта.

Привілейовані команди каналу також повинні бути вузькими та специфічними для призначення. Команда на кшталт:

Write any value to any registry key

створює набагато більшу поверхню атаки, ніж:

Update the application's approved policy setting

Служба не повинна надавати загальний доступ до файлів, модифікацію реєстру, створення процесів або виконання команд лише тому, що вона може виконувати ці операції. Кожна привілейована команда повинна визначати, до яких ресурсів можна отримати доступ, які значення приймаються, і які ідентифікатори клієнтів можуть її викликати.

Імітація найбільш ефективна, коли використовується як один шар у ширшій архітектурі безпеки. Сервер повинен все ще забезпечувати обмежувальні дозволи для каналу, перевіряти підключеного клієнта, авторизувати кожну команду, валідувати кожен запит і зберігати привілейовані операції вузько визначеними.

Розгляд повідомлень каналу як ненадійного вводу

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

З цієї причини кожне повідомлення, отримане через іменований канал, слід розглядати як ненадійний ввід. Сервер повинен перевірити як структуру повідомлення, так і запитувану ним операцію перед виконанням будь-якої привілейованої дії.

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

PipeRequest request = Deserialize(data); File.WriteAllText(request.Path, request.Content);

Навіть коли request має очікувану структуру, значення, такі як Path і Content, залишаються керованими клієнтом. Таким чином, привілейована служба може бути навчена перезаписувати файли поза каталогом програми, модифікувати захищену конфігурацію або споживати надмірний дисковий простір.

Безпечніший підхід — надавати вузько визначені команди та перевіряти кожне поле:

private static void ProcessRequest(PipeRequest request) {     if (request == null) throw new InvalidDataException("The request is missing.");      switch (request.Command)     {         case PipeCommand.UpdateConfiguration:             ValidateConfiguration(request.Configuration);             UpdateApprovedConfiguration(request.Configuration);             break;         case PipeCommand.GetStatus:             ReturnApplicationStatus();             break;         default:             throw new InvalidDataException("Unsupported command.");     } }

Протокол повинен уникати загальнодоступних операцій, таких як:

WriteFile(path, content)
StartProcess(path, arguments)
SetRegistryValue(key, name, value)
ExecuteCommand(command)

Ці команди дозволяють клієнту вибирати як привілейовану операцію, так і її ціль. Віддавайте перевагу специфічним для програми запитам, керованим сервером, дозволена поведінка яких контролюється сервером:

UpdateApplicationConfiguration(configuration)
RequestApplicationRepair()
InstallApprovedUpdate(updateId)
GetServiceStatus()

Валідація структури та розміру повідомлення

З’єднання іменованого каналу є потоком байтів, якщо програма навмисно не використовує режим передачі повідомлень. Один виклик Read не гарантує отримання повного повідомлення програми, і сервер не повинен припускати, що межі читання відповідають межам запиту.

Протокол повинен визначати явне формування повідомлень, наприклад, заголовок фіксованого розміру, за яким слідує корисне навантаження з префіксом довжини:

[Версія][Команда][Довжина корисного навантаження]

Оголошена довжина повинна бути перевірена перед виділенням пам’яті або читанням корисного навантаження:

private const int MaxMessageSize = 1024
• 1024; // 1MB private static async Task<byte[]> ReadPayloadAsync( Stream pipe, int payloadLength, CancellationToken cancellationToken) { if (payloadLength < 0 || payloadLength > MaxMessageSize) throw new InvalidDataException("Invalid payload length."); byte[] payload = new byte

; int offset = 0; while (offset < payload.Length) { int read = await pipe.ReadAsync(payload, offset, payload.Length - offset, cancellationToken); if (read == 0) throw new EndOfStreamException("The pipe was closed before the message was complete."); offset += read; } return payload; }

Без максимального розміру зловмисник може оголосити дуже велике корисне навантаження та змусити службу виділити надмірну пам’ять. Програма також повинна обмежувати розміри колекцій, довжину рядків, глибину вкладеності та кількість об’єктів, прийнятих десеріалізатором.

Валідація значень, а не лише типів

Успішна десеріалізація підтверджує лише те, що корисне навантаження можна перетворити на очікуваний тип об’єкта. Це не доводить, що значення прийнятні.

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

private static string ValidatePath(string suppliedPath, string allowedDirectory) {     string fullPath = Path.GetFullPath(suppliedPath);     string fullDirectory = Path.GetFullPath(allowedDirectory)         .TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;      if (!fullPath.StartsWith(fullDirectory, StringComparison.OrdinalIgnoreCase))     {         throw new UnauthorizedAccessException("The requested path is outside the allowed directory.");     }     return fullPath; }

Цей самий принцип застосовується до шляхів до реєстру, аргументів процесів, URL-адрес, ідентифікаторів, пакетів оновлень та значень конфігурації. Сервер повинен перевіряти кожне значення на відповідність списку дозволених або вузько визначеному діапазону, а не намагатися блокувати відомо небезпечні значення.

Перевірка шляхів також вимагає обережності щодо символічних посилань, з’єднань, точок повторного аналізу та перегонів “час перевірки/час використання” (time-of-check/time-of-use). Для чутливих файлових операцій валідації строкового шляху може бути недостатньо.

Безпечне відхилення недійсних запитів

Пошкоджені або неавторизовані повідомлення слід відхиляти без продовження часткової обробки. Сервер повинен уникати повернення стек-трасувань, внутрішніх шляхів, токенів безпеки або детальної інформації про винятки клієнту.

Помилки, що надсилаються через канал, повинні використовувати малий, контрольований набір кодів відповідей:

public enum PipeResult {     Success,     InvalidRequest,     Unauthorized,     UnsupportedCommand,     InternalError }

Детальна діагностична інформація може бути записана в захищені журнали служби, тоді як клієнт отримує лише інформацію, необхідну для обробки збою.

Кожен запит повинен проходити через передбачену послідовність:

  1. Читання обмеженого повідомлення.
  2. Перевірка версії протоколу та структури повідомлення.
  3. Автентифікація та авторизація підключеного клієнта.
  4. Перевірка кожного значення, керованого клієнтом.
  5. Виконання лише вузько визначеної операції.
  6. Повернення контрольованої відповіді.

Іменований канал є лише механізмом транспортування. Він не робить дані надійними, не гарантує правильне формування повідомлень і не запобігає надсиланню шкідливих запитів підключеним процесом. Отримуюча програма залишається відповідальною за застосування протоколу та захист кожної операції, що надається через нього.

Ризики відмови в обслуговуванні та віддаленого доступу

Кінцева точка іменованого каналу може бути захищена від неавторизованих команд, але залишатися вразливою до атак відмови в обслуговуванні. Зловмиснику не завжди потрібен дозвіл на виконання привілейованої операції; запобігання спілкуванню легітимних програм із службою може бути достатнім для порушення роботи продукту.

Шкідливий або несправний процес може багаторазово підключатися до каналу, займати всі доступні екземпляри, утримувати з’єднання відкритими без надсилання повних повідомлень або постійно перепідключатися після від’єднання. Як тільки всі екземпляри сервера зайняті, легітимні клієнти можуть не встановити з’єднання.

Такий самий ризик існує після прийняття з’єднання. Клієнт може надсилати дані надзвичайно повільно, оголошувати надмірно велике корисне навантаження, зупинятися на півдорозі повідомлення або закидати сервер справжніми, але дорогими запитами. Без обмежень така поведінка може вичерпати потоки, завдання, пам’ять, час процесора, дескриптори та внутрішні черги запитів.

Буфери іменованих каналів також споживають непагінований пул ядра. Тому кількість екземплярів каналу та обсяг буферизованих даних обмежені системними ресурсами. Створення необмеженої кількості екземплярів або вибір непотрібно великих буферів може призвести до вичерпання ресурсів.

Захисний сервер повинен встановлювати чіткі обмеження для:

  • одночасних з’єднань та екземплярів каналу;
  • розмірів повідомлень та полів;
  • часу, відведеного на встановлення та завершення запиту;
  • очікуючих запитів на клієнта;
  • паралельних дорогих операцій;
  • частоти запитів;
  • ємності внутрішніх черг.

Блокуючі операції повинні підтримувати скасування та не повинні чекати нескінченно, поки клієнт надішле більше даних. Коли клієнт перевищує ліміт часу, розміру або запиту, сервер повинен припинити це з’єднання та оперативно звільнити його ресурси.

Обмеження слід застосовувати до початку дорогої роботи. Наприклад, сервер повинен відхилити надмірний заявлений розмір корисного навантаження перед виділенням відповідного буфера. Аналогічно, авторизація та базова валідація запитів повинні відбуватися перед доступом до диска, створенням процесів, криптографічною роботою, запитами до бази даних або спілкуванням з драйвером ядра.

Програма також повинна уникати створення одного необмеженого робочого потоку для кожного з’єднання. Модель обмеженої паралельності запобігає вичерпанню пулу потоків процесу або створенню неконтрольованого зворотного порядку великою кількістю підключених клієнтів. Обмеження частоти можуть застосовуватися на з’єднання, процес, ідентифікатор користувача або сеанс входу, залежно від архітектури програми.

Однак заходи забезпечення доступності не повинні покладатися лише на PID клієнта. Процес може неодноразово перезапускатися, використовувати кілька процесів або встановлювати з’єднання під тим самим обліковим записом користувача. Кілька сигналів можуть потребувати спільного розгляду, і сервер повинен зберігати глобальне обмеження, навіть якщо присутні індивідуальні обмеження для клієнтів.

Інший, часто недооцінюваний ризик — віддалена доступність. Іменовані канали Windows не обов’язково обмежуються спілкуванням лише в межах локального комп’ютера. Вони також можуть підтримувати спілкування між комп’ютерами через мережу, і Microsoft стверджує, що іменовані канали можуть бути віддалено доступні, коли запущено службу Windows Server.

Це означає, що використання локального імені каналу саме по собі не гарантує виключно локальне спілкування. Канал, призначений для спілкування між локальною службою та локальною настільною програмою, повинен явно забезпечувати це вимога.

Нативні сервери каналів можуть вказувати PIPE_REJECT_REMOTE_CLIENTS, що змушує Windows автоматично відхиляти віддалені з’єднання. Без цієї опції віддалені клієнти можуть бути прийняті та оцінені відповідно до дескриптора безпеки каналу.

Список контролю доступу каналу також може заборонити доступ ідентифікатору NT AUTHORITYNETWORK. Там, де доступ повинен бути обмежений однією інтерактивною сесією, сервер може надати доступ до відповідного SID входу, а не до широких груп, що використовуються як локальними, так і віддаленими користувачами.

Ці захисти слід комбінувати, а не розглядати як альтернативи:

  • відхиляти віддалених клієнтів під час створення каналу, коли API це підтримує;
  • забороняти мережеві ідентифікатори в дескрипторі безпеки каналу;
  • надавати доступ лише необхідним користувачам або сеансам входу;
  • перевіряти ідентичність підключеного процесу;
  • застосовувати обмеження з’єднання, часу очікування, розміру та паралельності.

Захист від відмови в обслуговуванні та обмеження віддаленого доступу є частиною моделі безпеки каналу. Сервер іменованих каналів не є безпечним лише тому, що відхиляються неавторизовані команди. Він також повинен залишатися доступним для легітимних клієнтів і забезпечувати, чи дозволено з’єднанням походити з-за меж локального комп’ютера.

Проектування безпечної архітектури іменованих каналів

Безпечна архітектура іменованих каналів повинна мінімізувати як кількість відкритих операцій, так і обсяг привілейованого коду, який безпосередньо обробляє дані, керовані клієнтом. Канал повинен діяти як вузька межа комунікації, а не як загальнодоступний інтерфейс до операційної системи.

Практична архітектура розділяє обробку з’єднань, валідацію, авторизацію та привілейоване виконання:

Зловмисники атакують іменовані канали: як захистити міжпроцесну взаємодію в Windows 2

Клієнт ніколи не повинен спілкуватися безпосередньо з загальнодоступною привілейованою функціональністю. Замість цього він повинен надсилати вузько визначений запит до шлюзу каналу. Шлюз валідує формат повідомлення та передає лише структурований запит до шару авторизації. Привілейована робота починається лише після успішного завершення всіх перевірок безпеки.

Зберігайте протокол каналу вузьким

Протокол каналу повинен надавати доступ до бізнес-операцій, а не до примітивів операційної системи.

Наприклад, програма може законно потребувати запиту на оновлення політики, встановлення затвердженого оновлення, отримання статусу служби або оновлення конкретного значення конфігурації. Вона зазвичай не потребує необмежених команд для запису довільних файлів, зміни довільних ключів реєстру, запуску довільних виконуваних файлів або виконання інструкцій командного рядка.

Вузькі операції роблять авторизацію та валідацію практичними. Сервер знає, до яких ресурсів кожна команда може отримати доступ, які поля очікуються, і які ідентифікатори клієнтів можуть її викликати.

Хороший протокол повинен включати:

  • явну версію протоколу;
  • фіксований набір типів запитів;
  • унікальні ідентифікатори запитів;
  • обмежені розміри корисних навантажень;
  • передбачувані формати відповідей та помилок;
  • чіткі правила для непідтримуваних або пошкоджених повідомлень.

Сервер повинен відхиляти невідомі версії, команди, поля та стани, а не намагатися їх м’яко інтерпретувати.

Відокремлюйте доступ до з’єднання від дозволу команди

Дозвіл на підключення до каналу не повинен означати дозвіл на використання кожної функції, наданої через нього.

Дескриптор безпеки каналу повинен обмежувати, які ідентифікатори Windows можуть встановлювати з’єднання. Після з’єднання сервер повинен ідентифікувати клієнта та авторизувати кожну команду незалежно.

Це дозволяє підтримувати різні рівні довіри через одну й ту саму службу. Наприклад, звичайним користувачам може бути дозволено запитувати статус, тоді як лише адміністраторам або довіреному менеджерському процесу може бути дозволено змінювати захищені налаштування.

Для особливо чутливих операцій може бути краще використовувати окремі іменовані канали:

  • Product.Status – Інформація тільки для читання
  • Product.UserActions – Обмежені дії користувача
  • Product.Admin – Адміністративні дії
  • Product.Internal – Зв’язок довірених компонентів

Кожен канал тоді може мати власні правила контролю доступу, обмеження повідомлень та підтримуваний набір команд. Це зазвичай безпечніше, ніж розміщувати кожну операцію за одним великим протоколом і покладатися виключно на внутрішні перевірки команд.

Однак створення додаткових каналів автоматично не покращує безпеку. Кожен новий кінцевий пункт збільшує поверхню атаки і повинен бути захищений незалежно. Канали слід розділяти лише тоді, коли вони представляють справді різні межі довіри.

Використовуйте багаторівневу перевірку ідентичності

Жодна окрема перевірка ідентичності не повинна вважатися остаточною.

Архітектура може поєднувати:

  • обмежувальний DACL каналу;
  • SID підключеного користувача;
  • сеанс входу клієнта;
  • PID піра;
  • шлях до виконуваного файлу;
  • цифровий підпис виконуваного файлу;
  • запити/відповіді на рівні додатків;
  • авторизацію для конкретних операцій.

Перевірки PID та шляху до виконуваного файлу можуть допомогти виявити несподівані програми, але вони повинні залишатися елементами захисту в глибині. Процеси можуть змінюватися, дескриптори можуть успадковуватися або передаватися, а довірений процес сам може бути скомпрометований.

Найсильніші рішення повинні базуватися на ідентифікаторах безпеки Windows та вузько визначених дозволах, а не лише на apparentному імені виконуваного файлу.

Ізолюйте привілейоване виконання

Компонент, відповідальний за читання повідомлень каналу, повинен виконувати якомога менше привілейованої роботи.

Обробка з’єднань, десеріалізація, формування та базова валідація піддаються вводу, керованому зловмисником. Збереження цієї логіки окремо від привілейованих операцій зменшує вплив вразливості парсера або протоколу.

Шар привілейованих операцій повинен отримувати лише валідовані, строго типізовані інструкції. Він не повинен отримувати необроблені буфери повідомлень, довільні шляхи, командні рядки або серіалізовані об’єкти безпосередньо від клієнта.

Для високочутливих програм дизайн може піти далі, розділивши шлюз каналу та робочий процес з привілеями на різні процеси. Шлюз може працювати зі зниженими привілеями, валідувати вхідні запити та пересилати лише схвалені операції меншому привілейованому компоненту через другий обмежений канал.

Ця додаткова межа процесу збільшує складність, але може значно зменшити обсяг коду, спрямованого на атаку, що працює як LocalSystem або інший потужний обліковий запис.

Контролюйте життєвий цикл кожного з’єднання

Кожне прийняте з’єднання повинно мати чіткий та обмежений життєвий цикл:

  1. Прийняти з’єднання.
  2. Ідентифікувати та валідувати піра.
  3. Застосувати обмеження на рівні з’єднання.
  4. Прочитати обмежений запит.
  5. Авторизувати та валідувати запитувану операцію.
  6. Виконати схвалену дію.
  7. Повернути контрольовану відповідь.
  8. Від’єднатися або чекати наступного обмеженого запиту.

Сервер не повинен дозволяти неавтентифікованим клієнтам утримувати з’єднання нескінченно. Тайм-аути бездіяльності, терміни виконання запитів, обмеження з’єднань, скасування та обмежені черги повинні бути частиною архітектури з самого початку.

Тривалі операції не повинні непотрібно блокувати читача каналу. Служба може прийняти запит, призначити ідентифікатор операції та дозволити клієнту запитувати прогрес через окремий запит статусу. Це запобігає монополізації ресурсів сервера одним з’єднанням.

Зробіть сервер авторитетним

Клієнт повинен запитувати результат, тоді як сервер визначає, як цей результат буде досягнуто.

Наприклад, клієнт може запитувати встановлення затвердженого оновлення за ідентифікатором. Сервер повинен визначити розташування пакету, перевірити його підпис, визначити команду встановлення та забезпечити дотримання дозволеного призначення. Клієнт не повинен надавати шлях до виконуваного файлу, URL-адресу завантаження, аргументи командного рядка та цільовий каталог.

Це зберігає рішення, чутливі до безпеки, всередині довіреного компонента та зменшує кількість значень, керованих клієнтом, що перетинають межу привілеїв.

Сервер також повинен уникати довіри до рішень безпеки, раніше прийнятих клієнтом. Твердження на кшталт “користувач є адміністратором”, “цей файл підписаний” або “цей шлях безпечний” повинні бути незалежно перевірені сервером.

Аудитуйте діяльність, пов’язану з безпекою

Безпечна архітектура повинна записувати достатньо інформації для розслідування підозрілої поведінки без розкриття конфіденційних даних.

Корисні події аудиту включають:

  • відхилені з’єднання;
  • невдалі перевірки ідентичності;
  • неавторизовані команди;
  • пошкоджені або надмірно великі повідомлення;
  • повторні тайм-аути;
  • несподівані ідентифікатори процесів;
  • привілейовані операції та їхні результати;
  • незвичайна частота з’єднань або запитів.

Журнали повинні ідентифікувати користувача Windows, сеанс, PID піра, тип команди та результат, де це доречно. Сирі секрети, токени автентифікації та повні чутливі корисні навантаження не повинні записуватися в журнали.

Повторні збої можуть свідчити про атаку, але також можуть виявити дефектну версію клієнта або проблему розгортання. Тому дані аудиту повинні підтримувати як розслідування безпеки, так і усунення операційних несправностей.

Рекомендована архітектура

Для більшості привілейованих сценаріїв служб Windows, захищена конструкція складається з:

  • іменованого каналу тільки для локального використання з явним дескриптором безпеки;
  • окремих кінцевих точок для суттєво різних рівнів довіри;
  • перевірки як ідентичності Windows, так і піра процесу;
  • версіонованого, обмеженого за довжиною, специфічного для програми протоколу;
  • авторизації кожної команди;
  • суворої валідації кожного значення, керованого клієнтом;
  • коротких та ретельно контрольованих областей імітації;
  • невеликого рівня привілейованого виконання;
  • обмежених з’єднань, черг та часу виконання;
  • аудиту безпеки.

Центральний принцип полягає в тому, що іменований канал повинен надавати найменший можливий інтерфейс між рівнями довіри. Безпечна архітектура не намагається зробити довільні привілейовані операції безпечними. Вона взагалі уникає надання довільних привілейованих операцій.

Практичний контрольний список безпеки іменованих каналів

Перш ніж надавати доступ до функціональності програми через іменований канал, переконайтеся, що дизайн враховує кожну з наступних областей:

  • Визначте межу довіри. Розглядайте канал як відкритий локальний інтерфейс, особливо коли одна сторона працює з підвищеними привілеями.
  • Явно обмежте доступ до каналу. Використовуйте вузький дескриптор безпеки, а не покладайтеся на стандартні дозволи або широкі групи, такі як Everyone.
  • Відхиляйте віддалених клієнтів. Налаштуйте канал для виключно локальної комунікації та забороніть мережеві ідентифікатори, коли віддалений доступ не потрібен.
  • Перевіряйте обидва кінцеві пункти. Перевіряйте підключену ідентичність Windows і, де доречно, підтверджуйте PID піра, шлях до виконуваного файлу та цифровий підпис.
  • Не довіряйте імені каналу. Передбачуване ім’я ідентифікує кінцеву точку, але не автентифікує процес, який його створив.
  • Авторизуйте кожну команду. Дозвіл на підключення не повинен надавати доступ до всіх операцій, доступних через сервер.
  • Зберігайте протокол вузьким. Надавайте доступ до специфічних для програми дій, а не до довільних можливостей файлових, реєстрових, процесових або командних операцій.
  • Розглядайте всі повідомлення як ненадійні. Валідуйте формування, версію протоколу, тип команди, розмір корисного навантаження, значення полів, шляхи та кількість об’єктів.
  • Застосовуйте обмеження рано. Відхиляйте недійсні розміри та непідтримувані запити перед виділенням пам’яті або початком дорогої роботи.
  • Використовуйте імітацію обережно. Імітуйте лише тоді, коли операція повинна використовувати дозволи клієнта, зберігайте область дії невеликою та переходьте до закритого стану, якщо імітація не вдається.
  • Зберігайте привілейоване виконання ізольованим. Відокремлюйте парсинг та валідацію від коду, який виконує привілейовані операції.
  • Контролюйте використання ресурсів. Обмежуйте одночасні з’єднання, очікуючі запити, час бездіяльності, час виконання, глибину черги та частоту запитів.
  • Повертайте контрольовані помилки. Уникайте розкриття стек-трасувань, внутрішніх шляхів, токенів або інших чутливих деталей реалізації.
  • Аудитуйте події, пов’язані з безпекою. Записуйте відхилені з’єднання, невдалі перевірки ідентичності, пошкоджені запити, неавторизовані команди та привілейовані операції.
  • Закритий стан при помилці (Fail closed). Якщо ідентифікацію, авторизацію, валідацію або імітацію неможливо надійно завершити, відхиліть запит.

Безпечна реалізація іменованих каналів не повинна залежати від одного захисту. Найсильніший дизайн поєднує обмежувальний контроль доступу, перевірку кінцевих точок, авторизацію на рівні операцій, сувору валідацію введення та вузько визначену функціональність з привілеями.

Про автора:

Фарід Мустафаєв — розробник програмного забезпечення в ThreatLocker, який спеціалізується на розробці служб Microsoft Windows та кібербезпеці. Маючи понад 15 років досвіду роботи в галузі, він має глибокі знання в технологіях .NET, включаючи ASP.NET WebAPI, Windows Services, Windows Forms, WPF, RESTful API та низькорівневі внутрішні компоненти Windows. Він керував розробкою та зміцненням служб Windows, призначених для захисту систем від шкідливих програм та програм-вимагачів, включаючи роботу з інтеграціями на рівні ядра та кастомними покращеннями драйверів.

Раніше Мустафаєв працював технічним керівником, керуючи архітектурними рішеннями, наставляючи розробників та створюючи масштабовані, підтримувані системи. Його досвід також включає архітектури на основі мікросервісів та хмарні рішення на AWS з акцентом на доступність, продуктивність та безпеку в розподілених середовищах.

Спонсоровано та написано ThreatLocker.

Інформація підготовлена на основі матеріалів: www.bleepingcomputer.com

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *