воскресенье, 7 февраля 2010 г.

ISSP \ Домен 03. Архитектура и модель безопасности. Часть 10

В этой части рассмотрены следующие вопросы:
  • Анализ нескольких угроз
  • Закладки для поддержки
  • Атаки времени проверки / времени использования
  • Переполнение буфера
  • Что такое стек и как он работает?
  • Резюме

Обновлено: 03.05.2010

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

Программное обеспечение практически всегда имеет ошибки и уязвимости. Богатая функциональность, требуемая пользователями, ведет к повышению сложности, что в свою очередь открывает дверь проблемам компьютерного мира. Атакующие постоянно ищут способы использования различных функций систем для проведения атак и очень часто находят уязвимости. Всегда будут полицейские и грабители, всегда будут атакующие и специалисты по безопасности. Это игра, в которой один старается перехитрить другого, чтобы победить.
ПРИМЕЧАНИЕ. Университет Карнеги-Мелона установил, что на каждые 1000 строк кода приходится от 5 до 15 ошибок. Windows 2000 имеет 40-60 миллионов строк кода.

В мире программирования закладки для поддержки (maintenance hook) являются разновидностью черного хода (backdoor). Разработчик добавляет в программу команды, позволяющие получить простой доступ к коду, которые только он сам знает как вызвать. Это позволяет разработчику просматривать и редактировать код без прохождения через стандартную процедуру управления доступом. На этапе разработки программного обеспечения такие закладки могут быть очень полезны, но если они не были удалены перед передачей системы в промышленную эксплуатацию, они могут стать причиной серьезных проблем с безопасностью.

Закладки для поддержки обычно инициируются «случайной» последовательностью клавиш и позволяют получить доступ в программное обеспечение без прохождения через обычный процесс управления доступом, проверок и механизмов безопасности.

Приложение, которое имеет закладки для поддержки, позволяет разработчику выполнить определенные команды, используя определенные последовательности клавиш. При этом разработчик может работать внутри приложения, напрямую видеть код или конфигурационные файлы. Он может увидеть проблемные места в коде, проверить значения переменных, экспортировать дополнительный код в программу, исправить выявленные недостатки. Хотя это звучит прекрасно и здорово при использовании разработчиком, атакующий, найдя эти закладки, сможет выполнить крайне опасные действия. Поэтому все закладки должны быть удалены из программного обеспечения до начала его промышленной эксплуатации.
ПРИМЕЧАНИЕ. Многие полагают, что раз в настоящее время люди больше думают о безопасности, то и закладки для поддержки остались в прошлом. Но это не так. Разработчики продолжают использовать закладки для поддержки, потому что они недостаточно хорошо понимают и заботятся о вопросах безопасности. Множество закладок по-прежнему остается в старом программном обеспечении, которое используют компании.
Контрмеры:

Так как закладки для поддержки обычно вставляются программистами, они единственные, кто может удалить их до внедрения программы в промышленную эксплуатацию. Анализ кода и модулей, качественное тестирование должны выявить все черные ходы, пропущенные программистом. Поскольку закладки для поддержки расположены в коде приложения или системы, пользователь практически ничего не может сделать, чтобы предотвратить их присутствие. Когда производитель находит в коде своего продукта черный ход, обычно он выпускает патч, снижающий или устраняющий эту уязвимость. Т.к. большинство производителей продает свое программное обеспечение без исходных текстов, купившим его компаниям очень сложно выявить в нем черные ходы. Ниже представлен список некоторых превентивных мер против черных ходов:
  • Используйте системы IDS уровня хоста, чтобы выявить факт использования черного хода атакующим;
  • Используйте шифрующие файловые системы для защиты критичной информации;
  • Внедрите аудит для выявления фактов использования черных ходов.

Некоторые атаки могут использовать в своих интересах способы обработки системных вызовов и выполнения задач. Атаки «времени проверки» / «времени использования» (TOC/TOU – time-of-check/time-of-use) связаны с последовательностью шагов, выполняемых системой для завершения задачи. Это тип атак использует в своих целях зависимость от времени событий, происходящих в многозадачной операционной системе.

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

