Ч1. Гл. 8. Запас по прочности. Проектирование

Вернёмся к ошибке траверса директории.


Ещё один момент, касающийся траверса директории. Для защиты можно использовать механизмы взаимнооднозначных преобразований. Так, если у сервера запрашивают файл "/../secrets/secret.html", то он может искать файл с именем, который был получен путём перекодирования в шестнадцатиричную форму: "2F2E2E2F736563726574732F7365637265742E68746D6C". Причём все файлы должны быть только в одной директории.
Интересно то, что это тоже своего рода преобразование к форме, не допускающей двухсмысленность.

Однако, есть причины этим не пользоваться:
1. Это не удобно, так как не видно, какие именно файлы за что отвечают. Можно перепутать их имена (нужна специальная программа определения соответствий).
2. Сервера обычно не имеют такой функциональности
3. Если не знаешь, зачем это делать (не знаешь про траверс директории), то нет мотива делать. А если знаешь, то зачем делать?

Можно использовать и преобразования по таблице. То есть файл может быть преобразован по специальной автоматически сгенерированной таблице.
Это интересно следующим.

1. В таком случае без изменения таблицы невозможно получить доступ к файлам, не указанным в этой таблице.
Допустим, сервер позволяет выполнять любые php-файлы, которые запросил пользователь. Допустим, также, что хакеру удалось загрузить вредоносный php-файл для управления сервером. Однако, при табличном преобразовании вызвать этот файл у него не получится, если сервер просто говорит, что не находит этот файл. Для этого, разумеется, сервер должен запрещать все файлы, кроме тех, что указаны в таблице.
Естественно, что файл-таблица соответствия должен быть защищён от записи гораздо лучше, чем директории скриптов.

Такая таблица чем-то похожа на рассмотренный ранее белый список.

2. Автоматические построенные таблицы можно применять одновременно и для оптимизации кеширования. В таком случае файл, который запрашивает пользователь, может иметь некоторое уникальное имя, которое меняется при изменении содержимого исходного файла. А исходный файл носит понятное и постоянное название.

Оба варианта также не пригодны для защиты от несанкционированного траверса директории у архиваторов.

Разумеется, что такие преобразования (имеются в виду шестнадцатиричное представление массива байт) могут быть использованы при подстановке строк в какие-либо сложные форматы файлов, типа XML (лучше не использовать, но иногда надо). Так как в них нет пробелов и других служебных знаков, то они всегда представляют собой допустимую строку и не позволяют провести инъекцию кода.

Но это уже отдельный момент, не являющийся общим методом защиты информации. Хотя он и может быть распространён в узких сферах. Так, перекодирование (правда, одностороннее, с помощью хешей, а не взаимнооднозначное) широко используется и при хранении паролей в базах данных.

-----------------------------------------

Как мы видим, ни один из предъявленных методов не помогает против эксплуатации ошибки траверса директории в архиваторах.
Что же делать?
Один из ответов: правильно и внимательно читать документацию (если она вообще есть). И это единственный универсальный ответ, который может использовать прикладной программист.

Однако, можно ответить немного по-другому. Нужно правильно проектировать операционные системы или другое ПО, являющееся базовым слоем для прикладного ПО.

При проектировании синтаксиса запросов к файловой системе были допущены две ошибки в языке запросов. Первая ошибка состоит в том, что была смешена семантика команд к файловой системе и непосредствено путей. Действительно. Путь "../.." не существует в файловой системе. Это команда драйверу файловой системы.
Таким образом мы получаем смесь непосредственно запросов к файловой системе и команд к драйверу файловой системы.
В идеале, это два разных асбтрактных слоя, которые должны быть один над другим. Один позволяет отдавать команды, второй - только обращаться по канонизированным путям непосредственно к файлам.
Тогда пользователи (прикладные программисты) могли бы использовать, когда это необходимо, более высокоуровневый интерфейс, а в других случаях - менее высокоуровневый.

Однако, вероятнее всего, многие программисты бы предпочли вообще всегда использовать интерфейс с большей функциональностью. Поэтому эта защита не самая лучшая.

