Перейти к содержимому

Модель безопасности

winrig администрирует Windows-хосты по WinRM и держит пароль AD в памяти. Bearer-токен — это ключ, который одновременно даёт доступ к серверу и расшифровывает пароль AD. Эта страница описывает защиты, которые применяет сервер.

Каждый запрос обязан предъявить один из двух секретов. Общий WINRIG_AUTH_TOKEN использует путь по заголовкам и требует X-AD-User. Токен профиля расшифровывает файл профиля. Совпадение WINRIG_AUTH_TOKEN с токеном профиля останавливает старт. Сервер не стартует без хотя бы одного секрета (ADR-0001).

В HTTP секрет текущего запроса задаёт пользователя, в чьём имени работает инструмент. Токен профиля задаёт идентичность профиля и заставляет сервер игнорировать X-AD-*. Общий секрет требует X-AD-User. Идентичность не наследуется от задачи, создавшей сессию MCP. В stdio идентичность равна профилю процесса.

Сервер проверяет токен прежде, чем доверяет X-AD-User: этот заголовок — ключ кэша паролей и сессий в памяти. Кэш паролей и кэш сессий изолированы по пользователю (ADR-0001).

Пароли и парольные фразы не попадают в журналы, аудит и конфигурацию и не попадают в коммит. На диске лежит только аутентифицированный шифртекст профиля, а ключ на сервере отсутствует. Секрет, который несёт команда, не должен появиться в аудите ни в каком виде (ADR-0002, ADR-0008).

Файл профиля — corp.json в каталоге профилей. В нём лежат версия формата, каноническое имя учётной записи, соль HKDF-SHA256, nonce XChaCha20-Poly1305 и шифртекст. Имя учётной записи связано как associated data. На Unix права файла — 0600, права каталога — 0700.

Каждая модифицирующая операция требует явного подтверждения оператора; без канала подтверждения операция не запускается. Модифицирующие инструменты получают подтверждение через MCP elicitation и отказывают (fail-closed), когда клиент не может спросить (ADR-0006). Подтверждение называет хост, пользователя и инструмент, а также цель и эффект. Скрытое значение показано только как hidden: старое и новое значения set_registry, содержимое write_file и командная строка kill_process остаются скрытыми. Текст команды и текст скрипта показываются целиком, без лимита в 200 символов. Оператор подтверждает то, что запустится. Сервер очищает каждое поле и ограничивает остальные поля 200 символами.

Оператор может задать WINRIG_CONFIRM_FALLBACK=proceed, чтобы модифицирующая операция выполнилась даже когда клиент не объявил канал elicitation. Послабление привязано только к отсутствующему каналу: отказ пользователя, отмена, тайм-аут и ошибка транспорта никогда не обходятся. Аудит помечает запуск, который прошёл без подтверждения (ADR-0013, TR-SEC-11).

Настройка снимает fail-closed со всех модифицирующих инструментов сразу, включая удаление. Она действует для каждого клиента без elicitation, а в HTTP-режиме — для каждого запроса, который не объявил канал.

Защищённые пути и разрушительные операции

Заголовок раздела «Защищённые пути и разрушительные операции»

Рекурсивное удаление отвергает корни томов и системные каталоги в любой форме пути. Оно отвергает и точки повторной обработки (junction или symlink). write_file отвергает защищённые пути тем же списком, что охраняет удаление (ADR-0002, ADR-0015).

Оператор может разрешить запись под защищённым корнем списком WINRIG_WRITE_ALLOW_PATHS (ADR-0017 §7–13). Элемент снимает лексический отказ и отказ по корням хоста для write_file, delete_file и файловых операций copy_file, move_file, rename_file и create_directory. Тогда подтверждение несёт явное предупреждение с именем класса. Сравнение — префикс на границе компонента пути. Оно идёт по нормализованному пути без учёта регистра и проверяет оба разрешения. Список никогда не снимает отказ по компоненту 8.3. Он также не снимает отказ символической ссылки или junction: операция подействовала бы на цель ссылки. Путь тома устройства и существующий каталог как цель тоже отвергаются. delete_directory и DR-6 не меняются. Аудит записывает элемент, который разрешил защищённый путь (§39).