Приведем пример TOC/TOU-атаки. Процесс 1 проверяет авторизацию пользователя для открытия ему некритичного текстового файла, процесс 2 выполняет команду открытия. Если атакующий сможет подменить этот некритичный текстовый файл файлом, содержащим пароли, в то время, пока процесс 1 выполняет свою задачу, он сможет получить доступ к этому файлу. Возможность проведения такой атаки однозначно указывает на недостаток в коде программного обеспечения.
ПРИМЕЧАНИЕ. Этот тип атаки также называют асинхронной атакой. Асинхронным называется такой процесс, в котором время каждого шага может меняться. Атака происходит между этими шагами и изменяет что-то. Иногда TOC/TOU-атаки считают разновидностью атак соревнования (race conditions).
Атака совервнования (race condition) становится возможна, когда два различных процесса должны выполнить свои задачи с использованием одного и того же ресурса. Процессы должны работать в правильной последовательности. Процесс 1 должен выполнить свою работу до того момента, как процесс 2 получит доступ к тому же ресурсу и приступит к выполнению своей работы. Если процесс 2 начнет раньше процесса 1, результат может быть совсем другим. Если атакующий может управлять процессами, так, чтобы заставить процесс 2 начать первым, он может управлять результатом процедуры. Скажем, что командой процесса 1 является прибавление 3 к значению, а командой процесса 2 – деление значения на 15. Если процесс 2 выполнит свою команду первым, результат будет другим. Таким образом, если атакующий может заставить процесс 2 выполниться раньше процесса 1, он может управлять результатом.

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

Хотя термины «атака соревнования» и «TOC/TOU-атака» иногда используют, подразумевая одно и тоже, на самом деле это две разных вещи. В атаке соревнования атакующий заставляет процессы выполниться в неправильной последовательности для получения контроля над результатом. В TOC/TOU-атаке атакующий попадает между двух задач и изменяет что-то для контроля над результатом.

Контрмеры

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

Чтобы избежать TOC/TOU-атаки лучше всего, если операционная система может применять программные блокировки к элементам, которые будут использоваться при выполнении задач «проверки». Так, если пользователь запрашивает доступ к файлу, система на время проведения его авторизации применяет программную блокировку к запрашиваемому файлу. Это гарантирует, что файл не может быть удален или заменен другим файлом. Применение таких блокировок легко реализовать для файлов, но применять их к компонентам баз данных и содержимому таблиц может быть значительно более сложной задачей.


В настоящее время многие знакомы с термином «переполнение буфера» и его определением, но для специалистов по безопасности важно понимать его более глубоко.

Переполнение буфера (buffer overflow) происходит, когда приложением или операционной системой принимается слишком много входящих данных. Буфер – это некий выделенный сегмент памяти. Буфер может быть переполнен слишком большим объемом данных. Чтобы этим мог воспользоваться атакующий и запустить свой код на выполнение, вставляемый в буфер код должен иметь строго определенную длину. Таким образом, целью переполнения буфера может быть нарушение работы приложения путем записи произвольных данных в различные сегменты памяти; либо выполнение определенной задачи, путем введения в определенный сегмент памяти специально подготовленного набора данных, который выполнит эту задачу. Задача может заключаться, например, в открытии командной оболочки с административными полномочиями или выполнении вредоносного кода.

Давайте поглубже рассмотрим как это делается. Программное обеспечение может быть написано для получения данных от пользователя, веб-сайта, базы данных или другого приложения. Принятые данные требуют выполнения какого-либо действия, они принимаются для некого типа манипуляций или расчетов, либо использования в качестве параметра процедуры. Процедура – это часть кода, которая может выполнять определенные действия над данными и возвращать результат вызвавшей ее программе, как это показано на Рисунке 3-21.

Рисунок 3-21. Стек использует отдельный буфер для хранения команд и данных

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

Принятые от внешнего источника данные, размещаются в переменной. Эта переменная расположена в области памяти, которая называется буфером. Буфер похож на контейнер памяти для данных. Буфер должен быть достаточного размера, чтобы вместить все принятые данные. Так, если ожидается ввод одного символа, буфер должен иметь размер в 1 байт. Если программист не убедился, что получен только 1 байт, кто-то может ввести несколько символов и переполнить этот буфер.

Буфер хранит данные, размещающиеся в стеке памяти. Представьте, что буфер – это маленькое ведро с водой (данными). У нас есть несколько таких ведер, расположенных один над другим (стек). Если слишком много воды налить в верхнее ведро, вода перельется в стоящие ниже ведра (переполнение буфера) и перезапишет инструкции и данные в стеке памяти.

Что такое стек и как он работает?

