В сервисе имя файла проверяют на отсутствие последовательности «../», а затем декодируют URL-кодирование. Какую уязвимость создаёт такой порядок?
Такой порядок создаёт риск обхода проверки пути и уязвимости типа Path Traversal. Злоумышленник может передать закодированную форму разделителей или последовательности перехода в родительский каталог, которая проявится только после проверки. Безопасный порядок — сначала привести вход к каноническому виду, затем проверить итоговый путь и ограничить его разрешённым каталогом.
Приложения начали принимать имена файлов и другие пути от внешних пользователей задолго до появления современных API. Исходная проблема состояла в том, что файловая система интерпретирует специальные элементы пути, например переход в родительский каталог, тогда как разработчик мог воспринимать вход как обычную строку.
Дополнительную сложность создают разные представления одного значения: URL-кодирование, Unicode-нормализация, различия разделителей и символические ссылки. Поэтому проверка отдельных строковых фрагментов исторически оказалась ненадёжной защитой.
Если приложение сначала ищет запрещённую последовательность, а декодирование выполняет позже, проверяется не то значение, которое в итоге обработает файловая система. Закодированный переход может пройти фильтр, а после декодирования изменить фактический путь.
Последствия зависят от прав процесса и назначения сервиса: чтение конфигурации и секретов, перезапись чужих файлов или доступ к данным за пределами рабочей директории. Даже ошибка только в чтении может раскрыть учётные данные и облегчить дальнейшую атаку.
Сначала вход нужно обработать теми же преобразованиями, которые применяются перед обращением к файловой системе: декодировать допустимые представления, нормализовать разделители и получить канонический путь. Затем приложение должно проверить, что канонический путь находится внутри заранее разрешённого каталога, причём проверка границы должна учитывать полный сегмент пути, а не простое совпадение начала строки.
Надёжнее не принимать произвольные пути вообще: использовать серверный идентификатор ресурса, сопоставлять его с известным объектом или выбирать файл из заранее заданного набора. Если путь неизбежен, процесс приложения должен иметь минимальные права, а операции чтения и записи следует дополнительно ограничивать на уровне файловой системы.
Одной строковой фильтрации недостаточно. Нужно учитывать повторное декодирование, альтернативные разделители, абсолютные пути, Unicode-варианты и символические ссылки. Даже канонизация не устраняет гонку между проверкой и использованием файла: при чувствительных операциях применяют безопасные механизмы открытия файлов с ограничением каталога и минимизируют промежуток между проверкой и использованием.
Сервис загружал документы в рабочий каталог. Проверка отклоняла имя с явной последовательностью перехода в родительский каталог, но декодирование выполнялось отдельным слоем позже. Тестировщик обнаружил, что закодированное представление проходит фильтр и после декодирования указывает за пределы каталога загрузок.
Рассматривались три варианта. Усилить список запрещённых строк было быстро, но такой denylist оставался хрупким при повторном декодировании и новых вариантах представления. Полностью запретить пользовательские имена было надёжнее, но ухудшало совместимость и удобство. Выбранным решением стала генерация серверного имени, хранение исходного имени только как метаданных и проверка фактического объекта внутри разрешённого каталога.
После изменения путь перестал зависеть от пользовательского ввода, а права процесса ограничили каталогом документов. Регрессионные тесты добавили для кодированных разделителей, абсолютных путей, Unicode-вариантов и символических ссылок; это снизило риск повторного появления обхода при изменении слоя декодирования.
Почему проверка после одного декодирования всё ещё может быть недостаточной?
Разные компоненты системы могут декодировать или нормализовать значение независимо друг от друга. Если защитный слой декодирует один раз, а следующий слой делает это повторно, опасная последовательность может появиться уже после завершения проверки. Поэтому нужно определить единую нормализацию на границе приложения и не допускать повторной интерпретации значения без повторной проверки.
Почему проверки, что путь начинается с разрешённого каталога, недостаточно?
Простое сравнение строк может принять каталог с похожим префиксом: разрешённый путь может начинаться так же, как соседний каталог, но не быть его дочерним элементом. Кроме того, символическая ссылка внутри разрешённого каталога способна вести наружу. Проверять нужно канонический фактический путь с корректной границей сегмента и учитывать поведение файловой системы.
Устраняет ли канонизация пути риск полностью?
Нет. Она помогает правильно сравнить путь с разрешённой областью, но не решает проблему избыточных прав процесса, гонки между проверкой и открытием файла, небезопасных символических ссылок и ошибок в другом компоненте. Канонизация должна быть частью защиты в глубину: безопасная модель имён, ограниченные права, контроль каталога, корректная операция открытия и тесты на разные формы входа.