Перед подтверждением хост сообщает, куда на самом деле ляжет файл и где у него стоят системные каталоги. Поэтому Windows на томе, отличном от C:, защищена так же. Административный общий ресурс \\host\C$\... инструмент проверяет как C:\..., а общие ресурсы ADMIN$, PRINT$, SYSVOL и NETLOGON защищены целиком.

copy_file, move_file, rename_file и create_directory выключены по умолчанию (WINRIG_ALLOW_FILE_OPS) и подчиняются подтверждению DR-5. Копирование каталога рекурсивно и показывает счётчики; оно отвергает исходное дерево с символической ссылкой или junction, и команда повторяет проверку во время выполнения. Перемещение на одном томе переименовывает; между томами файл копируется и затем удаляется, а каталог отвергается, потому что подразумеваемое рекурсивное удаление попадает под DR-6. Цель каждого инструмента подчиняется границе записи; источник копирования только читается, поэтому чтение или скачивание из системного каталога работает (ADR-0017 §14–20).

download_file выключен по умолчанию (WINRIG_ALLOW_FILE_DOWNLOAD) и подчиняется подтверждению DR-5: он переносит данные с хоста. Локальный каталог — WINRIG_TRANSFER_DIR, в HTTP-режиме с одним подкаталогом на пользователя (DR-3), а каталог и его файлы используют приватные режимы. Инструмент читает файл хоста окнами по 16 МиБ через необработанный канал. Содержимое никогда не идёт в результат строчно. Инструмент хеширует байты по мере чтения и подтверждает всю длину отдельным проходом на хосте. Он принимает одно простое локальное имя файла. Он отвергает разделитель, компонент .., символическую ссылку, зарезервированное имя устройства и управляющий или форматный символ. WINRIG_MAX_TRANSFER_BYTES (по умолчанию 256 МиБ) отвергает более крупный файл по прочитанной длине до любого чтения. Сбой или отмена удаляют частичный локальный файл. Аудит записывает разрешённые пути, размер, оба хеша и состояние подтверждения, но не содержимое (ADR-0017 §21–28).

upload_file выключен по умолчанию (WINRIG_ALLOW_FILE_UPLOAD) и подчиняется подтверждению DR-5, а подтверждение показывает SHA-256 локального файла. Он читает только из каталога передачи пользователя запроса (DR-3). Он отвергает символическую ссылку, файл, доступный другому пользователю на запись, и в Unix файл с несколькими жёсткими ссылками. В Windows инструмент не видит жёсткую ссылку, поэтому держите каталог передачи приватным. Удалённая цель подчиняется границе записи write_file, включая WINRIG_WRITE_ALLOW_PATHS и строку предупреждения. Байты идут на стандартный ввод команды на хосте и никогда не попадают в текст команды. Инструмент хеширует локальные байты ещё раз при отправке, а хост хеширует временный файл до переименования. Изменение локального файла после подтверждения, расхождение или сбой до размещения удаляют временный файл и оставляют цель прежней. Отмена останавливает команду на хосте и удаляет временный файл. Отмена во время размещения оставляет цель в неизвестном состоянии, и аудит записывает unknown. Если сервер останавливается во время отмены, задача удаления может не выполниться, и временный файл остаётся на хосте; аудит всё равно записывает отмену. WINRIG_MAX_TRANSFER_BYTES (по умолчанию 256 МиБ) отвергает более крупный файл до чтения. Аудит записывает два разрешённых пути, размер, оба хеша и состояние подтверждения, но не содержимое (ADR-0017 §26, §38).