Можно было бы предложить также схему, при которой пути, содержащие команды, принимались бы в функциях только при условии установки флагов.
Однако, программисты могли бы использовать эти флаги всегда, даже не задумываясь над ними. Однако, это более интересная вещь, так как программист с большей вероятностью изучит флаги используемой функции, чем более низкоуровневый интерфейс, о котором он, возможно, просто не знает.
Ведь даже если сделать только один интерфейс для доступа к файлам, а второй для выполнения команд, то программисты сами сделают функции, объединяющие часто повторяющиеся два вызова: обработка команды и доступ к файлу. И, таким образом, большинство будут знать только интерфейс какой-то библиотеки, а не системные интерфейсы.


Что ещё можно предложить?
Отказаться от функциональности отдачи команд в путях файлов?
Но эта функциональность довольно удобна. Не хочется от неё отказываться.

Можно реализовать запрет на траверс директории в средствах разграничения доступа к файловой системе. То есть мы просто запрещаем процессу иметь доступ с траверсом через некоторые директории. Однако, если не знаешь об ошибке траверса директории, данное разграничение может показаться излишним, да и, возможно, не будет использоваться пользователями по той же причине.
Кстати, не смотря на все вопли линуксоидов о безопасности их ОС, в FACL Linux нет соответствующих методов разграничения доступа, а в ACL файловой системы NTFS, использующейся в Windows, есть. Впрочем, вряд ли это что-то меняет принципиально.


Далее, можем упростить жизнь программисту. Например, в пути "." является допустимым именем файла. То есть в зависимости от положения, точка имеет разную семантику: просто символ или команда. По-сути, типичная возможность для инъекции команды.

Если мы уберём символ "." из допустимых в файле вообще, то программист мог бы случайно, при проверке пути на корректность, засчитать путь с командой за некорректный (не зная об ошибке траверса и вообще забыв о его возможности).

Кроме этого, мы могли бы сделать в API изменения, которые позволяют обращаться к файлу с использованием командного синтаксиса, но не позволяют, скажем, проверять его наличие с использованием команд. Тогда программист мог бы случайно проверить файл на наличие перед доступом к нему и выдать ошибку, что такого файла нет.
Однако, зачем это делать при проектировании API, если мы ещё не знаем об ошибке траверса директории?

Конечно, лучше всего было бы выделить из строки пути подстроки, отдающие команды, в отдельные строки. То есть потребовать передавать команды отдельными строками, которые будут вставлять в шаблон строки нужные данные. Это было бы совсем не так просто и удобно, но позволило бы обратить внимание программиста на соответствующую функиональность. Но тоже не очень.


Все эти меры либо неудобны, либо рассчитаны на случайность. Подведём итог.
1. Предложенные меры являются не эффективными, а то и неудобными.
2. Зачастую требуют знания от проектировщика системного API о возможной ошибке траверса.

Какие меры можно посчитать самыми эффективными?
1. Возможность разграничения траверса директории. Но только в том случае, если по-умолчанию траверс запрещён, или если мы знаем о возможности несанкционированного траверса и хотим дополнительной защиты от ошибок программиста.
2. Разделение функциональности на два слоя: исполнение команд и исполнение запрошенного доступа к фаловому пути без каких-либо команд.

Кроме этого, в документации по файловой системе надо прописывать как можно кратче, но понятнее соответствующие возможности файловой системы в едином списке, чтобы программисту проще было анализировать возможные проблемы. Отдельно отмечая, что пользователь, по сути, может отдавать команды.

-----------------------------------------

Как видим, эффективного решения проблемы найти не удалось.

Давайте, чисто ради интереса, попробуем найти другие гипотетические решения, пусть даже не эффективные.


Прежде всего, давайте посмотрим на хакера. Что он делает при ошибке траверса?

1. Он имеет доступ к системе, в том числе на получение данных из неё.
2. Он имеет возможность задавать пути к файлам.
3. Он имеет возможность отдавать команды в языке запросов (к файловой системе).
4. Он имеет возможность получить результат исполнения команд.

Посмотрим на первый пункт.
Если мы лишим хакера доступа к системе, то он, конечно, не сможет воспользоваться траверсом директории. Однако, эта мера скорее не техническая, а организационная. Мы должны верно настроить СЗИ и пускать только тех лиц, которым действительно нужен доступ в эту систему.

Пункт 2. Он имеет возможность задавать пути к файлам.

Мы можем лишить его возможности задавать пути к файлам.
Вместо путей он мог бы запрашивать файл по номеру, а сервер мог бы не поддерживать вообще доступ к файлам каким-либо другим образом.
Однако, это абсолютно не применимо к архиваторам.

Пункт 3. Он имеет возможность отдавать команды в языке запросов (к файловой системе).

