
Вспомогательная функция writeToFile в Consul Template напрямую открывала указанное оператором место назначения и следовала по связанным компонентам пути, позволяя сгенерированному выводу выйти за пределы целевого каталога и перезаписать уже существующий файл.
Помощник writeToFile в Consul Template открывал указанное оператором место назначения напрямую и следовал по связанным компонентам пути, позволяя отрисованному выводу выйти за пределы задуманного каталога и перезаписать существующий файл.
Я нашёл эту проблему при анализе HashiCorp Consul Template, задав прямой вопрос о безопасности файловой системы:
Если writeToFile получает путь, который выглядит находящимся внутри задуманного каталога, проверяет ли он, куда файловая система фактически запишет данные?
В данном случае ответ был отрицательным.
Помощник шаблона writeToFile открывал итоговый указанный пользователем путь напрямую через os.Create() или os.OpenFile().
Эти операции следовали по символическим ссылкам, junction каталогов и эквивалентным перенаправлениям файловой системы, уже присутствующим в пути назначения.
Это означало, что строка пути могла оставаться под задуманным оператором корневым каталогом, в то время как фактическая запись попадала в другое место.
В моём контролируемом proof of concept связанный родительский каталог перенаправил отрисованный вывод за пределы задуманного дерева каталогов и привёл к перезаписи существующего целевого файла.
Эта проблема стала CVE-2026-14361.
Бюллетень HashiCorp: HCSEC-2026-20
Бюллетень IBM: бюллетень безопасности CVE-2026-14361
CVE: CVE-2026-14361
Исправлено в: 0.42.1
photo0
указанное оператором место назначения выглядит находящимся внутри задуманного корневого каталога -> атакующий заранее размещает связанный родительский или конечный компонент пути -> writeToFile открывает путь напрямую -> файловая система разрешает запись за пределами задуманного каталога -> отрисованный секрет перенаправляется -> существующий целевой файл может быть перезаписан
Consul Template отрисовывает данные из таких источников, как Consul и Vault.
Помощник writeToFile позволяет шаблону записывать выбранное содержимое в отдельный локальный файл, применяя запрошенных владельца, группу и режим прав доступа.
В документации HashiCorp этот помощник специально демонстрируется на материалах PKI:
private key -> writeToFile /my/path/to/cert.key
certificate authority -> writeToFile /my/path/to/cert.pem
certificate -> append to /my/path/to/cert.pem
Это делает его не просто обычным помощником для вывода данных.
Содержимое, пересекающее эту границу, может включать:
Важным вопросом было не то, может ли writeToFile создать запрошенное имя файла.
Настоящий вопрос заключался в следующем:
Записывает ли процесс данные в то место файловой системы, которое задумал оператор, или просто в тот объект, на который путь разрешается в момент открытия?
В уязвимых версиях он доверял второму.
Помощники записи — это поверхности безопасности высокой ценности, поскольку они переходят от данных приложения к изменениям файловой системы.
Интересные сбои часто не являются классическим обходом через ../.
Это сбои разрешения путей:
Это особенно важно, когда процесс работает с большими привилегиями в файловой системе, чем атакующий.
Локальный атакующий с низкими привилегиями может быть не в состоянии напрямую перезаписать чувствительный файл.
Но если он может влиять на компонент пути внутри задуманного каталога записи, более привилегированный процесс Consul Template может выполнить запись за него.
Именно эту границу я исследовал.
Я подошёл к этому не как к обычному анализу обхода путей.
Предоставленному пути не нужны сегменты ...
Он мог всё время оставаться лексически внутри задуманного корневого каталога.
Более сильный вопрос звучал так:
Отвергаются ли связанные компоненты назначения до того, как будет записано чувствительное содержимое?
Этот вопрос важен для двух случаев:
Второй случай особенно полезен, потому что журналы и конфигурация по-прежнему показывают безобидно выглядящий путь внутри ожидаемого каталога.
Файловая система разрешает его в другом месте.
Корневая причина — прямое создание файла на основе пути без проверки символических ссылок.
В протестированной ревизии writeToFile() выбирал один из двух путей открытия.
Режим добавления использовал:
f, err = os.OpenFile(
path,
os.O_APPEND|os.O_WRONLY|os.O_CREATE,
perm,
)
Обычный режим записи использовал:
dirPath := filepath.Dir(path)
if _, err := os.Stat(dirPath); err != nil {
err := os.MkdirAll(dirPath, os.ModePerm)
if err != nil {
return "", err
}
}
f, err = os.Create(path)
Ни один из путей не отвергал связанные компоненты назначения до открытия файла.
Это важно, потому что:
os.Create(path) следует существующим перенаправлениям файловой системы и усекает разрешённый файлos.OpenFile(path, ...) следует связанным компонентам пути в режиме добавленияТо же предположение, основанное на пути, сохранялось и после записи.
Владелец и права доступа снова применялись через путь:
err = os.Chown(path, uid, gid)
err = os.Chmod(path, perm)
Это означало, что операции с метаданными также были привязаны к изменяемому имени пути, а не к уже открытому файловому дескриптору.
Потому что атакующий может подготовить перенаправление до запуска writeToFile.
Для базовой атаки не требуется вероятностная гонка.
Последовательность проста:
writeToFile открывает её напрямуюВ этом и заключается вся уязвимость.
Действительно, операционные системы обычно следуют по символическим ссылкам при открытии файлов по пути.
Это не делает такое поведение приложения безопасным.
Вопрос безопасности не в том:
«Вёл ли себя Go так, как задокументировано?»
Настоящий вопрос в том:
Проверял ли помощник, записывающий чувствительные отрисованные данные, что разрешённое место назначения совпадает с задуманным оператором?
В уязвимых версиях — нет.
Это различие важно, потому что Consul Template может работать:
Следование перенаправлению файловой системы, созданному атакующим, в таком контексте создаёт реальную проблему привилегий и границ доверия.
Я создал автономный воспроизводитель на основе точного поведения writeToFile в протестированном коммите.
Контролируемая настройка использовала:
writeToFileПоток воспроизведения был таким:
Наблюдаемое поведение было таким:
writeToFile последовал перенаправлениюЭто подтвердило обе части утверждения:
Символическая ссылка в конечном компоненте уже продемонстрировала бы небезопасное следование ссылкам.
Но связанный родительский компонент доказывает более сильный эксплуатационный тезис:
Существующий целевой файл тоже был важен.
Без него PoC показал бы только неожиданное создание файла.
Начав с существующего файла и проверив его конечный хэш, воспроизведение напрямую доказало поведение перезаписи.
Это сделало результат более конкретным, чем утверждение, основанное только на исходном коде.
Эта проблема требует влияния на локальную файловую систему.
Атакующему нужен достаточный доступ, чтобы создать или изменить символическую ссылку, junction каталога или эквивалентное перенаправление в месте записи или под ним.
Затем процесс Consul Template должен выполнить запись через этот путь.
Воздействие сильно зависит от привилегий процесса и отрисованного содержимого.
Практические последствия включают:
Это не было удалённой неаутентифицированной произвольной записью из установки по умолчанию.
Но это был явный отказ локальной границы доверия с существенным воздействием в привилегированных развёртываниях или развёртываниях с общей файловой системой.
HashiCorp классифицировал проблему как:
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N
Опубликованная базовая оценка:
4.7 / Medium
Этот вектор отражает:
Официальные бюллетени также явно документируют, что перенаправленная запись может перезаписать существующий файл.
Оценка умеренная, потому что эксплуатация зависит от условий локального контроля над путём, а не потому, что эффект в файловой системе теоретический.
Бюллетень HashiCorp перечисляет:
Affected: consul-template up to and including 0.42.0
Fixed: consul-template 0.42.1
Исправление вышло в релизе 0.42.1 8 июля 2026 года.
Патч 0.42.1 укрепил путь записи на нескольких уровнях.
Исправленный помощник использует os.Lstat() для проверки:
и отклоняет эти компоненты, если они являются ссылками.
Это закрывает случаи прямого перенаправления родительского каталога и символьной ссылки в конечном файле, охваченные отчётом и регрессионными тестами.
На поддерживаемых платформах Unix назначение открывается с флагом O_NOFOLLOW.
Это заставляет само открытие завершиться ошибкой, если конечный компонент станет символической ссылкой между предварительной проверкой и открытием.
Это важно, потому что одна лишь предварительная проверка может создать ещё одно окно TOCTOU.
Платформенно-зависимая реализация является no-op на Windows, где O_NOFOLLOW недоступен через тот же механизм.
Патч заменил операции с владельцем и режимом, основанные на пути, операциями на основе дескриптора:
f.Chown(uid, gid)
f.Chmod(perm)
Это привязывает изменения метаданных к файлу, который был фактически открыт, вместо повторного разрешения пути позже.
Теперь помощник создаёт родительский каталог только тогда, когда ошибка stat действительно является os.IsNotExist.
Другие ошибки, такие как сбои прав доступа, возвращаются вместо продолжения по пути записи.
Патч добавил целевые тесты для:
Публичное обсуждение патча чётко документирует важное ограничение.
Новая проверка проверяет:
Она не обходит и не отклоняет каждый более высокий родительский компонент.
Поддерживающие выбрали эту границу, потому что у writeToFile нет настроенного корневого каталога песочницы, к которому можно привязать полную проверку изоляции, а распространённые операционные системы могут включать легитимные управляемые ссылки в префиксы путей, например /var -> /private/var на macOS.
O_NOFOLLOW также защищает конечный компонент, а не каждый родительский каталог.
Это не меняет статус сообщённой уязвимости или официальную исправленную версию.
Но это проясняет точное свойство безопасности, обеспечиваемое патчем:
Это различие стоит сохранить в техническом описании.
Я сообщил об этой проблеме конфиденциально в HashiCorp Security 21 марта 2026 года как о второй независимой находке в Consul Template.
Отчёт включал:
Первоначальное повторное письмо отсутствовало в очереди отчётов команды безопасности, вероятно, потому что оно было перехвачено спам-фильтром списка рассылки.
После того как я повторно переслал полный отчёт, HashiCorp связался с инженерной командой и исследовал проблему отдельно от первой проблемы Consul Template.
HashiCorp исправил уязвимость в 0.42.1 и опубликовал HCSEC-2026-20 8 июля 2026 года.
IBM опубликовал соответствующий бюллетень безопасности для того же CVE.
Оба официальных бюллетеня указали автора отчёта:
Mohamed Abdelaal (0xmrma)
Ключевой урок прост:
строка пути — это не то же самое, что объект файловой системы, на который она указывает
Это различие важно всегда, когда привилегированный код записывает пути, на которые влияет атакующий.
Проверки того, что строка начинается с задуманного каталога, недостаточно.
Даже чистый путь без последовательностей обхода может разрешиться в другом месте через:
Чувствительная операция должна быть привязана к месту назначения, чья идентичность проверена на правильной границе.
Эта проблема также подтверждает более общее правило:
когда у вас уже есть открытый файловый дескриптор, применяйте чувствительные к безопасности операции через этот дескриптор, а не разрешайте путь заново
Именно поэтому изменения Chown и Chmod на основе дескриптора так важны.
writeToFile задокументирован для чувствительных материалов, таких как сертификаты и закрытые ключиos.Create или os.OpenFileChown и Chmod вносили дополнительное доверие к изменяемому пути0.42.1 добавила проверки ссылок, O_NOFOLLOW там, где это поддерживается, операции с метаданными на основе дескриптора и регрессионные тестыЭта уязвимость не была связана с обходом через ../.
Строка пути выглядела корректно.
Место назначения в файловой системе — нет.
Consul Template принял задуманный оператором путь, последовал перенаправлению, созданному атакующим, и записал отрисованное содержимое в другой файл.
Именно поэтому это стало CVE-2026-14361.
Исправлено в consul-template 0.42.1.