run_command запускает одну команду PowerShell на хосте. Он выключен по умолчанию (WINRIG_ALLOW_COMMAND_EXEC) и подчиняется подтверждению DR-5. Сервер кодирует текст команды как base64-строку UTF-16. Затем он декодирует строку в блок скрипта на хосте. Кавычка или терминатор в тексте поэтому не могут выйти за пределы сгенерированной обёртки. Команда должна влезть в одну командную строку WinRS. Поэтому текст содержит не более 979 символов. Более длинный скрипт запускается через run_script.

run_script запускает скрипт PowerShell длиннее одной командной строки WinRS. Он выключен по умолчанию (WINRIG_ALLOW_SCRIPT_EXEC) и подчиняется подтверждению DR-5. Сервер записывает скрипт во временный .ps1 в каталоге временных файлов сеанса кусками, читает файл и выполняет его текст как блок скрипта, затем пытается удалить файл. У скрипта нет $PSScriptRoot, нет $PSCommandPath и нет аргументов. Сервер показывает SHA-256 скрипта в подтверждении и записывает его в аудит, и он сверяет хеш до запуска. Одно подтверждение покрывает всю операцию и показывает весь скрипт. WINRIG_ALLOWED_COMMANDS не фильтрует скрипт: сервер не может проверить текст скрипта по префиксу. Скрипт подчиняется WINRIG_MAX_WRITE_BYTES, а вывод — тем же лимитам, что у run_command (ADR-0017 §29–34).

WINRIG_ALLOWED_COMMANDS — это список префиксов, сравнение без учёта регистра после обрезки пробелов. Пустой список допускает любую команду. Список не образует границу (ADR-0013 §18). Ведущие &, . и powershell -c начинаются с текста, которого в списке может не быть. Псевдоним, оператор вызова и закодированная команда делают то же. Любой текст после разрешённого префикса выполняется. Поэтому ;, перевод строки, | и $(...) не возвращают управление списку. Элемент & или . сам по себе допускает всё. Скрипт, записанный через write_file и затем запущенный, тоже обходит его. Список защищает от случайной команды, а не от намерения.

Список разрешённых хостов и TLS по умолчанию

Заголовок раздела «Список разрешённых хостов и TLS по умолчанию»

Список разрешённых хостов и запрет на понижение проверки TLS настраиваются через окружение (ADR-0004, ADR-0015).

  • По умолчанию сервер подключается только к loopback: localhost, 127.0.0.0/8, ::1. Сервер не разрешает имена через DNS.
  • WINRIG_ALLOW_INSECURE_TLS по умолчанию false: сервер отвергает verify_cert=false до любой сетевой активности.
  • Неверный элемент в WINRIG_ALLOWED_HOSTS (например *.corp.example) останавливает старт.

winrig отдаёт открытый HTTP на /mcp, TLS-слушателя в бинаре нет. Bearer-токен теперь ключ: кто его перехватит, тот достигнет сервера и расшифрует пароль AD.

Многопользовательское развёртывание предполагает, что прокси аутентифицирует клиентов. Прокси передаёт заголовки Authorization, X-AD-User и X-AD-Password без изменений.

Удалённый участок WinRM отдельный: используйте use_ssl или порт 5986, когда хоп до Windows-хоста должен быть шифрованным. Поверх открытого HTTP этот участок WinRM шифрован лишь частично. winrm-rs запечатывает SOAP-тела ключом сессии NTLM, но ключа не существует, пока не завершится рукопожатие. Поэтому первый запрос на каждом новом соединении несёт своё тело открытым текстом рядом с сообщением Type 3. Но если хост отвечает на незашифрованное сообщение пустым HTTP 500 (AllowUnencrypted=false, умолчание Windows), он сразу отвергает этот запрос. Порт 5986 снимает и экспозицию, и отказ.

Не открывайте публичный issue. Используйте приватный канал GitHub в SECURITY.md. Политика безопасности перечисляет принятые границы продукта и то, чего ждать после отчёта.