«Тот же ПК» — ещё не та же среда выполнения ── граница пользователя: AppData, HKCU, DPAPI, учётные данные
· Обновлено: · Го Комура · Windows, DPAPI, Реестр, AppData, Учётные данные, Планировщик заданий
История изменений (первая версия, опубликована 28 Aug 2026)
- Первая публикация
Из Проводника файл настроек читается, из Планировщика заданий — «не найден». Сделали службу — сохранённый пароль не расшифровывается. В своём браузере вы уже вошли, в CI снова экран входа.
В задачах такого рода раньше кода проверяют «от кого и как идёт выполнение». Даже файл или настройка на том же ПК не обязаны быть так же доступны другому пользователю выполнения.
Вопрос статьи: почему приложение ломается от одной только постановки в задание, службу или CI, хотя код не меняли. Если идти от симптома, в таблице ниже — куда переходить.
| Симптом | Что сравнить сначала | Куда читать |
|---|---|---|
| Не находится файл настроек или команда | Пользователь выполнения, фактический путь AppData, пользовательские переменные среды | AppData и переменные среды |
| Нет значения реестра, которое вроде бы записали | Куст пользователя, на который указывает HKCU | HKCU |
| Файл читается, секрет не расшифровывается | Область DPAPI и владелец мастер-ключа | DPAPI |
| Состояние входа в браузер не переносится | Пользователь выполнения, профиль, ключ под DPAPI | Профиль браузера |
| Не удаётся использовать сохранённые учётные данные или сертификат | Сейф и хранилище сертификатов учётной записи выполнения, тип входа задания | Учётные данные и сертификаты |
| При запуске от имени администратора нет диска Z: | Токен до и после повышения и сеанс входа | Повышение UAC |
| Перед переносом хочется пройтись целиком | Зависимости и подготовка по форме выполнения | Чек-лист по форме выполнения, Порядок расследования |
Исходные условия статьи
| Пункт | Содержание |
|---|---|
| Кому | Разработчики, которые делают из бизнес-приложения службу, задание или CI, и эксплуатация, которая разбирает «у меня на месте работает» |
| Среда | Windows 10/11. Проверочный код запускают в PowerShell 5.1 и новее |
| Сложность | Средняя |
1. Сначала выводы
Базовая единица, которой Windows делит среду выполнения, — не ПК, а маркер доступа и SID, то есть «от кого идёт работа». AppData, HKCU, ключи DPAPI, профили браузера и учётные данные принадлежат среде этого пользователя.
Настройки и состояние входа, которые подготовил интерактивно вошедший вы, сами собой не переходят к SYSTEM или другой учётной записи службы. Область данных на всю машину — ProgramData и HKLM; даже при общем доступе нужно проектировать права.
flowchart TB
accTitle: Два мира внутри одного ПК
accDescr: Один и тот же ПК делит только HKLM и ProgramData; мир вашего SID и мир другого SID держат каждый свои AppData, куст реестра и ключ DPAPI и через границу пользователя друг друга не видят
pc["Один ПК"] --> shared["Общее: HKLM, ProgramData"]
pc --> wa["Мир вашего SID"]
pc --> wb["Мир другого SID (SYSTEM и т. п.)"]
wa --> ra["AppData, HKCU, ключ DPAPI"]
wb --> rb["Другой AppData, другой куст, другой ключ"]
ra -.-|"Граница пользователя: друг друга не видно"| rb
Рис. 1: Даже на одном ПК при другом пользователе выполнения AppData, реестр и ключи — другие.
Но один и тот же SID ещё не значит ту же среду. Смотрят и тип входа задания, загрузку профиля IIS, разницу сеанса входа при повышении UAC. Повышение под той же учётной записью не делает HKCU и сейф «другим пользователем»; важно разделять, какая граница меняется.
Дальше — пользователь выполнения как предпосылка (глава 2), пять границ (главы 3–7), проверка по форме выполнения (глава 8), проектирование и расследование (глава 9).
На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 33, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle
2. Предпосылка: всё решает «от кого идёт работа»
2.1 Смотрят не имя пользователя, а SID и маркер
Идентификатор, которым Windows узнаёт пользователя, — не имя, а SID (идентификатор безопасности). У процесса есть маркер доступа, и в нём SID пользователя выполнения. Когда думают об ACL файла, сущности реестра, ключе шифрования, отправная точка — этот субъект выполнения.
Для первой проверки берут whoami /user. Нужен результат в той среде, где проявляется проблема, а не в вашем терминале.
> whoami /user
Сведения о пользователе
----------------
Имя пользователя SID
=============== =============================================
desktop\you S-1-5-21-3623811015-3361044348-30300820-1001
C:\Users\you — сущность профиля, привязанного к этому SID. В профиле — набор папок вроде AppData и куст пользовательского реестра NTUSER.DAT. При первом входе его копируют из профиля по умолчанию, поэтому профиль другого пользователя начинается с другого начального состояния, не с той среды, которую вы настроили.1
flowchart TB
accTitle: От пути запуска до профиля
accDescr: Каким бы путём ни запустили — двойной щелчок, Планировщик заданий, служба или IIS — у процесса есть SID маркера доступа, и средой выполнения становится полный профиль, привязанный к этому SID
e1["Двойной щелчок"] --> tok["Маркер процесса (SID)"]
e2["Планировщик заданий"] --> tok
e3["Служба, IIS"] --> tok
tok --> prof["Полный профиль, привязанный к SID"]
prof -.-> note["Другой SID — другой профиль в начальном состоянии"]
Рис. 2: Путь запуска решает, профиль какого SID процесс несёт на себе.
2.2 У служебных учётных записей тоже своя среда
В Windows есть встроенные учётные записи, которые работают без интерактивного входа человека. У каждой — своя среда выполнения.2
| Учётная запись | SID | Куда смотрят профиль и реестр |
|---|---|---|
| SYSTEM (LocalSystem) | S-1-5-18 |
Профиль под C:\Windows\System32\config\systemprofile. HKCU связан с пользователем по умолчанию3 |
| LocalService | S-1-5-19 |
Под C:\Windows\ServiceProfiles\LocalService. Свой подраздел в HKEY_USERS4 |
| NetworkService | S-1-5-20 |
Под C:\Windows\ServiceProfiles\NetworkService. Как у LocalService, свой профиль и куст4 |
IIS AppPool\<имя> |
S-1-5-82-… |
Идентификатор пула. Профиль по умолчанию не загружается5 |
Разница путей запуска — двойной щелчок, Планировщик заданий, служба, IIS — это разница, чью среду субъекта выполнения вы берёте. Не считайте, что настройки, ключи и учётные данные, подготовленные вашим интерактивным входом, есть и там.
2.3 После «тот же пользователь» проверяют три условия
| Условие | Где видна разница |
|---|---|
| Тип входа | При входе S4U задания пароль не сохраняется, сеть и EFS недоступны6 |
| Загрузка профиля | В IIS от loadUserProfile зависит, доступны ли AppData и пользовательский куст7 |
| Сеанс входа и повышение | Даже при том же SID процесс после повышения может не видеть сетевой диск89 |
Не останавливайтесь на проверке SID: этими тремя разбирают, «что иное при той же учётной записи». Задание — глава 8, IIS — главы 4–5, повышение — глава 7.
3. Граница 1: AppData ── одна переменная среды указывает в другое место
Типичный симптом: едва поставили в задание, файл настроек «пропал». Путь не обязан быть «неразрешённым»: он может корректно разрешиться в путь другого пользователя, и файла там нет.
3.1 Деление AppData и разница по пользователю выполнения
AppData — папки под профилем пользователя. По назначению делятся так.10
| Область | Основное назначение |
|---|---|
%APPDATA% (Roaming) |
Пользовательские настройки, которые должны следовать за перемещаемым профилем |
%LOCALAPPDATA% (Local) |
Данные и кэш, локальные для машины |
| AppData\LocalLow | Данные процессов с низким уровнем целостности |
Один и тот же код, открывая %APPDATA%, попадает в разные места в зависимости от пользователя выполнения.
| Пользователь выполнения | Пример места, где ищут файл настроек |
|---|---|
| Пользователь A | C:\Users\a\AppData\Roaming\MyApp |
| SYSTEM | AppData под systemprofile |
Файла, который сохранил пользователь A, на стороне SYSTEM нет. По одной ошибке «нет файла настроек» это плохо видно, поэтому в расследовании смотрят не имя переменной, а фактически открытый путь.
flowchart TB
accTitle: Разрешение %APPDATA% зависит от пользователя
accDescr: Даже если один код открывает %APPDATA%, для вас это под C:\Users, для SYSTEM — под systemprofile, и вашего файла настроек там нет
code["Один код: открыть %APPDATA%"] --> q{"Кто пользователь выполнения?"}
q -->|"Вы"| a["C:\Users\you\AppData\Roaming"]
q -->|"SYSTEM"| b["AppData под systemprofile"]
b -.-> miss["Файла, который вроде бы положили, нет"]
Рис. 3: Переменная среды не врёт, но куда она разрешается, меняет маркер.
3.2 PATH тоже разный у каждого пользователя
Системные переменные среды общие для машины, пользовательские — свои у каждого. Если команду, которую вы добавили в свой PATH, служба не находит, проверяют и эту разницу.
Куда сохранять, решают по «кто будет читать». Настройки только этого пользователя — в AppData; данные, общие для всех пользователей и служб, — под %ProgramData%, и для последних проектируют ACL. Подробный выбор — в «Где хранить данные Windows-приложения: таблица решений SQLite / JSON / реестр / Access».
flowchart TB
accTitle: Место хранения выбирают по читателю
accDescr: Данные, которые читает только этот пользователь, кладут в AppData или HKCU; данные, общие для всех пользователей и служб, — в ProgramData или HKLM, и для последних проектируют ACL
q{"Кто читает эти данные"} -->|"Только этот пользователь"| f1["AppData, HKCU"]
q -->|"Все пользователи, службы"| f2["ProgramData, HKLM"]
f1 -.-> w1["Если позже станет службой — пересмотреть"]
f2 -.-> w2["Спроектировать право записи и ACL"]
Рис. 4: «После превращения в службу не читается» — следствие того, что эту развилку пропустили на этапе проектирования.
4. Граница 2: HKCU ── «текущий пользователь» меняется с вызывающим
Типичный симптом: сведения о лицензии, которые записал установщик, служба не находит. Даже если и писали, и читали под именем HKCU, сущность не обязательно одна.
4.1 HKCU — псевдоним куста пользователя
HKEY_CURRENT_USER (HKCU) — не самостоятельный куст, а псевдоним, который переключается на сущность в зависимости от вызывающего пользователя. В обычном пользовательском процессе он указывает на ключ этого SID под HKEY_USERS. Содержимое — NTUSER.DAT, загруженный при входе.1
Исключение — HKCU\Software\Classes: сущность — другой файл куста, UsrClass.dat. Он лежит под %LOCALAPPDATA%\Microsoft\Windows.11
HKCU у LocalSystem связан с пользователем по умолчанию (HKEY_USERS\.DEFAULT). Если установщик, запущенный под учётной записью администратора, пишет в HKCU, а служба SYSTEM читает из HKCU, адреса расходятся.3
flowchart TB
accTitle: Сущность псевдонима HKCU
accDescr: Когда приложение открывает HKCU, в вашем процессе это ключ вашего SID под HKEY_USERS, в процессе LocalSystem — ключ пользователя по умолчанию, и значения, которые записал установщик, пропадают
app["Код приложения: открыть HKCU"] --> alias["HKCU — псевдоним сущности"]
alias -->|"Ваш процесс"| ha["Ваш SID под HKEY_USERS"]
alias -->|"Процесс SYSTEM"| hd["HKEY_USERS\.DEFAULT"]
hd -.-> gone["Значения, которое вроде бы записали, нет"]
Рис. 5: Одно имя HKCU — при смене пользователя сущность чтения и записи другая.
Настройки на всю машину кладут в HKLM. Microsoft не рекомендует службам обращаться к HKCU. Если нужно читать настройки пользователя, его олицетворяют (impersonation) и вызывают RegOpenCurrentUser.12
4.2 Проверяют и то, загружен ли профиль
Кроме учётной записи выполнения иногда важно, загружен ли профиль.
Пул приложений IIS по умолчанию работает без загрузки пользовательского профиля. Включив loadUserProfile, получают AppData и куст под профилем.7 Эта настройка связана и с DPAPI следующей главы, и с местом ключей ASP.NET Core Data Protection.
Настройка задания «не сохранять пароль» — отдельно, как ограничение типа входа. Даже при том же имени пользователя вход S4U не даёт сеть и EFS. Конкретная разница конфигураций — в главе 8.6
5. Граница 3: DPAPI ── шифр висит на «ключе пользователя»
AppData и HKCU — задача «искали не там». В DPAPI возникает другая: файл читается, содержимое не расшифровывается. Пример — CryptographicException при превращении в службу приложения, которое пользуется сохранённым паролем.
5.1 Другой владелец мастер-ключа — расшифровать нельзя
DPAPI (Data Protection API) — шифрование, которое Windows даёт приложениям. Вызов CryptProtectData или ProtectedData.Protect в .NET защищает данные, не заставляя приложение самому таскать ключ в коде.13
Но ключ не исчезает. Берут мастер-ключ, которым управляет ОС. В области CurrentUser случайно порождённый мастер-ключ каждого пользователя защищают ключом, выведенным из учётных данных входа, и хранят под профилем.14
flowchart TB
accTitle: Цепочка ключей DPAPI
accDescr: Случайно порождённый мастер-ключ пользователя защищён ключом, выведенным из учётных данных входа; этим мастер-ключом шифруют секрет приложения, и мастер-ключом другого пользователя тот же шифртекст не расшифровать
pwd["Учётные данные входа"] -->|"Защита ключом вывода"| mk["Ваш мастер-ключ"]
mk -->|"Шифрование"| sec["Секрет приложения (сохранённый пароль и т. п.)"]
mk2["Мастер-ключ другого пользователя"] -.->|"Не расшифровать"| sec
mk -.-> loc["Место хранения — под профилем"]
Рис. 6: Данные шифруют не напрямую учётными данными, а мастер-ключом, который защищён ключом, выведенным из учётных данных.
Дальше — минимальный пример: пользователь A шифрует данные, кладёт их в общее место, другой пользователь пытается расшифровать. Общее место хранения не меняет того, кто в CurrentUser является расшифровщиком.
# В сеансе пользователя A: зашифровать в области CurrentUser и сохранить в общее место
Add-Type -AssemblyName System.Security
$bytes = [Text.Encoding]::UTF8.GetBytes("secret")
$enc = [Security.Cryptography.ProtectedData]::Protect($bytes, $null, "CurrentUser")
[Convert]::ToBase64String($enc) | Set-Content C:\ProgramData\demo.bin
# Другой пользователь (например SYSTEM через PsExec) пытается расшифровать
$enc = [Convert]::FromBase64String((Get-Content C:\ProgramData\demo.bin))
[Security.Cryptography.ProtectedData]::Unprotect($enc, $null, "CurrentUser")
# → CryptographicException: данные недействительны в текущем состоянии
5.2 Область выбирают по тому, кто должен уметь расшифровать
| Область | Единица расшифровки | На что смотреть в дизайне |
|---|---|---|
CurrentUser |
Мастер-ключ пользователя, который шифровал | Перенос на другую учётную запись выполнения — расшифровать нельзя |
LocalMachine |
Общий ключ той же машины | Расшифровать может любой процесс на той же машине, поэтому кто читает шифртекст, сужают ACL файла |
Секрет, который читают и служба, и интерактивный пользователь, с самого начала проектируют как сочетание LocalMachine и ACL файла либо запускают от учётной записи того, кто шифровал. Область меняют не чтобы «убрать ошибку расшифровки», а решая, кому разрешена расшифровка. На общем ПК имейте в виду и риск, что защита на уровне машины слишком широка.15
flowchart TB
accTitle: Как выбрать область DPAPI
accDescr: Если расшифровать должен только этот пользователь — область CurrentUser; если несколько субъектов выполнения на том же ПК — LocalMachine, и читателей сужают ACL файла
q{"Кто должен уметь расшифровать"} -->|"Только этот пользователь"| cu["Область CurrentUser"]
q -->|"Несколько субъектов на том же ПК"| lm["Область LocalMachine"]
cu -.-> r1["Запуск от другого пользователя: сбой расшифровки"]
lm -.-> r2["Читателей сужают ACL файла"]
Рис. 7: Область выбирают как дизайн расшифровщика, а не как «кто случайно запустился».
5.3 «Смена» пароля и «сброс» — разные вещи
Ключ, который стережёт мастер-ключ, зависит от учётных данных входа. Когда пользователь сам меняет пароль, мастер-ключ заново защищают ключом от нового пароля, способность расшифровки переходит.
Если же администратор сбрасывает пароль локальной учётной записи, прошлый шифртекст иногда перестаёт расшифровываться. Даже при той же учётной записи нужно смотреть, как меняли учётные данные.14
5.4 Даже в ASP.NET Core проверяют, куда кладут ключ
И не вызывая DPAPI напрямую, можно зависеть от этой границы. ASP.NET Core Data Protection сохраняет кольцо ключей для защиты вроде аутентификации cookie в место, зависящее от среды.16
| Среда | Куда кладут ключ и что из этого следует |
|---|---|
| Профиль пользователя доступен | %LOCALAPPDATA%\ASP.NET\DataProtection-Keys. На Windows шифруют DPAPI |
| Профиля нет, хост — IIS | Откат в реестр HKLM с ACL на учётную запись рабочего процесса |
| Ни то ни другое | Временный ключ только на время процесса. После перезапуска ключ теряется, защищённые данные вроде cookie аутентификации становятся недействительны |
loadUserProfile, setProfileEnvironment и форму хоста смотрят вместе и понимают, куда в этой конфигурации кладётся ключ. Важно не только «запускается», но и «тот же ключ доступен после перезапуска».
Выбор области и разделение с Credential Manager подробно разобраны в «Хранение секретов в Windows-приложениях — избегаем настроек в открытом виде с помощью DPAPI».
6. Граница 4: профиль браузера ── «уже вошёл» принадлежит пользователю
На месте вы уже вошли, а браузер, запущенный в CI, снова показывает экран входа. Задача складывается, если разделить место хранения профиля и ключ шифрования.
6.1 Две границы: место хранения и ключ
Профиль Chromium-браузеров вроде Chrome и Edge по умолчанию лежит в папке User Data под %LOCALAPPDATA%. История, cookie, расширения, сохранённые пароли принадлежат среде этого пользователя Windows.17
Cookie и сохранённые пароли шифруют ключом внутри профиля, а сам ключ защищён DPAPI. Поэтому скопировать папку другому пользователю или на другую машину — ключи не совпадут, расшифровать нельзя.
Современный Chrome ещё накладывает App-Bound Encryption (шифрование, привязанное к приложению). Расшифровку ключа проводят через службу с правами SYSTEM и проверяют не только пользователя, но и идентификатор запросившего приложения.18 Это про линию Chromium; у Firefox и браузеров с собственной защитой профиля иначе.
| Граница | Что происходит на стороне автоматизации |
|---|---|
| Граница AppData | У пользователя агента CI или службы нет привычного профиля |
| Граница DPAPI | Даже скопировав папку, защищённый ключ не расшифровать |
flowchart TB
accTitle: Две границы, на которых стоит состояние входа в браузер
accDescr: Профиль браузера лежит под LOCALAPPDATA и принадлежит границе 1; ключ шифрования cookie защищён DPAPI и принадлежит границе 3, поэтому ни профиль, ни ключ другому пользователю не переходят
prof["Профиль браузера"] --> loc["Место — под LOCALAPPDATA"]
prof --> key["Ключ cookie защищён DPAPI"]
loc -.-> ci["У пользователя CI — пустой другой профиль"]
key -.-> copy["Копией папки не унести"]
Рис. 8: «Унести уже вошедшее состояние» упирается и в границу 1, и в границу 3.
6.2 В автоматизации явно задают, как получают состояние входа
Selenium и Playwright по умолчанию запускаются с одноразовым временным профилем. Поэтому даже у того же пользователя состояние входа привычного браузера само не подхватывается.
Даже указав каталог постоянного профиля, на этапе переноса к CI или службе другого пользователя остаются проблемы места хранения и ключа из предыдущего раздела. Мера — не копировать уже вошедший профиль, а одно из двух.
- Закодировать шаги входа тестовой учётной записи.
- Явно сохранить и восстановить cookie и подобное механизмом storage state инструмента автоматизации.
То, что другой пользователь не может взять cookie одним копированием, — не только неудобство, но и граница безопасности. Автоматизацию проектируют исходя из того, что эта граница есть.
7. Граница 5: учётные данные и сертификаты ── сейф свой у каждого пользователя
Учётные данные, сохранённые через cmdkey, при выполнении задания не используются, ошибка аутентификации. И здесь проверяют не «сохранили на ПК», а «от какого пользователя сохранили».
7.1 Диспетчер учётных данных — сейф каждого пользователя выполнения
Диспетчер учётных данных, который смотрят через cmdkey /list, — сейф каждого пользователя.19 Сохранённые учётные данные лежат на диске, но защищены DPAPI, и ими пользуется программа, работающая от этого пользователя.20
В сейф попадают сохранённые учётные данные файлового сервера и сетевого диска, токен Git, который сохранил git-credential-manager, сохранённый пароль RDP, секреты приложений через Credential API.
То, что лежит в сейфе интерактивно вошедшего вас, в сейфе учётной записи службы или задания нет. Нужные учётные данные готовят установкой в контексте самой учётной записи выполнения. Если с места проходит только git pull, проверяют, какой сейф используется.
Но задание в конфигурации S4U одним вложением учётных данных не закрывается. Сначала смотрят тип входа и рассматривают конфигурацию с сохранением пароля или переход на учётную запись службы. Порядок шагов — в главе 8.
flowchart TB
accTitle: Сейф учётных данных свой у каждого пользователя
accDescr: В вашем сейфе лежат учётные данные git, сохранённые учётные данные файлового сервера и пароль RDP; сейф пользователя службы пуст, пока их туда не вложили, и это и есть лицо ошибки аутентификации
you["Ваш сейф"] --> g["Учётные данные git"]
you --> n["Сохранённые учётные данные файлового сервера"]
you --> r["Сохранённый пароль RDP"]
svc["Сейф пользователя службы"] -.-> empty["Не вложили — пуст = лицо ошибки аутентификации"]
Рис. 9: «С места аутентификация проходит» — лишь сокращение от «доступен ваш сейф».
7.2 У сертификата раздельно смотрят хранилище и права закрытого ключа
| Хранилище | Граница и назначение |
|---|---|
Cert:\CurrentUser |
Хранилище каждого пользователя. Закрытый ключ клиентского сертификата, который сюда положили, защищён на уровне пользователя |
Cert:\LocalMachine |
Хранилище на всю машину. Сюда кладут сертификат службы и ACL закрытого ключа разрешают чтение учётной записи службы |
Сертификат для службы проектируют комплектом: хранилище LocalMachine и ACL закрытого ключа.21 Сущность пользовательского хранилища — под HKCU\Software\Microsoft\SystemCertificates, то есть внутри границы HKCU.22
Подробности — в «Хранилище сертификатов Windows на практике — пользователь или компьютер».
7.3 При повышении UAC разделяют «та же учётная запись» и «другая учётная запись»
При входе пользователя-администратора с включённым UAC создаются два связанных маркера: стандартный с урезанными правами и полный администраторский.8
Назначение сетевого диска — на сеанс входа. В Проводнике Z: виден, а из средства, запущенного от имени администратора, иногда нет.9
| Способ запуска | Что меняется и что нет |
|---|---|
| Повышение UAC под той же учётной записью | SID тот же, HKCU и сейф учётных данных те же. Назначение диска на сеанс входа может пропасть из виду |
| Повышение / RunAs с учётными данными другой администраторской учётной записи | Меняется и SID. HKCU и сейф — уже того администратора. AppData, ключи, состояние браузера тоже под границей другого пользователя |
«После повышения не работает» не списывают оптом на смену пользователя: сначала проверяют, та же ли учётная запись.
flowchart TB
accTitle: Раскол внутри того же пользователя, который даёт повышение UAC
accDescr: Вход администратора с включённым UAC создаёт стандартный и повышенный маркеры; сетевой диск, назначенный со стороны стандартного маркера, из процесса с повышенным маркером не виден
logon["Вход пользователя-администратора"] --> t1["Стандартный маркер"]
logon --> t2["Повышенный маркер"]
t1 --> d1["Диск Z:, назначенный здесь"]
t2 -.->|"Другой сеанс входа"| d2["Из повышенного средства Z: не виден"]
Рис. 10: Повышение создаёт «другой мир того же пользователя». Граница — не только про SID.
8. Чек-лист по форме выполнения
8.1 В задании после учётной записи выполнения смотрят тип входа
В Планировщике заданий даже при том же пользователе от типа входа зависит, чем можно пользоваться.6
| Тип входа | Что проверить |
|---|---|
| Интерактивный маркер (InteractiveToken) | Конфигурация запуска в сеансе, где есть вход |
| Сохранение пароля (Password) | Конфигурация, в которой учётные данные доступны и без интерактива |
| S4U (пароль не сохраняется) | Пароль не сохраняется, сетевые ресурсы и зашифрованные файлы (EFS) недоступны |
Если задание не берёт сохранённые учётные данные, сначала проверяют ограничение S4U. Не делайте мерой «при S4U доложить учётные данные в сейф». Рассматривают конфигурацию с сохранением пароля или переход на учётную запись службы и уже потом, если нужно, вкладывают в сейф учётной записи выполнения.
Подробности настроек — в «Планировщик заданий не запускается или завершается с 0x1 — диагностика и надёжная эксплуатация».
flowchart TB
accTitle: Вторая ось: тип входа задания
accDescr: Конфигурация выполнения Планировщика заданий имеет типы входа InteractiveToken, Password и S4U; при S4U пароль не сохраняется, сеть и EFS недоступны
task["Конфигурация выполнения задания"] --> lt{"Тип входа"}
lt -->|"Интерактивный маркер"| it["Запуск в сеансе, где есть вход"]
lt -->|"Сохранение пароля"| pw["Учётные данные доступны и без интерактива"]
lt -->|"S4U (пароль не сохраняется)"| s4u["Сеть и EFS не достаются"]
Рис. 11: Даже при том же пользователе выполнения от способа входа зависит, чем можно пользоваться.
8.2 Таблица проверки до смены места выполнения
| Форма выполнения | Субъект выполнения | Какие границы и симптомы смотреть особенно |
|---|---|---|
| Планировщик заданий | Учётная запись, указанная при регистрации | Границы 1, 2, 3, 5. Куда смотрят AppData и HKCU, расшифровка DPAPI, сейф. При S4U нет сети и EFS |
| Служба Windows | SYSTEM, LocalService, NetworkService, учётная запись службы | Границы 1–5. SYSTEM берёт systemprofile и HKCU пользователя по умолчанию, LocalService/NetworkService — свои среды под ServiceProfiles. Ключ, сейф и состояние браузера разработчика не переходят |
| Пул приложений IIS | Собственный идентификатор пула вроде IIS AppPool\<имя> |
Границы 1, 2, 3, 5. По умолчанию профиль не загружен; место ключа Data Protection; доступ к закрытому ключу сертификата CurrentUser |
| RunAs / повышение UAC | Указанный пользователь или другой маркер того же пользователя | Та же учётная запись — SID, HKCU, сейф те же, назначение диска зависит от сеанса. Другая учётная запись — проверяют границы 1–5 целиком |
| Агент CI/CD | Пользователь службы агента. Часто без истории интерактивного входа | Границы 1–5. Нет ли зависимости от профиля и состояния входа браузера, учётных данных Git, настроек HKCU разработчика и данных под DPAPI |
| RDP / общий сервер | Несколько сеансов того же пользователя или несколько пользователей | Несколько сеансов того же пользователя делят AppData и HKCU — осторожно с конфликтом записи. Другой пользователь — разделены границами 1–5 |
Последняя строка — предупреждение в обратную сторону. Несколько сеансов RDP того же пользователя не делают AppData и HKCU независимыми по сеансам. Здесь проблема не «не видно», а «пишут в одно общее».
9. Ориентиры проектирования и разбора неисправностей
9.1 Место хранения и подготовку выбирают от субъекта, который пользуется
| Объект | База дизайна |
|---|---|
| Настройки конкретного пользователя | Кладут в AppData, HKCU |
| Данные и настройки, общие для всех пользователей и служб | Кладут в ProgramData, HKLM и проектируют право записи и ACL |
| Секреты | Область DPAPI выбирают от того, кто должен уметь расшифровать. Для LocalMachine проектируют и ACL файла |
| Учётные данные и сертификаты службы | Вложение в сейф учётной записи выполнения, размещение в хранилище сертификатов LocalMachine и ACL закрытого ключа включают в шаги установки |
Если впереди превращение в службу, субъекта выполнения включают в «читателей» уже на этапе выбора места хранения. Принцип — не тащить в эксплуатацию зависимость «случайно было в среде разработчика».
9.2 Расследование идёт от субъекта выполнения к фактическому адресу
flowchart TB
accTitle: Порядок расследования неполадок границы пользователя
accDescr: Пользователя выполнения подтверждают whoami, маркер смотрят в Process Explorer, фактически прочитанные путь и ключ реестра находят Process Monitor, при необходимости воспроизводят из мира другой стороны через psexec
s1["whoami /all: подтвердить пользователя выполнения"] --> s2["Process Explorer: подтвердить маркер"]
s2 --> s3["ProcMon: найти фактические путь и ключ"]
s3 --> s4["Воспроизвести из мира другой стороны через psexec"]
s3 -.-> hint["Путь профиля не тот, что ждали, — зацепка"]
Рис. 12: Подтверждают пользователя выполнения, смотрят фактические путь и ключ реестра, затем воспроизводят от целевого субъекта выполнения.
| Шаг | Как смотреть | На что смотреть |
|---|---|---|
| 1. Подтвердить пользователя выполнения | Сразу после запуска писать whoami /all в журнал |
Субъект выполнения задания или службы, где проявляется проблема, тот же, что на месте? |
| 2. Подтвердить маркер | Process Explorer | Пользователь и сеанс процесса. Другой пользователь или другой контекст выполнения того же? |
| 3. Посмотреть фактический адрес | Process Monitor | Путь открытого файла и ключ реестра. Нет ли в PATH NOT FOUND профиля, которого не ждали? |
| 4. Воспроизвести от целевого субъекта | Для SYSTEM — psexec -s -i cmd |
Те же операции из оболочки SYSTEM и сравнить с своей интерактивной средой |
Нет настроек — к AppData и HKCU; файл читается, не расшифровывается — к DPAPI; падает только аутентификация — к сейфу, сертификату, типу входа. Не судить по одному тексту ошибки: короткий путь — собрать «кто, куда, каким ключом или учётными данными».
10. Итог
«Работает на месте» значит «работает от этого пользователя, в этом профиле, с этим ключом и сейфом». Даже запуск того же .exe на том же ПК при постановке в задание, службу или CI нужно считать переносом среды выполнения.
AppData и пользовательские переменные среды смотрят в профиль пользователя выполнения, HKCU тоже направлен на куст этого пользователя. Область CurrentUser DPAPI зависит от мастер-ключа пользователя; состояние входа в браузер и диспетчер учётных данных тоже под этой границей.
Даже при том же SID остаются тип входа, загрузка профиля, разница сеанса из-за повышения UAC. Наоборот, если несколько сеансов того же пользователя делят AppData и HKCU, думают о конфликте записи.
На ревью дизайна проверьте такой вопрос.
Этот код верен, от какого бы пользователя ни шло выполнение?
Нужные места хранения, ключи и учётные данные явно готовят для фактического субъекта выполнения. Если этот принцип проверить до переноса, аварии вида «код не меняли, а сломалось» меньше уже на этапе проектирования.
Похожие статьи
- Хранение секретов в Windows-приложениях — избегаем настроек в открытом виде с помощью DPAPI
- Где хранить данные Windows-приложения: таблица решений SQLite / JSON / реестр / Access
- Планировщик заданий не запускается или завершается с 0x1 — диагностика и надёжная эксплуатация
- Учётные записи служб Windows: LocalSystem, виртуальные учётные записи и gMSA
- Хранилище сертификатов Windows на практике — пользователь или компьютер
Смежные области консультирования
Компания KomuraSoft LLC занимается проектированием среды выполнения при превращении бизнес-приложения в службу или задание, разбором неисправностей вида «у меня на месте работает» и проектированием хранения секретов в Windows-приложениях.
- Разработка приложений для Windows
- Расследование неисправностей и анализ причин
- Использование и перенос существующих активов
- Связаться с нами
Справочные ссылки
-
Microsoft Learn, About User Profiles. О том, что профиль пользователя создаётся при первом входе и состоит из куста реестра NTUSER.DAT (загружается при входе и отображается на HKEY_CURRENT_USER) и набора папок профиля в файловой системе. ↩ ↩2
-
Microsoft Learn, Local accounts. О том, что SYSTEM (S-1-5-18), NETWORK SERVICE (S-1-5-20) и LOCAL SERVICE (S-1-5-19) — учётные записи локальной системы по умолчанию, которыми пользуются ОС и службы. ↩
-
Microsoft Learn, LocalSystem Account. О том, что маркер LocalSystem содержит NT AUTHORITY\SYSTEM, не связан ни с какой учётной записью вошедшего пользователя, поэтому HKEY_CURRENT_USER связан с пользователем по умолчанию, а доступ к профилю другого пользователя требует его олицетворения. ↩ ↩2
-
Microsoft Learn, LocalService Account. О том, что учётная запись LocalService имеет свой подраздел под HKEY_USERS и HKEY_CURRENT_USER связан с LocalService. То же у NetworkService (NetworkService Account). ↩ ↩2
-
Microsoft Learn, Application Pool Identities. О том, что пул приложений работает под идентификатором пула, IIS по умолчанию не загружает профиль пользователя Windows, а атрибут LoadUserProfile=true позволяет профиль загрузить. ↩
-
Microsoft Learn, logonType Simple Type. О типах входа задания S4U, Password и InteractiveToken; при входе S4U пароль не сохраняется, нет доступа ни к сети, ни к зашифрованным файлам. ↩ ↩2 ↩3
-
Microsoft Learn, Process Model Settings for an Application Pool. О том, что в processModel пула приложений есть атрибуты loadUserProfile и setProfileEnvironment, которые управляют, загружает ли рабочий процесс профиль пользователя. ↩ ↩2
-
Microsoft Learn, How User Account Control works. О том, что при включённом UAC вход пользователя-администратора создаёт два связанных маркера: стандартный пользовательский и полный администраторский маркер доступа. ↩ ↩2
-
Microsoft Learn, Mapped drives are not available from an elevated prompt. О том, что сетевой диск, назначенный в сеансе со стандартным маркером, недоступен из повышенного процесса, потому что два связанных сеанса входа держат назначения дисков отдельно. ↩ ↩2
-
Microsoft Learn, KNOWNFOLDERID. О том, что FOLDERID_RoamingAppData (%APPDATA%), FOLDERID_LocalAppData (%LOCALAPPDATA%) и FOLDERID_LocalAppDataLow определены как известные папки каждого пользователя. ↩
-
Microsoft Learn, Error occurs during desktop setup and desktop location is unavailable. О том, что профиль пользователя держит два файла куста — NTUSER.DAT и UsrClass.dat — и UsrClass.dat лежит под AppData\Local\Microsoft\Windows. ↩
-
Microsoft Learn, Services and the Registry. О том, что службе не следует обращаться к HKEY_CURRENT_USER и HKEY_CLASSES_ROOT, а при олицетворении пользователя следует вызывать RegOpenCurrentUser. ↩
-
Microsoft Learn, CryptProtectData function. О том, что CryptProtectData обычно защищает данные сеансовым ключом, связанным с вошедшим пользователем, и предполагает расшифровку тем же пользователем; флаг CRYPTPROTECT_LOCAL_MACHINE переключает на защиту на уровне машины. ↩
-
Microsoft Learn, Windows Data Protection. О том, что DPAPI защищает случайно порождённый мастер-ключ, шифруя его ключом, выведенным из пароля пользователя; мастер-ключ хранится под профилем; при смене пароля мастер-ключ защищают заново. ↩ ↩2
-
Microsoft Learn, ProtectedData Class. О том, что DataProtectionScope.CurrentUser разрешает расшифровку только защитившему пользователю, а LocalMachine — любому процессу на той же машине. ↩
-
Microsoft Learn, Data Protection key management and lifetime in ASP.NET Core. О том, что при доступном профиле ключ кладут в %LOCALAPPDATA%\ASP.NET\DataProtection-Keys и на Windows шифруют DPAPI; если хост IIS и профиля нет — откат в реестр HKLM с ACL на учётную запись рабочего процесса; если ни одно условие не выполняется, ключ пропадает с концом процесса и защищённые полезные данные нельзя расшифровать; участвует атрибут setProfileEnvironment. ↩
-
проект Chromium, User Data Directory. О том, что на Windows каталог User Data Chrome по умолчанию — %LOCALAPPDATA%\Google\Chrome\User Data, и профиль (история, закладки, cookie и т. п.) лежит под ним. ↩
-
Google Security Blog, Improving the security of Chrome cookies on Windows. О том, что Chrome на Windows шифровал cookie и подобное через DPAPI, а App-Bound Encryption защищает ключ через службу с правами SYSTEM и проверяет идентификатор запросившего приложения. ↩
-
Microsoft Learn, cmdkey. О том, что команда cmdkey умеет показывать, создавать и удалять сохранённые имена пользователей и пароли (учётные данные). ↩
-
Microsoft Learn, Cached and Stored Credentials Technical Overview. О том, что учётные данные, сохранённые в диспетчере учётных данных, лежат на диске и защищены DPAPI, и программа, работающая от этого пользователя, может обращаться к учётным данным этого хранилища. ↩
-
Microsoft Learn, Local Machine and Current User Certificate Stores. О двух видах хранилища сертификатов: хранилище локальной машины (на всю машину) и хранилище текущего пользователя (на пользователя). ↩
-
Microsoft Learn, System Store Locations. О том, что системное хранилище CERT_SYSTEM_STORE_CURRENT_USER лежит в реестре под HKEY_CURRENT_USER\Software\Microsoft\SystemCertificates. ↩
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Обработка ошибок и retry в PowerShell — от ловушки нерабочего try/catch до exit code и типовых схем повтора
На практике разбираем различие завершающих и незавершающих ошибок PowerShell, ловушку, из-за которой не срабатывает try/catch, стандартны...
Где хранить данные Windows-приложения: таблица решений SQLite / JSON / реестр / Access
Куда и в каком виде хранить данные настольного Windows-приложения. Разбираем выбор между AppData и ProgramData, сильные стороны и ловушки...
Планировщик заданий не запускается или завершается с 0x1 — диагностика и надёжная эксплуатация
Разбираем, как вывести периодические задания Windows в стабильную эксплуатацию: учётная запись выполнения и тип входа, типичные причины з...
Введение в профили пользователей Windows — AppData и NTUSER.DAT
Базовое устройство профиля пользователя Windows: как разделять AppData, локальный / перемещаемый / Mandatory / Temporary, Folder Redirect...
Почему общая папка Windows то открывается, то нет — разбор Kerberos, NTLM и учётных данных
Диагностируйте прерывистый доступ к общей папке Windows по симптомам и журналам. Проверяйте имена и IP, сбои только в приложении, пустые ...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Бизнес-приложения, интеграция оборудования и средства связи — от требований до разработки.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Приложение находит файл настроек, если запустить его из Проводника, и не находит из Планировщика заданий. Почему?
- Потому что переменные вроде %APPDATA% разрешаются в профиль «пользователя, от которого идёт выполнение». Если задание запущено как SYSTEM или под другой учётной записью, переменные указывают на другой профиль (у SYSTEM — под systemprofile), и сохранённого вами файла настроек там нет. Проверьте учётную запись задания и данные, которые должны быть общими, кладите под ProgramData — это постоянная мера.
- Пароль, сохранённый через ProtectedData.Protect, после превращения в службу перестал расшифровываться.
- DPAPI в области CurrentUser зависит от мастер-ключа пользователя, который шифровал. У учётной записи службы другой мастер-ключ, поэтому расшифровка падает с CryptographicException. Секрет, который читают и служба, и интерактивный пользователь, проектируйте заново как LocalMachine плюс ACL файла либо запускайте службу от той же учётной записи, которая шифровала.
- Что будет, если HKCU читать из процесса под SYSTEM?
- У процесса LocalSystem HKEY_CURRENT_USER связан с пользователем по умолчанию (HKEY_USERS\.DEFAULT), поэтому значения, которые интерактивный пользователь записал в HKCU, не видны. Общие для машины настройки кладите в HKLM; если без пользовательских настроек не обойтись, олицетворите этого пользователя и вызовите RegOpenCurrentUser.
- Можно ли скопировать уже вошедший профиль Chrome/Edge на машину CI?
- Как правило нет. Профиль Chromium-браузеров вроде Chrome и Edge лежит под %LOCALAPPDATA% пользователя, а ключи шифрования cookie и сохранённых паролей защищены DPAPI этого пользователя. Скопировать папку другому пользователю или на другую машину — ключи не совпадут, расшифровать нельзя (у Firefox и браузеров с собственной защитой профиля ситуация иная). В автоматизации кодируйте вход тестовой учётной записи или пользуйтесь механизмом storage state инструмента автоматизации.
- Учётные данные, сохранённые через cmdkey, не используются при запуске из Планировщика заданий.
- Сейф диспетчера учётных данных свой у каждого пользователя: сохранили вы в сейф интерактивного входа. Плюс задание в конфигурации «не сохранять пароль» (S4U) работает без сетевых учётных данных, поэтому в сейф при S4U класть их бесполезно. Сначала рассмотрите конфигурацию «сохранять пароль» или переход на учётную запись службы и только потом, если нужно, вложите учётные данные в сейф самой выполняющей учётной записи.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.