Если вы работаете с приложением, которое считает процентную ставку по ипотеке, вы вводите в качестве параметров для расчета срок кредита и сумму кредита. Эти параметры будут сохранены в пустых переменных и размещены в некой линейной конструкции (стеке памяти), которая действует как очередь операций извлечения данных для выполнения расчета. Первая задача вашего приложения расчета ипотечной ставки – разместить указатель возврата (return pointer). Это указатель на адрес памяти запрашивающего приложения, который говорит процедуре, как вернуть управление запрашивающему приложению после обработки ей всех значений в стеке. Затем приложение переходит к адресу выше указателя возврата, и размещает там оставшиеся данные (введенные пользователем). После этого приложение отправляет запрос процедуре для выполнения необходимых вычислений, как показано на Рисунке 3-21. Процедура берет данные из стека, начиная сверху (первым пришел – последним ушел, FILO – first in, last off). Процедура выполняет эти функции над всеми данными и возвращает результат и управление обратно приложению, используя указатель возврата в стеке.

Таким образом, стек – это просто сегмент памяти, который позволяет обмениваться информацией между запрашивающим приложением и подпрограммой (процедурой). Потенциальные проблемы появляются в случае, если запрашивающее приложение не выполняет надлежащую проверку границ, т.е. не убеждается, что введенные данные имеют приемлемую длину. Посмотрите на следующий код на Си, чтобы понять, как это происходит:
#include
int main(int argc, char **argv)
{
char buf1 [5] = "1111";
char buf2 [7] = "222222";
strcpy (buf2, "3333333333");
printf ("%s\n", buf2);
printf ("%s\n", buf1);
return 0;
}
ПРЕДУПРЕЖДЕНИЕ. Вам не обязательно знать язык Си, чтобы сдать экзамен CISSP. Мы углубились в этот вопрос потому, что переполнение буфера является общей и серьезной уязвимостью уже много лет. Для сдачи экзамена вам достаточно понимать общую концепцию переполнения буфера.
Итак, сначала мы установили длину буфера buf1 равной четырем символам, а буфера buf2 – шести символам. Для обоих буферов установлено начальное значение NULL (значение NULL указывает на конец буфера в памяти). Если мы сейчас посмотрим содержимое этих буферов, мы увидим следующее:
Buf2
\0 2 2 2 2 2 2
Buf1
\0 1 1 1 1
Затем наше приложение записывает десять троек в buf2, который может хранить только шесть символов. При этом шесть символов будет записано в buf2, а еще четыре символа – в buf1, затирая расположенные в нем ранее данные. Это происходит потому, что команда strcpy не проверяет, имеет ли буфер достаточную длину для хранения нужного количества символов. Так, теперь мы увидим следующее содержимое буферов:
Buf2
\0 3 3 3 3 3 3
Buf1
\0 3 3 3 3
Но результат будет еще интереснее, если будет перезаписан указатель возврата, как это показано на Рисунке 3-22. Тщательно подготовленная атака переполнения буфера обеспечивает перезапись указателя возврата на контролируемое значение с целью выполнения загруженных в стек вредоносных команд, вместо возврата управления запросившему приложению. Это позволяет выполнить вредоносные команды в контексте безопасности запросившего приложения. Если приложение работает в привилегированном режиме, атакующий получает более обширный доступ и привилегии для нанесения большего ущерба.

Рисунок 3-22. Атака переполнения буфера

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

Ядро Windows написано на языке Си и имеет несколько слоев объектно-ориентированного кода над собой. Когда процедуре нужно вызвать операционную систему для выполнения некой задачи, она обращается к системной службе посредством API-вызова. API работает как дверь к функциям операционной системы.

Язык Си чувствителен к атакам переполнения буфера, т.к. он позволяет использовать прямые операции с указателями. Отдельные команды могут предоставить доступ к низкоуровневым адресам памяти без проверки границ. Функции Си, которые выполняют необходимую проверку границ, это sprintf(), strcat(), strcpy() и vsprintf().

Операционная система должна быть написана для работы с конкретными архитектурами процессоров. Эти архитектуры диктуют адресацию системной памяти, защитные механизмы, режимы выполнения, работу с определенными наборами команд. Это означает, что атака переполнения буфера, работающая на процессорах Intel, не обязательно будет работать на процессорах Motorola или SPARC. Эти различные процессоры используют различную адресацию памяти стека, поэтому атакующий должен иметь несколько вариантов кода переполнения буфера для различных платформ. Обычно это не является проблемой, т.к. в последнее время такой код уже написан и доступен на хакерских веб-сайтах.


Контрмеры

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




Архитектура компьютерных систем очень важна, она включает в себя множество различных аспектов.

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

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

1 комментарий:

Анонимный комментирует...

Атака соВЕрВнования (race condition)