Мы могли бы лишить пользователя возможности использовать команды в языке запросов.
Однако, команды пользователя не отличимы сами по себе от команд, отданных разработчиком сайта.
Пользователь может сам, произвольно, ввести путь, а может пройти по ссылке. Для сервера это абсолютно идентично.

Не идентичным эти пути можно сделать следующим образом. Команды в путях, которые поступают от пользователя, уже должны отсутствовать. А программист, html-верстальщик или система управления контентом сайта (CMS) должна выдавать пользователю только абсолютные пути.

Это может годится для архиваторов. В том смысле, что они спокойно могут проверить, что в путях нет траверса директории или других команд.

Пункт 4. Он имеет возможность получить результат исполнения команд.

Для архиваторов этот пункт также вряд ли применим, исключая случаи, если они будут всё делать на отдельном логическом диске, специально созданном для распаковки (естественно, что такая защита сработает только в Windows, так как там траверс между дисками невозможен). Кроме этого, мы можем проверять, что файл, который мы записиываем, ещё не создан (чтобы не перезаписать системные файлы).

Для web ограничение такой возможности также вряд ли полезно. Так как обычный пользователь должен получить данные.
Исключение составляет случай, если мы используем фильтрацию по контенту, что-то типа DLP-системы (Data Leak Prevention). Например, мы можем смотреть, что отдаём файл, который начинается с "<html>", не содержит команд на языке, который используется для программирования сервера, имеет не слишком большой размер, правильное расширение и т.п.


Обращу внимание, что пока мы думали, мы допустили небольшую ошибку. Как её найти? В другой главе я указывал методы защиты от несанкционированного траверса директории. Но мы не пришли к части эти методов, хотя могли.

Так, пункт 2 правильно было бы сформулировать как "Он имеет возможность задавать произвольные пути к файлам". В третий пункт также надо было бы ввести слово "произвольные".
Это помогло бы нам выйти на то, что можно ограничивать хакера белым списком путей.
Таким образом, не до конца точная формулировка лишила нас возможности выйти на данные пункт.


Давайте теперь посмотрим, что делает прикладное ПО и система, когда обрабатывает запрос хакера.
1. Получает запрос и относит его именно к тому типу запросов, который нужно обработать определённым методом
2. Парсит команды в запросе
3. Передаёт запрос ОС
4. Вероятнее всего, преобразует запрос в каноническую форму
5. Осуществляет доступ к файловой системе
6. Выдаёт результат
7. Выдаёт результат пользователю


Пункт 1-ый сводится к тому же, что и первый пункт хакера: разграничение доступа к файловой системе.
Пункт 2. Парсит команды в запросе

Здесь вряд ли можно что-то сделать. Ведь хакер имеет возможность подать произвольный запрос. Если только на этапе парсинга выдавать определённые ограничения на парсинг строк.

Пункт 3. Передаёт запрос ОС
Если мы не будем передавать запрос ОС, то система не будет работать. Ничего хорошего не сделать.

Пункт 4. Вероятнее всего, преобразует запрос в каноническую форму

А может и не преобразует. Сложно сказать, что тут можно сделать.

Пункт 5. Осуществляет доступ к файловой системе

Можно разграничить доступ к файловой системе

Пункты 6. Выдаёт результат и 7. Выдаёт результат пользователю

Вряд ли мы обойдёмся без выдачи результатов пользователю. Однако, теоретически, ОС может защитить свои файлы дополнительными средствами. Грубо говоря, запретить выдавать их содержимое или перезаписывать их обычным процессам.


Мы пришли к новым возможностям, однако вряд ли они очень интересны. Получается, что проектированием, в данном случае, запас по прочности эффективно обеспечить не получится.


Таким образом, основной защитой остаётся тщательное написание документации по API и её тщательное изучение прикладным программистом.
При этом особое внимание надо обратить на
1. указание командных функций языка запросов (к файловой системе)
2. указание неоднознаностей в синтаксисе. То есть разных команд, которые приводят к одному и тому же результату (tmp и ./././tmp - одно и то же)
3. указание неоднозначностей в контексте. То есть одинаковых команд, которые приводят к различным результатам в зависимости от контекста (../tmp приводит к разным результатам в зависимости от строения файловой системы)
4. указание побочных эффектов. То есть указание на то, что именно может изменить команда.
5. указание на неограниченные возможности. Например, запрос к файлам операционной системы.


Рецензии