Проверка безопасности приложения
Ручной направленный анализ кода как метод нефункционального тестирования. Эксперт играет роль нарушителя и систематически перебирает классы уязвимостей, ища их признаки в коде, конфигурациях, протоколах и интерфейсах исследуемого ПО.
Процесс №18 в жизненном цикле РБПО
В ГОСТ Р 56939-2024 разработка безопасного ПО разбита на 25 взаимосвязанных процессов. Большинство из них работают на этапах проектирования, кодирования и тестирования — там же, где работают SAST, DAST, фаззинг и код-ревью. Процесс №18 — «Проверка безопасности приложения» — стоит особняком: это последний рубеж, где живой эксперт ищет то, что пропустили автоматические инструменты.
Где этот процесс в жизненном цикле
Проверка безопасности приложения относится к нефункциональному тестированию: она проверяет не «работает ли ПО так, как задумано», а «нельзя ли использовать ПО против его пользователей». Запускается после функционального тестирования, обычно — на стадии готового кандидата в релиз. Таблица ниже — упрощённая модель водопадного жизненного цикла; в современном DevSecOps процессы перекрываются: SAST идёт на каждый коммит, фаззинг параллельно с экспертной проверкой, а сама проверка безопасности может запускаться итеративно.
| Когда запускается | Что проверяет | Кто выполняет |
|---|---|---|
| На этапе кодирования | Свойства кода (SAST, ревью) | Разработчик / SAST-инструмент |
| На этапе тестирования | Поведение работающего ПО (DAST, фаззинг) | Тестировщик / DAST-инструмент |
| Перед выпуском (процесс №18) | Стойкость к атакам в модели нарушителя | Эксперт по безопасности |
| В эксплуатации | Реакция на инциденты, мониторинг | SOC, AppSec-инженер |
Зачем нужен ручной анализ, если есть автоматика
Автоматические инструменты ищут шаблоны: то, что разработчик алгоритма заранее назвал уязвимостью. Эксперт ищет нестандартное: бизнес-логику, нарушенные инварианты, плохую модель угроз, специфику конкретного продукта. Эти классы дефектов хорошо описываются словесно, но плохо алгоритмизируются.
Главное отличие: SAST/DAST ловят то, что уже занесено в каталог их правил. Эксперт ловит то, что требует понимания смысла — нарушение бизнес-логики (можно перевести деньги без проверки баланса в одном API, тогда как соседний API проверяет), сломанный инвариант (двухфакторка обходится повторным открытием сессии), несоответствие декларируемой и фактической модели угроз. В каталоге CWE для этого зарезервирована категория CWE-840 Business Logic Errors — её сканер «открыть» может, а проверить — нет.
Представить себя нарушителем
Главная задача эксперта — представить себя нарушителем и попытаться нарушить безопасность исследуемой системы в рамках разумной модели нарушителя.
Под «разумной моделью нарушителя» обычно понимается:
- непривилегированный локальный пользователь — имеет доступ к рабочему столу, может запускать любые свои программы;
- удалённый неаутентифицированный пользователь — может посылать сетевые запросы по любым открытым портам;
- удалённый аутентифицированный пользователь — имеет валидную учётную запись с минимальными правами;
- инсайдер с доступом к коду — может изучить исходники, бинарники, конфигурации, файлы данных.
Эксперт не пытается «сломать всё подряд». Он принимает модель угроз, договорённую с заказчиком, и систематически проверяет, может ли нарушитель указанного уровня обойти декларируемые механизмы защиты.
Терминологическая оговорка. Перечень выше — учебная классификация по точке входа нарушителя. Не путать с уровнями возможностей нарушителя по Методическому документу ФСТЭК «Методика оценки угроз безопасности информации» от 05.02.2021 (базовый / базовый повышенный / средний / высокий) — там классификация по другому критерию.
Важное замечание докладчика. Все приведённые далее примеры — из реальных случаев исследования ПО. К какому продукту и какому разработчику относится каждый пример, не упоминается; участки скриншотов, раскрывающие эту информацию, замазаны.
Систематический перебор классов уязвимостей
Принципиально различных типов уязвимостей и мест их расположения не очень много. Работа эксперта в основном сводится к тому, чтобы последовательно перебирать типы и места уязвимостей и для каждого искать в анализируемом ПО признаки наличия уязвимости данного типа.
Из чего состоит метод
Знание класса
Эксперт держит в голове 11 типовых классов: от обхода аутентификации до некорректной ASLR. Это не словарь сигнатур, а «карта типичных мест».
Знание признака
Для каждого класса — набор быстрых индикаторов: подозрительно короткий хеш, конкатенация в SQL, незакрытое отладочное API, лишнее имя в выводе dbus-send.
Поверхностный осмотр
На каждое подозрительное место — 1–2 минуты. Файлы конфигурации, листинги директорий, вывод ps, netstat, strings, поиск по бинарникам.
Целевой эксперимент
Если поверхностный осмотр зацепил — эксперт пишет короткий PoC: подсовывает кавычку, генерирует «недопустимый» запрос, перехватывает трафик, пробует dbus-send от имени непривилегированного пользователя.
Почему это работает. Любая уязвимость — это нарушение какого-то предположения, сделанного автором кода. Предположений конечное число: «клиент валидирует данные», «никто не читает приватный ключ», «никто не знает протокол сервиса». Эксперт идёт по списку этих предположений и проверяет каждое.
Аутентификация
Самая массовая категория. Условно делится на четыре подкласса: возможен обход; применяется не та криптография; криптография применяется, но неправильно; криптография не применяется там, где нужна.
4.1
Возможности обхода
Самая частая ошибка — забыть проверить, прошла ли аутентификация. Бывает, что аутентификация реализуется только в клиентской программе, а сервер полагает: «если запрос пришёл — значит, всё в порядке». Бывает, что в протокол добавляют много повышающих безопасность дополнительных параметров, которые не проверяются или проверяются неправильно.
Часто обходы заметны при визуальном просмотре исходного текста. Если ПО небольшое — удобнее всего сгенерировать каждый поддерживаемый запрос сторонней программой и посмотреть, что будет. Если ПО большое, а протокол не очень сложный — пишется простой сканер:
for (перебор по параметрам запросов)
{ Send (текущий вариант);
Error = Receive ();
if (Error != NOT_IMPLEMENTED && Error != ACCESS_DENIED)
cout << параметры запроса;
}
Любой ответ, отличный от «не реализовано» или «доступ запрещён», — повод копнуть глубже. Псевдокод применим к протоколам с явной семантикой кодов ответа (HTTP, gRPC, SOAP). Для бинарных протоколов признак «эта команда что-то сделала» — таймаут или disconnect vs. структурированный ответ; критерий определяется по конкретному протоколу.
4.2
Применяется не та криптография
По документам для хеширования паролей применяется только ГОСТ Р 34.11-2012 (хеш-функция «Стрибог»), а реально — как повезёт. Или отечественная криптография вообще отсутствует, хотя должна присутствовать.
Реальный кейс: в одной отечественной сборке Linux отечественная криптография отсутствовала из-за ошибки при сборке — прилинковали не ту библиотеку.
Что делает эксперт: ищет по исходникам и бинарникам строки наподобие gost, crypt; смотрит экспортируемые символы через nm или strings; проверяет, какие библиотеки реально подгружены — ldd для ELF, dumpbin /dependents для PE; проверяет OID-ы алгоритмов в выпускаемых сертификатах. Версии заимствованных библиотек могут быть устаревшие, с неустранёнными уязвимостями.
4.3
Криптография применяется неправильно
Часто для хеширования паролей применяют слабые хеш-функции (например, MD5) без марканта/соли. В таких случаях сразу обращает на себя внимание необычно короткий хеш. Для проверки, используется ли соль, достаточно создать две учётные записи с одинаковыми паролями и сравнить хеши. Алгоритм шифрования/подписи в большинстве случаев проще всего установить с помощью одного из многочисленных онлайн-калькуляторов.
Попадались случаи, когда секретный ключ хранился в общедоступном файле (см. кейс №1 ниже).
Правило безопасности. Нормальному человеку не под силу самостоятельно реализовать нетривиальный криптопротокол. Если видите самописный код, делающий что-то криптографическое, — там, скорее всего, много ужасных ошибок. Единственный правильный подход — целиком заимствовать чужое программное решение, подготовленное профессионалами.
4.4
Криптография не применяется когда надо
Базовые ошибки, которые ловятся одним tcpdump или Wireshark:
- пароль передаётся по сети в открытом виде;
- по сети передают не пароль, а незашифрованный хеш — это позволяет перехватить его и передать повторно (pass-the-hash);
- для идентификации сетевого соединения используется легкоугадываемое значение, например порядковый номер.
Эксперт обязательно перехватывает трафик пары «клиент — сервер» во всех штатных сценариях: вход, выход, смена пароля, восстановление сессии. Странности видны сразу.
Найди уязвимость: шесть кейсов из реальной практики
Шесть фрагментов из реальных проверок: листинг файлов, хеш пароля, сетевой пакет, JWT-токен, окно файлового менеджера, вывод D-Bus. В каждом — характерный признак уязвимости, который опытный эксперт видит за секунды. Прочитайте, попробуйте найти проблему сами, потом раскройте подсказку.
/.../archive/install/unix/Release/bin/cert/:-rw-r--r-- 1 radmin radmin 1266 авг 31 10:21 10.168.250.201.crt
-rw-r--r-- 1 radmin radmin 1018 авг 31 10:21 10.168.250.201.key
-rw-r--r-- 1 radmin radmin 1702 авг 31 10:21 10.168.250.67.crt
-rw-r--r-- 1 radmin radmin 1241 авг 31 10:21 10.168.250.67.csr
-rw-r--r-- 1 radmin radmin 989 авг 31 10:21 10.168.250.67.key
-rw-r--r-- 1 radmin radmin 1675 авг 31 10:21 10.168.253.21.crt
-rw-r--r-- 1 radmin radmin 1316 авг 31 10:21 10.168.253.21.csr
-rw-r--r-- 1 radmin radmin 1058 авг 31 10:21 10.168.253.21.key
-rw-r--r-- 1 radmin radmin 1706 авг 31 10:21 privatekey.txt
-rw-r--r-- 1 radmin radmin 32 авг 31 10:21 rootCA.crt
-rw-r--r-- 1 radmin radmin 1456 авг 31 10:21 rootCA.key
-rw-r--r-- 1 radmin radmin 1702 авг 31 10:21 rootCA.srl
-rw-r--r-- 1 radmin radmin 18 авг 31 10:21 server.crt
-rw-r--r-- 1 radmin radmin 1332 авг 31 10:21 server.csr
-rw-r--r-- 1 radmin radmin 1142 авг 31 10:21 server.key
privatekey.txt с правами -rw-r--r-- доступен на чтение всем пользователям системы. Для секретного ключа JWT это эквивалентно компрометации механизма подписи: любой локальный пользователь может выпустить произвольные токены. Полнота воздействия зависит от того, что ещё валидирует сервер (kid, iss, aud, привязка токена к session_id в БД и т. п.). Кейс №4 показывает, что в этой системе сервер не валидирует ничего — поэтому утечка ключа здесь действительно даёт полный контроль над выпуском токенов.
Your String → Qwerty-123
MD5 Hash → f7f12464d28bec02951b713dd016cb58
current request user: Администратор
pwd: f7f12464d28bec02951b713dd016cb58
md5("Qwerty-123") = f7f12464d28bec02951b713dd016cb58. Соль не используется (две учётные записи с одинаковыми паролями имеют одинаковый хеш) — это CWE-759 (Use of a One-Way Hash without a Salt); сама MD5 как алгоритм для хранения паролей — CWE-328 (Use of Weak Hash). Радужные таблицы для MD5 без соли — массовый ширпотреб; пароль восстанавливается за секунды.
POST /spring-security-jdbc-example/login.html HTTP/1.1
Host: java-srv:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 75
Origin: http://java-srv:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
_csrf=fbd7a3d8-d139-4764-8e23-f12fee9f95e2&username=admin&password=password
password: либо тестовая учётка не была удалена в проде, либо политика паролей отсутствует.
{
"alg": "HS512",
"typ": "JWT"
}
.
{
"AccessLevel": "0",
"FileCommandId": "1",
"FileId": "346",
"FileSourceVersionId": "0",
"FileVersionId": "1",
"IpAddress": "Слава России!",
"UserGuid": "' or 1=1; drop table",
"iat": "меня заставляют отправлять сетевые пакеты",
"sub": "УПЯЧКА!!!111 ЖЕПЬ ЕБРИЛО! ХЫВТОНЕ ПОЛЯЧСЫ!!!!"
}
IpAddress, UserGuid, iat, sub на произвольный текст и SQL-инъекцию — токен принят сервером, инъекция дошла до БД. Это два независимых дефекта в разных слоях: (1) самописный JWT-парсер не проверяет даже типы полей — стандартное iat должно быть Unix-таймстампом, а здесь строка (CWE-20); (2) значения полей попадают в SQL-запросы напрямую, без параметризации (CWE-89). JWT-парсер и не должен ловить SQL-метасимволы — это задача SQL-слоя через PreparedStatement; но и пропускать структурно некорректный токен он не должен. Фоном работает кейс №1: секретный ключ подписи лежит в privatekey.txt с правами 644, поэтому подменить токен любому пользователю системы тривиально.
\..\..\..\..\Resources\Translation.xaml
# А затем посмотрел, куда он попал:
C:\tmp2\Resources\
Translation(1).xaml 14.04.2020
Translation(2).xaml 14.04.2020
Translation.xaml 18.08.2013
.. не санитизируются. Файл с переданным именем «выпрыгнул» из своего каталога и перезаписал ресурс приложения Translation.xaml от 18.08.2013. Аналогичным образом можно перезаписать конфиги, исполняемые файлы, файлы автозагрузки. То же самое демонстрируется фейковым именем \..\..\..\..\..\..\..\..\..\..\etc\shadow в Linux-варианте. Простая «проверка строки на наличие ..» здесь не помогает — обходится через ....\\, %5c..%5c, NTFS short names. Канонический способ защиты в Windows — PathCanonicalize/GetFullPathName с проверкой, что результат начинается с базового каталога; в .NET — Path.GetFullPath и сравнение префиксов.
$ dbus-send --print-reply --dest=org.freedesktop.DBus \
/org/freedesktop/DBus \
org.freedesktop.DBus.ListActivatableNames
... ru.hornshooves.app ...
$ dbus-send --print-reply --dest=ru.hornshooves.app / \
org.freedesktop.DBus.Introspectable.Introspect
<node>
<interface name="ru.hornshooves.app">
<method name="Hello">
<arg direction="out" type="s"/>
</method>
<method name="GetSomeName">
<arg direction="in" type="s"/>
<arg direction="in" type="u"/>
<arg direction="out" type="s"/>
</method>
</interface>
</node>
$ dbus-send --print-reply --dest=ru.hornshooves.app / \
ru.hornshooves.app.GetSomeName string:"aaa" uint32:666
dbus-send без флага --system): на ней авторизации почти нет по дизайну, поэтому любой процесс этого пользователя может вызвать GetSomeName с произвольными параметрами. Это типичная картина для open-source библиотек, чьи D-Bus-интерфейсы попадают в продукт «по умолчанию». Если бы метод регистрировался на системной шине (--system) — потребовалась бы polkit-политика и конфиг в /etc/dbus-1/system.d/; их отсутствие там было бы более грубой ошибкой. Реальный пример того же класса: в Astra Linux 1.6 D-Bus давал доступ к меню «Пуск» версии 1.4 — не уязвимость, но иллюстрация случайно вставленного НДВ «в хорошем смысле».
Что объединяет все шесть кейсов. Ни один из них не обнаруживается обычным SAST-сканером: листинг каталога, хеш в дампе БД, перехваченный трафик, payload JWT, окно мессенджера, вывод dbus-send — всё это контекст, а не код. Зато для эксперта, прошедшего через сотни проверок, каждый из этих шести случаев — характерный признак, который сразу запускает «копать сюда».
SQL-инъекции
Хороший обзор: Википедия — Внедрение SQL-кода. Тема старая, но встречается до сих пор почти в каждой проверке.
Простой эмпирический тест
Проще всего выявить, введя в форму ввода кавычку, апостроф и т. п. Появление синтаксической ошибки сигнализирует о SQL-инъекции:
Ошибка! При проверке пользователя обнаружена ошибка: [DBERRCODE(42601)]
ОШИБКА: ошибка синтаксиса (примерное положение: «А»)
СТРОКА 2: state = 'A'
Параллельно потенциальные SQL-инъекции часто видны визуально в коде. Признак — ручная конкатенация строк запроса:
// Так делать нельзя — конкатенация даёт SQL-инъекцию:
String sql = "SELECT * FROM Users WHERE UserName = '"
+ userName + "'";
var command = new SqlCommand(sql, connection);
// Так — корректно, через параметризацию:
public boolean login(String username, String password) {
PreparedStatement stmt = connection.prepareStatement(
"SELECT * FROM users WHERE username = ? AND password = ?");
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
...
}
При построении SQL-запроса должны применяться специальные средства, реализующие все необходимые проверки. Эти средства не должны быть самописными.
Оговорка к примеру. Сравнение пароля прямым SQL-запросом (как в коде выше) — анти-паттерн даже с параметризацией: пароль должен сравниваться через bcrypt/Argon2 в коде приложения, а в БД лежать солёный хеш. Пример иллюстрирует только защиту от инъекции, не корректную работу с паролями (см. раздел 4.3).
XSS
Хороший обзор: OWASP Community — XSS. В общем случае ищутся с большим трудом, но иногда их наличие очевидно.
Реальный пример из проверки — параметр back_url на странице авторизации:
// Безобидный вариант:
http://127.0.0.1/login.html?back_url=http://127.0.0.1/
// А что, если подставить javascript:?
http://127.0.0.1/login.html?back_url=javascript:%0Aalert(document.cookie)
В реальной системе ввод второго URL приводил к выполнению JavaScript в контексте сайта — alert с содержимым cookie. Дальше это превращается в кражу сессии любого пользователя, перешедшего по подделанной ссылке.
Универсальный индикатор. Ошибка при вводе в форму кавычки или апострофа может сигнализировать не только об SQL-инъекции, но и о XSS — на сервере что-то «не съело» специальный символ и вернуло его в ответ. Это сигнал копать.
Path traversal
В любом месте, где пользователю предлагается указать имя файла, эксперт пробует ввести имя с путём — и смотрит, что получится.
Что подставлять
../,./— переход на уровень выше;\в начале строки (актуально для Windows);/etc/shadow,C:\Windows\System32\config\SAM— абсолютные пути к чувствительным файлам;- странные кодировки Unicode для обхода фильтров (
%c0%ae%c0%ae,%252e%252e); - длинные пути с превышением
MAX_PATH; - NTFS-потоки данных вида
file.txt:hidden.
Реальный кейс — окно поиска файлов в защищённой системе. Введя \..\..\..\..\..\..\..\..\..\..\etc\shadow в поле поиска, эксперт получил окно, предлагающее «скачать /etc/shadow» (см. также кейс №5 выше — то же самое в формате имени присоединяемого файла).
Если делается санитизация — стоит проверить, можно ли её обойти необычными кодировками или какими-то другими ухищрениями. Стандартный тест: подставить %2e%2e%2f, ..%2f, %c0%ae%c0%ae%c0%af и сравнить, обрабатывает ли их сервер так же, как чистые ../.
Самописные драйверы и модули ядра
Любой самописный драйвер — гарантированный источник проблем. Три типичных подкласса.
9.1
Неисчерпаемый источник сбоев
Включаем максимальное количество функций драйвера/модуля и запускаем SyzKaller, либо пишем простейший «фаззер»:
for (;;)
for (i = 0; i < MAX_SYSCALL; i++)
{ if (NonFuzzable (i))
continue;
r1 = rand (); r2 = rand ();
r3 = rand (); r4 = rand ();
r5 = rand (); r6 = rand ();
syscall (i, r1, r2, r3, r4, r5, r6);
}
Результат предсказуем: BSOD на Windows, kernel oops на Linux, паника ядра — за минуты. Драйвер, который не выживает после такой проверки, не должен попадать в продукт.
9.2
Утечки данных (Heartbleed-паттерн)
Корректный код IOCTL-обработчика выставляет размер выходного буфера после валидации параметров каждой команды:
switch (code)
{ case CODE1:
if (!CheckParameters1 ()) return ошибка;
SetOutBufferSize (как хочет клиент);
return DoRequest1 ();
case CODE2:
if (!CheckParameters2 ()) return ошибка;
SetOutBufferSize (как хочет клиент);
return DoRequest2 ();
default:
return ошибка;
}
На каком-то этапе автор делает «безобидный» рефакторинг — выносит общий вызов SetOutBufferSize наружу. А спустя пару релизов добавляет команду без параметров:
SetOutBufferSize (как хочет клиент);
switch (code)
{ case CODE1: if (!CheckParameters1()) return ошибка; return DoRequest1 ();
case CODE2: if (!CheckParameters2 ()) return ошибка; return DoRequest2 ();
case CODE3_NO_PARAMETERS:
return DoRequest3 (); // нет CheckParameters — буфер уже выставлен!
default: return ошибка;
}
В результате при обработке запроса с кодом CODE3 клиент получает до OutputBufferLength байт неинициализированной части системного буфера — фактически утечка содержимого соседних аллокаций ядра. Реальный кейс — драйвер промышленного ПО под Windows через METHOD_NEITHER (прямой пользовательский указатель, без ограничения page-aligned аллокацией) отдавал до мегабайта памяти ядра одной командой.
9.3
Недостаточно ограниченные права
Часто забывают ограничить права непривилегированных пользователей к самописным устройствам. Эксперт смотрит листинг /dev/ или \\.\, ищет устройства с правами 666 / FILE_ANY_ACCESS и пробует обращаться к ним от имени обычного пользователя.
D-Bus и COM/DCOM
Многие библиотеки open source предоставляют доступ к своим функциям через D-Bus. Доступ пользователей к этим функциям, как правило, предоставляется без разграничения, на основе случайной политики безопасности, самопроизвольно возникшей при заимствовании сторонних библиотек.
Опасные сервисы по умолчанию
Практически во всех Linux-системах с systemd и графической средой присутствуют сервисы, через которые непривилегированное приложение может вмешиваться в графическую сессию:
org.freedesktop.login1— интерфейсsystemd-logind; методLockSession()блокирует собственную сессию пользователя без авторизации,LockSessions()для всех сессий обычно закрыт polkit-правилом, но в неаккуратной конфигурации может оказаться открытым;org.freedesktop.ScreenSaver— стандартный интерфейс DE (GNOME/KDE/Xfce); вызовыLock,Inhibit,UnInhibitдоступны любому приложению пользователя.
Пример «случайного НДВ». В Astra Linux 1.6 D-Bus давал доступ к меню «Пуск» версии 1.4 — не уязвимость, просто пример случайно вставленного НДВ «в хорошем смысле»: библиотека графического стека экспортировала больше методов, чем ожидал интегратор.
Стандартный осмотр шины
Получить список сервисов:
$ dbus-send [--system] --print-reply --dest=org.freedesktop.DBus \
/org/freedesktop/DBus \
org.freedesktop.DBus.List[Activatable]Names
Получить список узлов и интерфейсов конкретного сервиса:
$ dbus-send [--system] --print-reply --dest=сервис /узел \
org.freedesktop.DBus.Introspectable.Introspect
Дальше — пробовать каждый интерфейс от имени непривилегированного нарушителя (см. кейс №6 выше). Можно обнаружить много чёрных ходов, позволяющих делать странные вещи.
D-Bus можно прослушивать, если это специально не запрещено. Потенциально по D-Bus могут передаваться пароли и другая чувствительная информация. В Windows ближайшим аналогом D-Bus является COM/DCOM — те же вопросы задаются и о нём.
Графические интерфейсы
Графический стек — отдельный мир уязвимостей. Сценарии «приложение видит чужие окна», «приложение посылает чужому окну сообщение», «приложение перехватывает клавиатуру» — на разных платформах решаются по-разному.
X Window System
Linux desktop · традиционныйНа рабочем столе все приложения видят все окна. Любое приложение может отправить любое сообщение любому окну. Можно накрыть чужое окно своим и перехватывать клавиатурный ввод.
Готовые «фокусы» для проверки:
Wayland · Android · Win Vista+
Современные стекиНа уровне дизайна стека защита от классических X-атак есть. В Windows с Vista — UIPI и Session 0 Isolation. В Wayland — изоляция клиентов на уровне протокола. В Android — изоляция UI через permissions в манифесте. Но это не значит, что класс атак исчез — он сместился.
В Wayland проблемы переехали в брокеры и мосты: xdg-desktop-portal для ScreenCast/RemoteDesktop, мост Xwayland (фактически восстанавливает X-модель угроз для совместимости), вопрос nested security context. В Android типовые атаки требуют либо особых разрешений (Accessibility Service, Overlay), либо абьюза доверенных компонентов: tapjacking, toast overlay (Cloak & Dagger без permissions), TapTrap (USENIX Security 2025), банковские трояны через Accessibility (Anubis, ToxicPanda).
Эксперт проверяет: какой стек используется по умолчанию. Astra Linux SE и RedOS поставляются с Xorg как основным графическим стеком. Также — не открыт ли «X-сервер для совместимости» (Xwayland, XRDP, X11 forwarding) в рабочих сценариях.
Окна привилегированных приложений на общем рабочем столе
Сумел запустить из привилегированного приложения файловый менеджер или консоль — захватил контроль над ОС. Актуально в любом графическом интерфейсе, не только в X Window System. Самый известный пример прошлого — Shatter attack (Chris Paget, август 2002); в 2003–2007 годах было известно много уязвимых для shatter-атак продуктов — антивирусов и персональных файрволов того периода (исторически — Agnitum Outpost, Symantec Antivirus и др.). Современный аналог в актуальных продуктах — уязвимости через Window messages в win32k (например, CVE-2020-1054).
Что проверяет эксперт. Открывает «Системный монитор» / диспетчер задач, ищет процессы привилегированного ПО, у которых есть видимые окна на общем рабочем столе. Затем смотрит, к какому пользователю эти окна принадлежат, и пробует послать им сообщения от имени непривилегированного пользователя.
Мандатное управление доступом
Мандатное управление доступом (МУД) — отдельный класс защитных механизмов. У него два характерных вида проблем: программа просто не работает в МУД-окружении, либо работает, но позволяет утечки.
12.1. «Чума секретности» — программа не работает
Как правило, программы, изначально не предназначавшиеся для работы в условиях мандатного управления доступом, в них не работают. Обычно достаточно построить нетривиальную политику работы с секретами, поработать несколько дней — и всё рушится само собой. Нормальных реализаций МУД, пригодных для повседневного использования, видимо, нет.
Простейший пример «чумы секретности». В орфографический словарь попало слово, помеченное как секретное. Засекретился весь словарь. Засекретились все документы, где проверялась орфография. Засекретился шаблон нового документа по умолчанию. Засекретилась вся система документооборота. Откатить — невозможно.
12.2. Утечки помеченной информации
Как правило, информация, помеченная как секретная, всё равно может утекать в несекретные области ОС. Типичные пути:
journalctl в открытом виде.Замкнутая программная среда
Замкнутая (изолированная) программная среда — режим, в котором можно запускать только разрешённые исполняемые файлы. На бумаге — неприступно. На практике — неисчерпаемый источник обходов.
13.1. Типичные пути обхода
- Файлы-исключения, доступные для записи непривилегированным пользователям. Очень характерно для «защищённых» надстроек над Windows.
- Интерпретаторы из whitelist (LOLBin / GTFOBin). Если в перечне разрешённых исполняемых оказался интерпретатор (
bash,perl,python,powershell,mshta,wscript,cscript,regsvr32) — через него запускается произвольный код:
Тот же класс —$ bash script.txt > for /f "delims=;" %i in (file.txt) do %i // в .bat — %%i; cmd должен быть в whitelistawk,find -exec,vim -c,gdb -ex, в Windows —certutil,installutil,msbuild. Полные каталоги: LOLBAS и GTFOBins. - Эксплойт 38473 из ExploitDB — обход политики
noexecчерезmemfd_create()+fexecve(): исполняемый файл запускается из анонимной памяти, минуя точку монтирования сnoexec. Применим как путь обхода ЗПС в тех ОС, где замкнутая программная среда опирается именно наnoexecточек монтирования (характерно для ряда отечественных дистрибутивов). - Ярлыки на рабочем столе, если запрещено запускать файловый менеджер: ярлык можно создать вручную или подменить чужой.
- Файлы конфигурации, например
~/.bashrc: попадают в нагрузку оболочки и могут содержать произвольные команды. - Перенаправление ввода-вывода, если запрещён доступ к консольным устройствам.
- Журнальные файлы: вывод программы, не получившей доступа к консоли, может оказываться в доступном на чтение журнальном файле.
13.2. Обход через фронтенд
Если фронтенд приложения реализован веб-подобными технологиями, файлы HTML, JS и т. п., как правило, не рассматриваются замкнутой программной средой как исполняемые и могут невозбранно модифицироваться.
Запреты на обращения к опасным интерфейсам могут обходиться динамической генерацией интерпретируемого программного кода: даже если запрещён прямой eval, можно собрать строку из частей и передать в безобидно выглядящий обработчик событий.
Кроме того, замкнутая программная среда — неисчерпаемый источник сбоев в основной работе пользователя: периодически блокируются нужные операции, тормозят легитимные приложения, падают сторонние компоненты. Эксперт смотрит не только на пути обхода, но и на удобство — слишком жёсткая ЗПС всё равно будет отключена администраторами «потому что мешает».
Средства отладки и ASLR
Двa механизма из «инфраструктуры безопасности» ОС, которые эксперт всегда проверяет: блокировка отладочных интерфейсов и качество рандомизации адресных пространств.
14.1. Средства отладки
Отладочные интерфейсы могут использоваться как непосредственно для НСД, так и для преодоления отдельных элементов защиты. Что должно быть:
В Linux-системах
обязательный минимум- Yama LSM включён (в современных ядрах это уже так по умолчанию:
CONFIG_SECURITY_YAMA=y). - Значение
kernel.yama.ptrace_scope— не менее 1 (по умолчанию в Ubuntu/Debian/Astra), для защищённых сегментов — 2 или 3. - Интерфейс
ptrace, файлыmaps/memв/proc/<pid>/недоступны непривилегированным приложениям.
В Windows-системах
разумный компромиссВ идеале нельзя делать OpenProcess и OpenThread на компоненты защищённого ПО. В реальности полностью добиться этого, скорее всего, не получится — должен быть какой-то разумный компромисс. В любом случае, стороннее непривилегированное приложение не должно иметь возможности применять средства отладки к защищённому ПО.
Реальный кейс. Однажды в эксплуатируемом промышленном ПО нашёлся неудалённый стенд отладки веб-интерфейса — позволял отлаживать SQL-инъекции прямо через GET-параметры. Стенд оставался доступен по сети без какой-либо аутентификации.
14.2. ASLR — рандомизация адресных пространств
Основное средство защиты от эксплуатации переполнений буферов и некоторых других видов уязвимостей. Что проверяется:
- Достаточная энтропия: известны случаи успешного подбора распределения памяти при количестве вариантов до примерно 100 000.
- Различие между процессами: в одновременно выполняющихся процессах распределение должно отличаться. Сходное или полностью одинаковое распределение — типичный недостаток.
- Отсутствие злоупотребления отключением: средства отключения ASLR для отдельных процессов не должны применяться без необходимости.
- Защита от обхода через обработку исключений: злоупотребление средствами обработки исключений позволяет нарушителю угадывать варианты неограниченно долго невзирая на ASLR.
Чек-лист эксперта: 38 контрольных вопросов
Практический опросник для проведения процесса №18. Ниже — все 38 вопросов, сгруппированные по тематическим разделам. Каждый вопрос — это и тест для разработчика, и пункт плана для эксперта по безопасности.
Опросник заимствован из материалов В. Г. Проскурина (ИСП РАН), редакция 2017–2020 гг. Несколько пунктов могут выглядеть «двумерно» относительно реалий 2025 года: к устаревшей криптографии добавились SHA-1, TLS 1.0/1.1, RSA с e=3 без OAEP, ECDSA с предсказуемыми nonce; к D-Bus и COM/DCOM добавились gRPC внутри продукта, REST-внутри-продукта, Android Binder, Unix-сокеты с собственным протоколом. Структура опросника от этого не теряет ценности.
№ 1–4
Аутентификация и протоколы
- Какие запросы к серверу доступны без аутентификации? Достаточно ли ограничен перечень таких запросов? Как это проверялось — визуальным просмотром кода, вводом тестовых запросов вручную, созданием автоматического сканера на основе системы тестов?
- Допустимо ли, если сервер получает пакеты не от штатной клиентской программы, а от сторонней программы? Если нет — каким образом реализуется запрет подключения таких клиентов? Если да — точно ли это не приводит к нарушениям безопасности (например, если функция безопасности реализована не на сервере, а только в клиентской программе)?
- Какой протокол аутентификации применяется? Если самописный — кто его разрабатывал и тестировал? Если стандартный — точно ли он подходит к данной конкретной ситуации?
- Есть ли в протоколе дополнительные параметры, повышающие безопасность (ограничения по адресам подключения, времени подключения, сроку действия ключей и т. п.)? Они точно используются? Как это проверялось?
№ 5–15
Криптография и работа с ключами
- Как сервер устанавливает длины буферов с передаваемыми клиенту данными? Нет ли HeartBleed-подобных уязвимостей?
- Какие криптосистемы применяются? Точно ли реальность соответствует документации? Как это проверялось? Проверялись ли особые случаи (например, в Linux-совместимой ОС самый первый пользователь, создаваемый при установке)? Не применяются ли самописные криптоалгоритмы?
- Какой заимствованный код используется в проекте? Какие в нём известны уязвимости? Проводилась ли работа по анализу заимствованного кода как минимум на уровне мониторинга открытых источников? Проводится ли такая работа на регулярной основе? Какая реакция предусмотрена в случае обнаружения в заимствованной библиотеке критической уязвимости?
- Применяются ли в проекте устаревшие криптосистемы наподобие DES, MD5, RC4? Если да — безопасно ли это?
- При хешировании паролей используется ли маркант (соль, salt, seed)? Если нет — почему? Проверялось ли это прямым сравнением хешей одинаковых паролей у разных пользователей?
- Как вводятся ключи шифрования? Если они хранятся внутри системы — как реализовано это хранение? Кому могут быть доступны эти данные в каких ситуациях?
- Передаются ли пароли по сети в открытом виде? Проверялось ли это перехватом и анализом трафика?
- Передаются ли по сети хеши аутентификации в открытом виде? Проверялось ли это перехватом и анализом трафика?
- Если защита трафика полагается на полное его шифрование сторонним протоколом (например, SSL) — как обрабатывается ситуация, когда шифрование по каким-то причинам невозможно? Не пойдёт ли трафик в открытом виде?
- Как сервер идентифицирует сетевые соединения клиентов? Не используются ли легкоугадываемые идентификаторы (например, порядковые номера), передаваемые прямо в пакете с данными?
- Какие генераторы случайных чисел применяются в проекте? Не применяются ли для криптографических задач линейные рекурренты или другие неподходящие алгоритмы?
№ 16–18
Инъекции и валидация ввода
- Проводились ли проверки кода на наличие SQL-инъекций? Какие средства формирования SQL-запросов применяются в проекте? Не формируются ли SQL-запросы вручную (
sprintfи т. п.)? Если подавать на вход непарные кавычки, апострофы и т. п. — не появляются ли в ответах сервера ошибки SQL? - Проводились ли проверки кода на предмет наличия XSS-уязвимостей? Как сервер реагирует на вручную сформированные пакеты, похожие на штатные, но с необычными заполнениями полей? Как сервер реагирует на команды JavaScript, размещаемые внутри пакета? Может ли нарушитель добиться их некритичного размещения внутри веб-системы? Если имитировать ввод в формы непарных кавычек, апострофов и т. п. — не появляются ли в ответах ошибки веб-интерфейса? Насколько корректно сервер принимает пакеты с текстом в необычных кодировках?
- Как обрабатываются ситуации, когда клиент указывает имя файла вместе с путём? Не пытается ли сервер применить этот путь к своей файловой системе? Как при этом обрабатываются подстроки
.., строки, начинающиеся с\в Windows и т. п.? Если делается санитизация — можно ли её обойти необычными кодировками текста или какими-то другими ухищрениями?
№ 19–25
Драйверы, права доступа, IPC и интерфейсы ОС
- Драйверы и модули ядра, содержащиеся в проекте, проверялись ли SyzKaller-подобными фаззерами?
- Как ограничиваются права доступа к файлам приложения? Что может сделать с файлами приложения непривилегированный пользователь, используя предоставленные права? Если приложение работает в Windows — те же вопросы задаются в отношении ключей реестра и объектов корневой файловой системы (
\??\*и т. п.). - Как ограничиваются права доступа к устройствам, обслуживаемым исследуемым драйвером? Драйвер полностью полагается на права доступа, заданные в ОС, или дополнительно реализует собственные проверки? Что может сделать с драйвером непривилегированный пользователь?
- Как ограничивается доступ к реализованным в проекте сервисам D-Bus или интерфейсам COM/DCOM? Что может делать непривилегированный пользователь, используя предоставленные права?
- Принимаются ли в проекте специальные меры, блокирующие доступ сторонних приложений через отладочные интерфейсы операционной системы? Не стоит ли их принять?
- Принимаются ли в проекте специальные меры, блокирующие доступ сторонних приложений через графический интерфейс операционной системы? Не стоит ли их принять?
- Не создаёт ли анализируемое ПО на общем рабочем столе окна, обслуживаемые высокопривилегированным процессом? Могут ли такие окна создаваться непредумышленно — например, при запуске справки или после аварийного завершения Windows Explorer? Что может сделать непривилегированный пользователь, направляя в эти окна специально подобранные комбинации сообщений? Не стоит ли избавиться от таких окон?
№ 26–38
Защитные механизмы, анализ кода, чувствительные данные
- Поддерживает ли анализируемое ПО мандатное управление доступом? Если да — все ли функции работают в секретных сессиях? Как это проверялось? Возможны ли утечки информации, помеченной как секретная, через поисковые индексы, журнальные файлы, графические интерфейсы, привилегированные системные процессы, внешние накопители информации, другими путями?
- Поддерживает ли анализируемое ПО изолированную (замкнутую) программную среду? Если да — блокированы ли типичные пути обхода через файлы-исключения, скрипты, эксплойт 38473 из ExploitDB, ярлыки на рабочем столе, файлы конфигурации, перенаправление ввода-вывода, журнальные файлы и т. д.? Распространяются ли правила ЗПС на интерпретируемый и динамически генерируемый программный код, COM-подобные интерфейсы и т. д.? Не вызывают ли правила ЗПС сбоев при доступе к принтерам, внешним накопителям, сети и т. д.?
- Скомпилирован ли проект с поддержкой рандомизации распределения адресных пространств (ASLR)? Насколько качественна эта рандомизация — устранены ли типичные недостатки? Может ли нарушитель игнорировать ASLR, используя злоупотребление средствами обработки исключений?
- Проводился ли статический анализ исходных текстов проекта? Проводится ли он на регулярной основе? Как организовано реагирование на предупреждения статического анализатора?
- Какие принимаются меры для защиты от переполнений буфера? Используются ли в коде проекта небезопасные функции обработки строк? Присутствуют ли в программе переполнения буферов?
- Какие принимаются меры для защиты от обращений к освобождённой памяти? Присутствуют ли в программе ошибки обращения к освобождённой памяти?
- Какие принимаются меры для защиты от ошибок двойного освобождения памяти? Присутствуют ли в программе ошибки двойного освобождения памяти?
- Используются ли в проекте форматные строки (
printfи т. п.)? Какие принимаются меры для защиты от ошибок в работе с форматными строками? Присутствуют ли в программе ошибки работы с форматными строками? - Какие принимаются меры для защиты от утечек памяти? Присутствуют ли в программе утечки памяти?
- Какие принимаются меры для обеспечения корректной многопоточности (недопущение race conditions и deadlocks)? Присутствуют ли в программе ошибки обеспечения корректной многопоточности?
- Построена ли для приложения поверхность атаки? Предпринимались ли попытки её построить? Полностью ли она покрыта вышеперечисленными вопросами?
- Проводилось ли фаззинг-тестирование приложения? Планируется ли его проводить? Учитывалась ли перспектива проведения фаззинг-тестирования при проектировании архитектуры и в ходе разработки приложения?
- Работает ли приложение с чувствительными данными (пароли, ключи шифрования, идентификаторы банковских счетов и т. п.)? Какими? Проводилось ли для приложения автоматизированное выявление утечек чувствительных данных? Были ли выявлены утечки и какие?
Как пользоваться чек-листом. Опросник работает в двух режимах. Разработчику — как контрольный самотест перед сдачей продукта. Эксперту — как план интервью с командой разработки и план собственных проверок. Каждый «нет» в ответах — повод для отдельного исследования.