Минимальный чек-лист безопасности при разработке Windows-приложений
· Го Комура · Разработка Windows, Безопасность, Проектирование, C# / .NET, Win32
Скачать чек-лист в формате Excel
Разговор о безопасности Windows-приложений имеет свойство быстро разрастаться. Нулевое доверие, EDR, SBOM, управление сертификатами, управление уязвимостями. Всё это важно, но на практике есть немало базовых вещей, которые не хочется упустить ещё до этого.
Особенно для приложений следующего рода закрытие пробелов в базовых мерах работает эффективнее, чем «продвинутая защита»:
- десктопные приложения на WPF / WinForms / WinUI
- Win32-приложения на C++ / C#
- интеграция с оборудованием, интеграция файлов, подключения к БД, инструменты для внутреннего распространения
- бизнес-приложения с механизмом автообновления
- конфигурации, включающие службы Windows или вспомогательные EXE
В разработке Windows-приложений реалистичнее сначала не оставлять явно опасных дыр, чем пытаться сразу довести всё до совершенства. Здесь мы структурируем в удобном для проверки виде минимальные пункты, которые не стоит упускать, — в порядке проектирования, реализации, распространения и эксплуатации.
1. Сначала вывод
- Первое, что не стоит упускать: не запрашивать лишние права администратора, подписывать код, не хранить секреты в открытом виде и не отключать проверку сертификатов.
- Для Windows-приложения сам дистрибутив становится поверхностью атаки. Безопаснее рассматривать всё целиком: EXE / DLL / MSI / MSIX / модуль автообновления.
ServerCertificateValidationCallback => true, строки подключения в открытом виде, небрежная загрузка вродеLoadLibrary("foo.dll")и выполнение SQL через конкатенацию строк — это то, чего стоит избегать даже на минимальном уровне.- Если прав администратора требует лишь часть операций, безопаснее не повышать права всего приложения, а выделить эту часть в отдельный EXE или службу.
- Для приложения, распространяемого на Windows, стоит по умолчанию закладывать подпись + метку времени. Это не только повышает доверие пользователей, но и облегчает обнаружение подделки и объяснение на этапе эксплуатации.
- Для конфиденциальных данных при хранении в зависимости от задачи используют DPAPI / ProtectedData или Credential Locker. По крайней мере стоит уйти от хранения в открытом виде в
appsettings.json. - Логов не всегда должно быть «чем больше, тем лучше». Если оставлять в них токены, пароли, строки подключения, персональные данные или полное тело запроса как есть, сам лог становится главным действующим лицом инцидента.
Минимальная безопасность — это не добавление особых функций, а отказ от опасных настроек по умолчанию и небрежной реализации.
2. Область применения статьи и что значит «минимум»
2.1. Что охватывает статья
В этой статье речь идёт о таких Windows-приложениях.
- десктопные приложения на WPF / WinForms / WinUI
- Win32-приложения на C++ / C#
- инструменты для внутреннего распространения, интеграции с оборудованием, мониторинга
- конфигурации, включающие вспомогательные EXE, службы Windows, апдейтеры
- бизнес-ПО, распространяемое как EXE / MSI / MSIX
«Минимум» здесь означает не финальное состояние, проходящее аудит, а пункты, отсутствие которых обычно приводит к инцидентам.
2.2. Что не входит в статью
При этом есть вещи, которые остаются за пределами основного фокуса статьи.
- проектирование нулевого доверия в масштабах всей компании
- комплексная эксплуатация EDR / SIEM / DLP / MDM
- детальное усиление защиты (hardening) для драйверов уровня ядра
- проектирование криптографических схем с нуля
- продвинутый анализ угроз и процедуры форензики
Иными словами, мы говорим не о «масштабных мерах безопасности организации в целом», а о базовой линии, которую разработчик Windows-приложения способен и должен самостоятельно закрыть перед релизом.
3. Чек-лист, с которого стоит начать
Прежде чем переходить к детальному обсуждению, приведём таблицу, которая даёт общий обзор. Уже по ней можно понять, на что обратить внимание в первую очередь.
3.1. Общая картина
| Что проверить | Минимум действий | Типичная ошибка |
|---|---|---|
| Права выполнения | По умолчанию asInvoker; операции, требующие повышения, выделяются отдельно |
Делать весь app requireAdministrator |
| Достоверность дистрибутива | Подписывать EXE / DLL / MSI / MSIX кодом, добавлять метку времени | Распространять без подписи |
| Обновление | Фиксировать источник обновления, обнаруживать подделку через HTTPS и проверку подписи | Скачивать по HTTP и сразу перезаписывать поверх |
| Конфиденциальные данные | Не хранить секреты в исходном коде или открытых настройках, использовать DPAPI / Credential Locker и т.п. | Хранить API-ключи и строки подключения в конфигурационном файле в открытом виде |
| Обмен данными | Использовать HTTPS, не отключать проверку сертификатов | Постоянно пропускать проверку сертификата через return true |
| Внешние входные данные | Проверять всё: SQL, файлы, IPC, URI, CSV, JSON и др. | Пропускать без проверки, потому что «это внутренний инструмент» |
| Загрузка DLL | Использовать абсолютные пути, SetDefaultDllDirectories, безопасный порядок поиска |
Полагаться на текущий каталог при LoadLibrary("foo.dll") |
| Логирование | Маскировать токены, пароли, PII, разделять сообщения об ошибках для пользователя | Показывать или сохранять детали исключений и строки подключения как есть |
| Зависимости | Постоянно обновлять SDK, NuGet, среды выполнения VC++, OSS-зависимости | Фиксировать версии на годы и не следить за уязвимостями |
3.2. По умолчанию использовать asInvoker для прав доступа
Это первое, что стоит пересмотреть в Windows-приложении. Если запускать всё приложение с правами администратора, то ошибки, подмена DLL, неверное чтение конфигурации и недостатки внешних входных данных будут выполняться с теми же высокими правами.
Базовая стратегия такая.
- Обычные UI-приложения запускаются как
asInvoker - Только операции, требующие прав администратора, выделяются в отдельный процесс или службу
- Повышение прав выполняется только в нужный момент
- Входные данные, передаваемые вспомогательному EXE или службе, тоже проверяются
Если обычно десктопное приложение занимается только просмотром и редактированием, а права администратора нужны лишь для установки или изменения настроек брандмауэра, безопаснее не делать requireAdministrator для всего приложения, а вынести только требующую повышения часть в брокер.
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
«Проще, если оно просто работает от администратора» — обычно аукается позже. Если работать с минимальными правами и выделять в отдельные операции только то, что действительно необходимо, радиус возможного инцидента становится значительно меньше.
3.3. Подписывать бинарные файлы и установщик
В Windows решающую роль играет достоверность дистрибутива. Пользователь имеет дело не с исходным кодом, а с EXE, DLL, MSI, MSIX и апдейтером. Если они не подписаны, страдают и объяснение на этапе эксплуатации, и обнаружение подделки, и уверенность при распространении.
На минимальном уровне стоит обратить внимание на следующее.
- Подписывать EXE / DLL / MSI / MSIX
- Подписывать не только установщик, но и вспомогательные бинарные файлы, используемые при обновлении
- Добавлять метку времени
- Включать срок действия сертификата и порядок его продления в процедуру релиза
Особенно подпись без метки времени часто создаёт проблемы при проверке после истечения срока действия сертификата. Не стоит считать, что «раз подписано — значит всё готово»; стабильнее включить в процедуру релиза именно связку подпись + метка времени.
Если вы используете MSIX, подпись пакета — обязательное условие. При распространении через MSI / EXE тоже стоит подписывать хотя бы сам установщик и основные исполняемые файлы.
3.4. Зафиксировать канал обновления и внедрить обнаружение подделки
В современном Windows-приложении канал обновления используется дольше, чем первоначальная установка. Если отнестись к нему небрежно, то даже при аккуратной разработке самого приложения именно апдейтер станет самым слабым местом.
Вот пять пунктов, которые стоит продумать в отношении обновления как минимум.
- Получение файлов обновления только по HTTPS
- Проверять подпись или хеш скачанного обновления
- Не допускать, чтобы URL источника обновления можно было произвольно подменить через код или настройки
- Подписывать сам модуль обновления
- Определить процедуру отката и восстановления при сбое
Если можно использовать MSIX + App Installer, механизм обновления легче переложить на уровень ОС. Если же вы используете собственный апдейтер, нужно проверять и безопасность канала связи, и подлинность дистрибутива. Один только HTTPS защищает «канал связи», но не гарантирует, что «этот файл действительно опубликован именно вами».
3.5. Не хранить секреты в исходном коде или открытых настройках
На практике здесь действительно легко попасть в инцидент. Из соображений «это внутренний инструмент» или «мы всё равно просто раздаём exe» строки подключения, API-ключи, учётные данные общей папки и фиксированные токены нередко оказываются в исходном коде или конфигурационных файлах.
На минимальном уровне стоит избегать вот такого хранения.
- API-ключи, прописанные прямо в исходном коде
- Пароли в открытом виде в
appsettings.jsonилиapp.config - Строки подключения, попавшие в репозиторий
- Архитектура, в которой ключ расшифровки хранится рядом с зашифрованными данными
- Общие для всех пользователей фиксированные учётные данные вместо индивидуальных
Реалистичные варианты для Windows-приложения сводятся примерно к этим четырём.
- Нужно хранить учётные данные Windows Для packaged desktop app / приложений на WinUI стоит рассмотреть Credential Locker
- Нужно хранить секрет локально в зашифрованном виде
Для Win32 / .NET используйте DPAPI /
ProtectedData - Целевая система поддерживает проверку подлинности Windows или интегрированную аутентификацию По возможности не давайте приложению вообще хранить пароль
- Секретами можно управлять на стороне облака или сервера Отдавайте предпочтение архитектуре, не встраивающей долгоживущие секреты в клиент
В C# уже одно только использование DPAPI, как показано ниже, значительно лучше хранения в открытом виде.
using System.Security.Cryptography;
using System.Text;
byte[] plaintext = Encoding.UTF8.GetBytes(secretText);
byte[] ciphertext = ProtectedData.Protect(
plaintext,
optionalEntropy: null,
scope: DataProtectionScope.CurrentUser);
Важно здесь не то, что «раз зашифровано — значит безопасно», а то, чтобы в архитектуре было явно решено, кто может расшифровать данные.
Выбор между CurrentUser и LocalMachine существенно меняет смысл.
Для подключения к SQL Server в локальных (on-premises) окружениях первым кандидатом часто может быть проверка подлинности Windows.
Если без учётных данных в строке подключения обойтись никак нельзя, безопаснее как минимум сохранять Persist Security Info=False и не оставлять их в открытом конфигурационном файле.
3.6. Обмен данными по умолчанию через HTTPS, не отключать проверку сертификатов
Лазейка, добавленная «только на время разработки», так и остаётся в продакшене. Инциденты, связанные с обменом данными, чаще всего следуют именно этой схеме.
В выпущенном продукте особенно часто остаётся код или настройки такого рода.
ServicePointManager.ServerCertificateValidationCallback += ... => trueHttpClientHandler.DangerousAcceptAnyServerCertificateValidator- Поставка с отключённой проверкой отзыва сертификата
- Код, рассчитанный на самоподписанный сертификат для разработки, остаётся в продакшене
Минимальная политика проста.
- Продакшен-трафик идёт по HTTPS
- Не пропускать проверку сертификата постоянно
- Если необходимо исключительное послабление проверки, ограничивать его конкретными хостами и сертификатами
- Гарантированно исключать код обхода для разработки через условия сборки или настройки
- В .NET учитывать также проверку отзыва сертификата
Плохой пример выглядит примерно так.
ServicePointManager.ServerCertificateValidationCallback +=
(_, _, _, _) => true;
На первый взгляд это удобно, но по сути такое поведение близко к «пропускать это HTTPS-соединение независимо от того, к кому оно подключается». Если убрать проверку сертификата, даже при использовании HTTPS содержание оказывается во многом выхолощенным.
3.7. Считать все внешние входные данные «недоверенными»
Windows-приложение — не веб-приложение, поэтому проверка входных данных легко получается небрежной. Но на деле точек входа для внешних данных гораздо больше, чем кажется.
- Пути к файлам
- CSV / Excel / JSON / XML
- Аргументы командной строки
- именованные каналы (named pipe) / сокеты / COM / RPC / gRPC
- Строки, передаваемые в БД
- Значения реестра
- Буфер обмена
- URL / deep link
- Данные, возвращаемые внешними устройствами или SDK
Особенно не стоит упускать следующие три пункта.
- Всегда параметризовать SQL Не строить SQL через конкатенацию строк.
- Нормализовать пути к файлам перед использованием Не использовать путь, указанный пользователем, напрямую для удаления, перезаписи или распаковки.
- При чтении внешних файлов задавать ограничение по размеру и проверку формата «Файл открылся» не означает «это безопасно».
На примере SQL — вот чего стоит избегать.
var sql = "SELECT * FROM Users WHERE Name = '" + userName + "'";
Хотя бы на минимальном уровне стоит приводить код к такому виду.
using System.Data;
using Microsoft.Data.SqlClient;
using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT * FROM Users WHERE Name = @name";
cmd.Parameters.Add("@name", SqlDbType.NVarChar, 256).Value = userName;
«Это внутренний инструмент, поэтому входным данным можно доверять» — довольно опасное допущение. В реальности регулярно поступают повреждённые CSV, неожиданные имена файлов, устаревшие данные из БД, опечатки при ручном вводе оператора и недооформленный JSON, написанный другими инструментами.
3.8. Не оставлять источник загрузки DLL неопределённым
Это ловушка, характерная именно для Windows.
Если загружать DLL только по имени, как в LoadLibrary("foo.dll"), то в зависимости от порядка поиска можно получить DLL из непредусмотренного места.
Набор действий здесь известен.
- По возможности указывать абсолютный путь к DLL
- Устанавливать
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)на раннем этапе - Явно добавлять места поиска через
AddDllDirectory - Избегать архитектуры, передающей результат
SearchPathнапрямую вLoadLibrary - Не полагаться полностью на safe DLL search mode
Например, для нативного кода эффективна архитектура, при которой на раннем этапе инициализации процесса выполняется следующее.
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
После этого через AddDllDirectory регистрируются только те дополнительные каталоги, которые действительно нужны.
Поскольку «обычно всё работает», этот момент легко оставить без внимания, — но если в месте развёртывания меняется рабочий каталог или в PATH попадает DLL другого продукта, всё тихо ломается. Это важно не только для безопасности, но и заметно помогает предотвращать сбои.
3.9. Не выводить конфиденциальные данные в логи и исключения
Наращивать объём логов для расследования сбоев важно. Но логи так же легко становятся кладбищем конфиденциальных данных.
Вот что стоит пересмотреть в отношении логирования как минимум.
- Не выводить в лог пароли, Bearer-токены, API-ключи
- Не выводить строку подключения целиком
- Маскировать персональные данные и содержимое бизнес-данных
- Разделять детали исключения между экраном для пользователя и внутренним логом
- Не включать в продакшене отладочное логирование PII
- Пересматривать права доступа к местам хранения dump и trace
В последних версиях .NET стало проще выстраивать логирование с расчётом на redaction (маскирование данных). По крайней мере, стоит отказаться от практики «превратить всё в строку и записать как есть».
Приведём несколько типичных ошибок.
- Сохранение тела HTTP-запроса / ответа целиком
- Вывод токена или всех заголовков целиком при сбое аутентификации
- Вывод сообщения исключения напрямую в MessageBox
- Включение всех конфиденциальных логов в ZIP для обслуживания
Отображение ошибок, например, разделяют так.
- Для пользователя: «Не удалось подключиться к серверу. Проверьте сетевые настройки и URL.»
- Внутренний лог: хост назначения, тип ошибки TLS, идентификатор корреляции, stack trace, число повторных попыток
Уже одно такое разделение заметно улучшает баланс между утечкой информации и возможностью расследования.
3.10. Не оставлять без внимания библиотеки-зависимости и инструменты разработки
Последний пункт неброский, но с большим эффектом. Даже при аккуратной разработке самого приложения, если оставить устаревшую среду выполнения или зависимости с известными уязвимостями, почва уходит из-под ног.
Список того, что нужно проверять, на самом деле невелик.
- Держать .NET SDK / runtime в поддерживаемых версиях
- Регулярно проверять обновления зависимостей NuGet / OSS
- Для C++ вести управление версиями распространяемых компонентов среды выполнения и внешних DLL
- Включать проверку сведений об уязвимостях в предрелизную проверку
- Подготовить smoke-тесты, чтобы обновление зависимостей не ломало приложение незаметно
Здесь самое опасное — подход «сделаем всё разом потом». Если откладывать на полгода или год, разница между версиями становится слишком большой, и сама работа по обеспечению безопасности превращается в тяжёлый проект.
4. Чек-лист перед релизом
Приводим таблицу в виде, который можно сразу использовать как шаблон для ревью и решения о выпуске. Для удобства проверки минимальные пункты перед релизом сгруппированы по категориям в таблицах.
4.1. Права доступа, модель выполнения
| Пункт проверки | Отметка | Примечание |
|---|---|---|
Обычный запуск выполняется как asInvoker |
□ | |
| Операции, требующие прав администратора, вынесены в отдельный EXE / службу и т.п. | □ | |
| При использовании службы учётная запись выполнения не сильнее, чем необходимо | □ | |
Разделены зоны ответственности между %ProgramFiles% и пользовательскими данными |
□ |
4.2. Распространение, подпись
| Пункт проверки | Отметка | Примечание |
|---|---|---|
| EXE / DLL / MSI / MSIX / updater подписаны | □ | |
| К подписи добавлена метка времени | □ | |
| Срок действия сертификата и порядок его продления включены в процесс релиза | □ | |
| Определён способ проверки хеша дистрибутива и обнаружения подделки | □ |
4.3. Обновление
| Пункт проверки | Отметка | Примечание |
|---|---|---|
| Получение обновлений выполняется по HTTPS | □ | |
| После скачивания проверяется подпись или хеш | □ | |
| Архитектура затрудняет произвольную подмену URL источника обновления | □ | |
| Есть политика отката или повторных попыток при сбое обновления | □ |
4.4. Конфиденциальные данные
| Пункт проверки | Отметка | Примечание |
|---|---|---|
| Пароли, API-ключи, строки подключения не прописаны прямо в исходном коде | □ | |
| Секреты не хранятся в конфигурационном файле в открытом виде | □ | |
| Секреты, которые нужно хранить локально, защищены через DPAPI / Credential Locker и т.п. | □ | |
| Там, где возможно, используется проверка подлинности Windows или пользовательские учётные данные | □ |
4.5. Обмен данными
| Пункт проверки | Отметка | Примечание |
|---|---|---|
| Продакшен-трафик использует HTTPS | □ | |
DangerousAcceptAnyServerCertificateValidator или => true не остались в выпущенном продукте |
□ | |
| Учитываются проверка отзыва сертификата и проверка имени хоста | □ | |
| В продакшен не попал код или настройки, рассчитанные на сертификат для разработки | □ |
4.6. Входные данные, доступ к данным
| Пункт проверки | Отметка | Примечание |
|---|---|---|
| SQL параметризован | □ | |
| Для входных данных из командной строки, файлов, IPC, URI и т.п. заданы ограничения и проверка формата | □ | |
| Операции с путями нормализуются, что предотвращает выход за пределы корневого каталога | □ | |
| Сообщения исключений не выводятся на экран напрямую | □ |
4.7. DLL и среда выполнения
| Пункт проверки | Отметка | Примечание |
|---|---|---|
| Источник загрузки DLL указан явно | □ | |
Порядок поиска контролируется через SetDefaultDllDirectories / AddDllDirectory и т.п. |
□ | |
| Загрузка DLL не полагается на текущий каталог или PATH | □ | |
| Известен полный набор файлов, необходимых для динамической загрузки в месте развёртывания | □ |
4.8. Логирование, эксплуатация
| Пункт проверки | Отметка | Примечание |
|---|---|---|
| Токены, пароли, PII не выводятся в лог | □ | |
| Внутренний лог отделён от сообщений для пользователя | □ | |
| Права доступа к местам хранения dump / trace / log пересмотрены | □ | |
| Проверяется актуальность обновлений SDK и библиотек-зависимостей | □ |
5. Частые ошибочные представления
На практике чаще всего встречаются примерно такие заблуждения.
5.1. «Это внутренний инструмент, значит всё в порядке»
Даже во внутреннем инструменте обычное дело — повреждённые файлы, ошибки оператора, принесённые извне устройства, общие папки, устаревшие DLL, небрежные настройки прав. Отсутствие публикации в интернете не устраняет поверхность атаки.
5.2. «Раз HTTPS — значит безопасно»
HTTPS важен, но при отключении проверки сертификата его смысл сильно ослабевает. Кроме того, при распространении обновлений нужен не только HTTPS, но и проверка подлинности дистрибутива.
5.3. «Раз зашифровано — значит безопасно»
Если не упорядочены место хранения ключа расшифровки, права на расшифровку, границы пользователей и границы машин, одного шифрования недостаточно.
В частности, если использовать значение, защищённое с LocalMachine, считая его «секретом для конкретного пользователя», позже это приводит к путанице.
5.4. «Чем больше логов, тем легче расследование»
Если логов просто много, а токены и персональные данные утекают в них без разбора, это само становится инцидентом. Если нужна возможность расследования, сначала стоит решить, что оставлять, а что скрывать.
5.5. «Запустим от администратора — и всё решится»
Поначалу это удобно, но потом обычно доставляет проблемы с UAC, распространением, поддержкой, границами прав, загрузкой DLL и местами хранения файлов. В долгосрочной перспективе стабильнее минимальные права.
6. Примерный порядок приоритетов
Если делать всё сразу тяжело, приоритеты выстраиваются примерно так.
- Пересмотр прав администратора
Прежде всего отказаться от постоянного использования
requireAdministrator. - Подпись и метка времени Привести в порядок достоверность дистрибутива.
- Вынос конфиденциальных данных Убрать секреты из исходного кода и открытых настроек.
- Исправление HTTPS + проверки сертификата
Убрать из выпущенного продукта конструкции вида
=> true. - Пересмотр входных данных SQL / файлов / IPC Сократить конкатенацию строк и непроверенный ввод.
- Фиксация загрузки DLL Отказаться от загрузки только по имени и надежды на PATH.
- Маскирование логов Сделать так, чтобы при инциденте лог не стал вторичным бедствием.
- Регулярное обновление зависимостей Сделать проверку частью каждого релиза.
В таком порядке проще двигаться в духе «сначала закрыть явно опасные дыры».
7. Итоги
Прежде чем внедрять специальные продукты или масштабные системы, безопасность разработки Windows-приложений заметно меняется уже от того, что приведены в порядок семь пунктов: права доступа, подпись, конфиденциальные данные, обмен данными, входные данные, DLL и логирование.
Если сформулировать минимальную планку по одной строке на пункт, получится так.
- Не запускать всё приложение с правами администратора
- Подписывать дистрибутив и обновления, добавлять метку времени
- Не хранить секреты в исходном коде или открытых настройках
- Использовать HTTPS, не отключая проверку сертификатов
- Не доверять внешним входным данным: SQL, файлам, IPC и прочему
- Не оставлять источник загрузки DLL неопределённым
- Не выводить конфиденциальные данные в лог
- Не оставлять без внимания библиотеки-зависимости
Тема безопасности обширна, но не обязательно делать всё сразу с самого начала. Однако минимум — не выпускать в продакшен опасные настройки по умолчанию как есть — стоит закрыть на достаточно раннем этапе.
8. Источники
- Administrator Broker Model - Win32 apps
- How User Account Control works
- Authenticode Digital Signatures
- Time Stamping Authenticode Signatures
- Sign a Windows app package
- Credential Locker for Windows apps
- CryptProtectData function (dpapi.h)
- CA5359: Do not disable certificate validation
- CA5399: Enable HttpClient certificate revocation list check
- Configuring parameters - ADO.NET Provider for SQL Server
- Connection String Syntax - ADO.NET
- Dynamic-Link Library Security - Win32 apps
- SetDefaultDllDirectories function (libloaderapi.h)
- Data redaction in .NET
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Как на практике выделить в Windows-приложении «только те операции, для которых нужны права администратора»
Разбираем на практике дизайн, при котором UI Windows-приложения остаётся asInvoker, а операции, требующие прав администратора, выносятся ...
Хранение секретов в Windows-приложениях - избегаем открытых настроек с помощью DPAPI
Чтобы не хранить учётные данные и API-токены в конфигурационных файлах Windows-приложений в открытом виде, разбираем принципы DPAPI / Pro...
Таблица решений: завершать работу приложения или продолжать при неожиданном исключении
Разбираем, когда после неожиданного исключения приложение стоит завершать, а когда можно продолжать работу — с точки зрения повреждения с...
Введение в ADR (Architecture Decision Record) — минимальный способ сохранить «почему мы спроектировали именно так» в небольшой команде
Код никогда не объясняет, почему он написан именно так. Разбираем, как использовать ADR (Architecture Decision Record) — одно решение, од...
Если ваше Windows-приложение приняли за вирус — как реагировать на ложные срабатывания Microsoft Defender и жить с влиянием на производительность
Разбираем правильный порядок действий, если Microsoft Defender ложно определяет ваше Windows-приложение как вредоносное: как устроена сов...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Речь идёт о пересмотре Windows-приложения целиком — вплоть до проектирования прав доступа, способа распространения, механизма обновления и логирования, — поэтому тема хорошо сочетается с услугой разработки Windows-приложений.
Технические консультации и ревью дизайна
Если вы хотите начать с пересмотра безопасности существующего приложения, упорядочивания границ прав доступа или пересмотра политики обновления, это можно оформить как техническую консультацию и ревью архитектуры.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Что нужно проверить в первую очередь, когда речь заходит о безопасности Windows-приложения?
- Четыре пункта: не запрашивать лишние права администратора, подписывать код, не хранить секреты в открытом виде и не отключать проверку сертификатов. Минимальная безопасность — это не добавление особых функций, а отказ от опасных настроек по умолчанию и небрежной реализации. Даже на минимальном уровне стоит избегать постоянного пропуска проверки сертификата, хранения строк подключения в открытом виде, загрузки DLL, полагающейся на текущий каталог, и выполнения SQL через конкатенацию строк.
- Нельзя ли просто запускать всё приложение с правами администратора?
- Этого стоит избегать. Если запускать всё приложение с правами администратора, то любые ошибки, подмена DLL, неверное чтение файла конфигурации или недостатки во внешних входных данных будут выполняться с этими же высокими правами. Базовая стратегия — обычные UI-приложения запускать как asInvoker, а операции, требующие прав администратора, выносить в отдельный процесс или службу, выполняя повышение прав только в нужный момент.
- Где следует хранить API-ключи и строки подключения?
- В первую очередь нужно уйти от хранения в открытом виде в исходном коде или конфигурационных файлах вроде appsettings.json. Для конфиденциальных данных при хранении в зависимости от задачи используют DPAPI / ProtectedData или Credential Locker. Кроме того, если оставлять в логах токены, пароли, строки подключения и персональные данные как есть, сам лог становится главным действующим лицом инцидента, поэтому такие данные нужно маскировать.
- Нужна ли подпись кода даже для приложения, распространяемого только внутри компании?
- Да, это стоит считать обязательным условием. В Windows-приложениях сам дистрибутив (EXE / DLL / MSI / MSIX / модуль автообновления) становится поверхностью атаки. Подпись кода вместе с меткой времени даёт возможность обнаружения подделки, повышает доверие пользователей и облегчает объяснение при эксплуатации. Механизм обновления также должен фиксировать источник обновления и обнаруживать подделку через HTTPS и проверку подписи.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.
Публичные ссылки