|
04.05.2018 «Безопасность компьютерных систем». Лекция 1: «Вступление: модели угроз» (MTI)Часть 1.Массачусетский Технологический институт. Курс лекций #6.858. «Безопасность компьютерных систем». Николай Зельдович, Джеймс Микенс. 2014 год Computer Systems Security — это курс о разработке и внедрении защищенных компьютерных систем. Лекции охватывают модели угроз, атаки, которые ставят под угрозу безопасность, и методы обеспечения безопасности на основе последних научных работ. Темы включают в себя безопасность операционной системы (ОС), возможности, управление потоками информации, языковую безопасность, сетевые протоколы, аппаратную защиту и безопасность в веб-приложениях. Лекция 1: «Вступление: модели угроз» Николай Зельдович: в этом классе на наших лекциях будет присутствовать содокладчик, приглашённый профессор Джеймс Микенс из Microsoft Research. Позже он расскажет о таких темах, как Интернет-безопасность. В этом году у нас есть 4 помощника-преподавателя, это Стивен, Уэбб, Хоган и Джеймс. Если вам понадобиться помощь, вы можете обратиться к ним в рабочие часы в течение года. Тема этих лекций – понять, как создавать безопасные системы, почему компьютерные системы иногда бывают небезопасными и как можно исправить положение, если что-то пошло не так. Не существует никакого учебника на эту тему, поэтому вы должны пользоваться записями этих лекций, которые также выложены на нашем сайте, и вы, ребята, должны их заблаговременно читать. Имеется также ряд вопросов, на которые вы должны будете ответить в письменной форме, а также вы можете прислать свои собственные вопросы до 10-00 часов вечера перед лекционным днём. И когда вы придёте на лекцию, мы обсудим ваши ответы и вопросы и выясним, что собой представляет эта система, какие проблемы решает, когда это работает и когда это не работает, и хороши ли эти способы в других случаях. Надеюсь, что благодаря такому виду обучения мы получим некоторое понимание того, как мы на самом деле строим системы, которые являются безопасными. На сайте у нас есть предварительное расписание лекций, оно достаточно гибкое. Если есть другие темы, представляющие для вас интерес, или есть какие-то бумажные заметки, можете присылать их нам на электронную почту, и мы постараемся учесть ваши пожелания. Так что если вы хотите услышать о чём-то больше, чем предусмотрено, просто дайте нам знать. Точно так же, если у вас когда-нибудь возникнет вопрос или вы заметите какую-то ошибку, просто прерывайте и спрашивайте нас в любое время лекции. Безопасность во многом состоит из деталей, поэтому на них стоит обращать внимание. Я, конечно же, буду совершать ошибки, поэтому вы должны воспользоваться шансом, чтобы об этом сказать. Просто прервите доклад и спросите, так мы выясним, что идёт не так и как это исправить. Что касается организации лекций, то их большую часть составят лабораторные работы. Первая уже размещена на сайте. Они помогут вам понять различные проблемы компьютерной безопасности и как предотвратить их появление на простом веб-сервере. Поэтому в лабораторной работе №1 вы просто возьмёте веб-сервер, который мы вам предоставим, и попробуете найти способы взломать его, используя уязвимость, связанную с переполнением буфера. Вы завладеете контролем над сайтом, просто отправив на него тщательно проработанные запросы и пакеты. В других лабораторных работах вы будете искать способы защиты веб-сервера, искать «баги» в коде, писать «червей», которые запускаются в браузере пользователя, и изучать другие интересные виды интернет проблем. Многих студентов удивляет, что каждая лабораторная работа (ЛР) использует свой язык программирования. Так, ЛР №1 использует С и Ассемблер, вторая ЛР использует программирование на Python, третья ещё какой-то язык, в пятой появляется JavaScript, и так далее. Это неизбежно, поэтому заранее извиняюсь, что вам придётся изучить все эти языки, если вы до сих пор их не знаете. В некотором смысле это даже полезно, потому что таков реальный мир. Все системы сложны и состоят из различных частей. В конце концов, это будет полезно для вашего морального самоутверждения. Однако это потребует определённой подготовки, особенно если вы не видели эти языки раньше. Так что чем раньше вы начнёте их изучать, тем лучше. В частности, ЛР№1 будет основываться на множестве тонкостей языка C и кода Ассемблер, которые мы не преподаём здесь так подробно, как в других учебных курсах. Мы попросим ассистентов преподавателей выделить пару часов на следующей неделе, чтобы провести нечто вроде учебного занятия на тему того, как выглядит программа из двоичных кодов, как их разбирать, как выяснить, что находится в стеке, и так далее. Ещё одно новшество состоит в том, что мы с этого года делаем видеозаписи наших лекций, которые можно потом просмотреть онлайн. Мы разместим их сражу же, как только получим от видеооператоров. Кроме того, вы можете задавать вопросы онлайн с использованием портала Piazza, как делали это во время изучения других курсов. Прежде чем мы приступим к изучению вопросов компьютерной безопасности, скажу вам одну вещь. Существуют определённые правила, которые наш институт соблюдает для доступа к сети МТИ, поэтому при проведении исследований безопасности нужно учитывать, что не всё, что вы сможете осуществить технически, является законным. В процессе этого курса вы узнаете, как можно взломать или нарушить работу системы, но это не значит, что вы можете проделывать подобное где угодно. В этой лекции размещена ссылка на правила, в которых всё это подробно описано. В общем, если вы сомневаетесь в законности того или иного шага, лучше спросите лектора или помощника преподавателя. В конце концов, это не головоломка. И не стесняйтесь задавать вопросы Итак, что же такое безопасность? Начнем с некоторых основных вещей и рассмотрим некоторые общие примеры того, почему бывает трудно обеспечить безопасность что значит попытаться построить безопасную систему. Эти соображения не описаны на бумаге и не являются высокоинтеллектуальными рассуждениями, но всё же прояснят вам предысторию вопроса и дадут пищу для ума на тему того, как следует представлять себе безопасность систем. В целом безопасность представляет собой пути достижения какой-то цели, возможность противостоять присутствию реального врага. Подумайте о том, что в этом случае всегда существуют «плохие парни», которые хотят убедиться, что у вас ничего не получится. Они хотят украсть ваши файлы. Они хотят удалить содержимое вашего жёсткого диска. Они хотят убедиться, что у вас ничего не работает, ваш телефон не может выйти на связь, и так далее. Так вот, безопасной является такая система, которая действительно может что-то делать независимо от того, что пытается проделать с вами плохой парень. Круто то, что мы можем потенциально создавать системы, устойчивые к вмешательству плохих парней, атакующих, хакеров – называйте их как угодно. И мы всё ещё способны создавать компьютерные системы, способные выполнять свою работу в таких случаях. Для облегчения понимания безопасности разобьём её на 3 части. Одна часть представляет собой принципы, которые должна приводить в исполнение ваша система, то есть её предназначение. Назовём её Policy. Собственно, это и есть та цель, которую вы должны достичь, внедряя систему безопасности. Например, только я должен прочитать вам содержимое этого курса 6.858. Или, может быть, помощники преподавателей, или содокладчики – то есть в этом состоит наше предназначение как «системы». Однако существуют основные требования к тому, что я хочу от своей системы, как я хочу, чтобы она выполняла своё предназначение. Например, одним из принципов, который вы должны были бы написать, связан с конфиденциальностью данных, то есть необходимо задать права доступа, чтобы материалы лекций были доступны только тем, кто имеет право читать курс 6.858. Далее можно было бы подумать о честности, то есть ввести ограничения, что только преподаватели курса 6.858 могут изменять файл оценок, или то, что только они имеют право предоставить на кафедру итоговый файл оценок. Затем вам нужно предусмотреть такую вещь, как доступность. Например, сайт должен быть доступен даже в том случае, если плохие парни пытаются «положить» его и организовать какой-то тип DOS–атаки, «отказа в обслуживании» Denial of Service. Итак, это характеристики системы, которые должны нас заботить. Но поскольку это безопасность, то здесь замешан плохой парень. И нам нужно понять и подумать о том, что он собирается сделать. Именно это мы обычно называем моделью угрозы, Threat model — вторая часть системы безопасности. В основном этот набор предположений о том, что собой представляет плохой парень или противник. Важно иметь какие-то предположения о нём, потому что он вездесущ и может проникнуть повсюду одновременно, и вы будете вынуждены делать то, что он захочет, а в этом случае трудно достичь даже подобия безопасности. Например, вы предполагаете, что хакер не знает вашего пароля, или не имеет физического доступа к вашему телефону, к вашим ключам и к вашему ноутбуку. В противном случае не удастся достигнуть в этой игре какого-то прогресса. Получается, что пока на самом деле сложно придумать, как ещё можно защититься, но я считаю, что лучше переоценить угрозу, чем недооценить её, потому что противник всегда может удивить вас с точки зрения осуществления угрозы на практике. Наконец, для обеспечения безопасности, для достижения нашей цели в рамках сделанных предположений об угрозах, мы должны рассмотреть какой-то механизм. Mechanism – это третья часть системы безопасности. В основном это программное или аппаратное обеспечение или какая-либо часть системного дизайна, реализации и т. д., которая будет пытаться убедиться, что наша система выполняет своё предназначение до тех пор, пока поведение хакера соответствует модели угрозы. Таким образом, конечный результат заключается в том, что пока наша модель угрозы остаётся верной, нашей системе удаётся выполнять своё предназначение. Это понятно? Однако почему это так тяжело осуществить на практике, если наш план выглядит таким простым? Вы осуществили все 3 принципа, система заработала и вам больше нечего делать. Однако на практике вы могли убедиться, что компьютерные системы всегда взламывают тем или иным способом. И взломы довольно обычное дело. Основная причина, по которой обеспечение безопасности становится проблемой, это выбор неправильной цели (об этом уже говорили в курсе 6.033), значит, мы должны убедиться, что наша политика безопасности действует независимо от того, что предпринимает злоумышленник. Давайте пойдём от обратного. Если вы хотите построить файловую систему и убедиться, что мои помощники могут получить доступ к файлу оценок, это довольно легко. Я просто спрашиваю его: «Эй, ребята, сходите и посмотрите, можете ли вы получить доступ к оценкам?», и если им это удаётся, то прекрасно, работа сделана, система работает. Но если мне надо, чтобы никто, кроме моих помощников, не имел доступа к файлу оценок, то осуществление подобного принципа представит собой более серьёзную проблему. Потому что теперь я должен выяснить, что могут проделать все те люди, которые не являются моими ассистентами, для того, чтобы получить файл оценок. Они могут просто попытаться открыть его и прочитать, и возможно, что моя система безопасности должна это запретить. Но они могут попробовать другие виды атак, например, подбор пароля моего ассистента, или кражу его ноутбука, или взлом его комнаты, кто знает? Все эти способы взлома должны учитываться нашей моделью угроз. Например, я не беспокоюсь о файле оценок в случае, если из общежития украдут ваш ноутбук. Хотя, возможно, должен бы был и этим озаботиться, трудно сказать. В результате эти игры с безопасностью не настолько ясно представляются, чтобы изначально составить правильные предположения о возможных угрозах. Иногда бывает так, что только после их осуществления вы задумываетесь, что хорошо бы было предусмотреть такой способ взлома заранее. Поэтому, как следствие, это очень итеративный процесс. И только после того, как вы реализуете каждую итерацию, вы сможете увидеть, где самое слабое звено вашей системы. Может быть, я не правильно составил модель угрозы. Может быть, мой механизм содержал некоторые ошибки, поскольку это программное обеспечение для использования в больших системах, а в них обычно содержится множество «багов». И вы начинаете править ошибки, менять модель угрозы, переделывать всю систему безопасности, и к счастью, делаете её лучшей. Одно из опасных пониманий темы этих занятий состоит в том, что вы просто уйдёте с мыслью о том, что всё кругом взломано, ничего не работает, мы должны просто сложить руки и прекратить пользоваться компьютерами. Это одна из возможных интерпретаций проблемы, но она не совсем правильна. Тема этих лекций заключается в том, что мы рассмотрим все эти различные системы, чтобы продвинуться в их понимании. Мы должны рассмотреть, что произойдёт, если сделать вот так? Оно сломается? А что получится, если попробовать сделать это? К чему оно приведёт? Неизбежно, что каждая система будет иметь свою точку взлома, и мы разберёмся в своих проблемах, когда её обнаружим. Мы поймём, что можем взломать эту систему, если пойдём таким путём, или что система перестанет работать при таком наборе условий. Каждая система неизбежно будет иметь своё уязвимое место, но это совсем не означает, что все компьютерные системы никчёмны и беззащитны. Это просто означает, что вы должны знать, где и какой дизайн системы нужно использовать. И упражнения по поиску уязвимых точек помогут узнать, в каких случаях данные способы срабатывают, а в каких нет. В реальности это имеет нечёткие границы, но чем более безопасной вы делаете свою систему, тем меньше вероятность того, что попадёте в историю на первой странице New York Times, рассказывающую о том, как номера социального страхования миллионов людей попали в открытый доступ. И при этом вы платите меньше денег, чтобы защититься от подобной ситуации. Одна из выдающихся черт безопасности заключается в том, что она способна проделать такие вещи, которые вы не могли предусмотреть заранее, потому что механизмы безопасности, предусматривающие возможность защиты от определённых типов угроз, являются чрезвычайно мощными. Например, браузер раньше был довольно скучной вещью в смысле того, что с ним можно проделать. Вы могли только просматривать в нём веб-страницы или запускать какие-нибудь JavaScript. Но сейчас это довольно крутые механизмы, которые мы изучали несколько недель назад, и которые позволяют вам запускать произвольный код х86 и убеждаться, что он не может сделать ничего «весёлого» с вашим компьютером. Существует интересная технология Native Client от Google, это «песочница», позволяющая безопасно запускать машинный код прямо в браузере, не зависимо от операционной системы. Прежде, чем запустить какую-то игру на вашей машине, вы должны загрузить её и установить, а для этого необходимо отметить в диалоговом окне, что вы разрешаете ей совершить ряд действий. Но сейчас вы просто можете запустить её в браузере, без всяких дополнительных кликов, и она пойдёт. Причина, по которой это так просто и мощно, состоит в том, что система безопасности запускает программу в изолированной «песочнице», не предлагая пользователю ничего решать относительно безопасности или вредоносности данной игры или любой другой программы, запускаемой на компьютере. В большинстве случаев хороший механизм безопасности позволяет конструировать такие крутые новые системы, которые до этого невозможно было создать. Поэтому в остальной части нашей лекции я хочу привести вам ряд примеров, когда система безопасности работает неправильно. Эти примеры научат вас, как не надо поступать, чтобы у вас сформировалось лучшее представление о том, как требуется подходить к решению проблем безопасности. В этом случае взлом системы безопасности показывает, что практически каждая из 3-х составляющих её частей была сделана неправильно. На практике люди ошибаются и в функциях системы, и в создании модели угрозы, и в механизме реализации защитных функций. Давайте начнём с примеров того, как можно испортить системную политику, то есть предназначение системы. Возможно, самый ясный для понимания и простой пример это запрос на восстановление доступа к учётной записи. Как известно, при входе в учётную запись на сайте вы вводите пароль. Но что произойдёт, если вы его потеряете? Некоторые сайты пришлют вам пароль на электронную почту, если вы потеряли свой пароль, со ссылкой на страницу изменения пароля. Так что это достаточно просто, если вы указали резервный почтовый ящик. В этом заключалась система безопасности, предложенная вашим провайдером электронной почты. Ещё несколько лет назад Yahoo создали свою электронную почту в Интернете, где использовали другой механизм восстановления пароля. Если вы забыли свой пароль от ящика Yahoo, они не могли его вам никуда прислать, потому что вариант использования запасного почтового ящика не предусматривался. Вместо этого для восстановления пароля вы должны были ответить на пару вопросов, ответы на которые могли знать только вы. И если вы забыли пароль от своего ящика, то могли нажать на ссылку, ответить на вопросы и получить свой пароль снова. Что произошло в этом случае? Изменение политики безопасности, потому что прежде политика состояла в том, что воспользоваться ящиком электронной почты можно было только в случае, если вы знали пароль. Теперь же воспользоваться входом могли не только люди, знающие пароль, но и те, кто знал ответы на вопросы. И это прямым образом ухудшило безопасность системы, чем и воспользовались некоторые люди. Одним из хорошо известных примеров является случай с Сарой Пэйлин, у которой был ящик на Yahoo. Её секретными вопросами были такие, как: «Где вы посещали школу? Как звали вашего друга? Когда у вас день рождения?» и так далее. Всё это было написано на её странице в Википедии. И в результате этого любой мог зайти в её электронную почту Yahoo, просто прочитав в Википедии, в какую школу она ходила и когда родилась. Так что вам действительно нужно тщательно обдумать последствия различных политик безопасности, прежде чем внедрять их в жизнь. Более сложным и интересным примером является то, что происходит, когда у вас есть несколько систем, которые начинают взаимодействовать друг с другом. Есть хорошая история о парне по имени Мэт Хонан, возможно, вы её прочитали пару лет назад. Он является редактором журнала wired.com. Так вот, кто-то завладел его почтой на Gmail и сделал много плохих вещей. Нас интересует, как ему это удалось? Это довольно интересно, потому что обе стороны этой истории делали вроде бы разумные вещи, однако это привело к печальным результатам. Итак, у нас есть Gmail, который позволяет восстановить забытый пароль, как это делают почти все остальные почтовые сервисы. Для того, чтобы изменить пароль от ящика Gmail, вы отправляете им запрос. Однако они не ответят, если запрос поступил от какого-то незнакомца, они пошлют вам ссылку для восстановления пароля на резервный адрес электронной почты или адрес другой электронной почты, который вы указали при регистрации. Полезно то, что они обычно распечатывают для вас адрес электронной почты. Так что если кто-то зайдёт от имени нашего парня и попросит дать ссылку на переустановку пароля, они ответят: «конечно, без проблем, мы вышлем её на ваш запасной ящик foo@me.com, который является службой электронной почты Apple». Отлично, но у плохого парня нет доступа к ящику на @me.com. Однако он хочет получить эту ссылку, чтобы затем получить доступ к ящику на Gmail. Оказывается, что в случае с почтовым сервисом Apple имеется возможность изменить пароль от ящика @me.com, если указать ваш платежный адрес и последние четыре цифры номера вашей кредитной карты. Нам всё ещё не понятно, как злоумышленнику удалось решить эту задачу. Допустим, он мог узнать домашний адрес Мэта, в своё время тот был довольно известным человеком, но как хакеру удалось завладеть номером кредитной карты Хонана? Продолжим нашу историю. Оказывается, у этого парня был аккаунт на Amazon.com, который выступал другой частью этой истории. Amazon действительно заинтересован в том, чтобы вы покупали вещи. Поэтому он имеют достаточно хитроумную систему управления аккаунтом. Но так как прежде всего Amazon заинтересован в вас как в покупателе, он не требует регистрации на сайте, если вы собираетесь купить какую-то вещь и оплатить её с помощью кредитной карты. Так что я могу зайти на Amazon.com и сказать, что я именно этот пользователь и хочу приобрести этот набор зубных щёток. И если я хочу использовать сохранённый в аккаунте номер кредитной карты, я должен войти в этот аккаунт. И если я хочу добавить к этому аккаунту другую карту, «Амазон» предоставляет мне такую возможность. Это выглядит неплохо, не так ли? Я могу оплатить набор щеток, используя один из двух аккаунтов, но это всё равно номера не вашей кредитной карты, а моей. Так что нам всё ещё не понятно, что в этом деле происходит не так. Но у Amazon есть и другой интерфейс, ведь это сложные системы, поэтому у него имеется интерфейс для сброса пароля. Для того, чтобы сбросить пароль в Amazon, вам необходимо предоставить номер любой одной из ваших кредитных карт. Что же у меня получается? Я могу заказать вещи и добавить номер кредитной карты на свой аккаунт, который на самом деле принадлежит не мне, а затем сказать: «Эй, ребята, я хочу изменить свой пароль, вот вам номер моей кредитной карты!». И это сработало – именно так хакер получил доступ к аккаунту своей жертвы на Amazon.com. Отлично, но как ему удалось раздобыть номер кредитной карты для смены пароля электронной почты Apple? Amazon в этом смысле очень осторожен. Даже если вам удастся войти в чей-то аккаунт, вы не увидите полного номера кредитной карты. Но вам покажут его последние 4 цифры, просто чтобы вы знали, какой картой пользуетесь в данный момент. Таким образом, вы можете переписать последние 4 цифры номера всех кредитных карт данного аккаунта кроме той, которую сами добавили – её номер вы и так знаете. После этого вы сможете войти в ящик жертвы на @me.com, получить там ссылку на сброс пароля и завладеть ящиком Gmail. Это достаточно деликатные вещи. Если система изолирована, она делает разумные вещи. Намного трудней рассуждать об уязвимости и слабых сторонах, когда от вас скрыта вся мозаика, и вы вынуждены собирать её вместе по кусочкам. Это довольно сложная штука. Поэтому к созданию каждой из 3-х частей, составляющих систему безопасности, нужно подходить очень внимательно и вдумчиво. Я думаю, что генеральный план заключается в том, чтобы быть осторожным при выборе своей политики, чтобы не зависеть от влияния других сайтов. Я не уверен, что есть какой-то действительно хороший совет, как поступать в таких случаях. Но теперь вы об этом знаете и можете совершать другие ошибки. Есть множество других примеров неправильно выбранных политик, приводящих к уязвимости системы. Давайте рассмотрим, как люди создают модели угроз, и что собой представляют примеры, когда модель угроз приводит к неприятностям. В большинстве своём это связано с человеческим фактором. Мы часто предполагаем, что люди будут поступать по нашим правилам, придумают надёжный пароль, не станут переходить по ссылкам на чужие сайты и вводить пароль там, и так далее. Однако на практике получается не так. Люди будут придумывать плохие пароли, переходить по ссылкам и не будут обращать внимания на ваши предупреждения. То есть, вероятно, вы не захотите иметь модели угроз, основанные на предположениях, что люди неизбежно будут делать так, чтобы всё пошло неправильно. Ещё одной чертой моделей угроз является то, что они меняются со временем. Примером может служить проект «Афина» созданный в нашем институте в середине 80-х годов, в результате которого была разработана система «Цербер». Вы узнаете о нём на лекциях через пару недель. Разработчики выяснили, что «Цербер» должен основываться на криптографии. Поэтому требовалось создать такие шифровальные ключи, чтобы люди не могли их разгадать. В то время 56 битные ключи казались вполне подходящего размера для шифра DES (Data Encryption Standard). Для середины 80-х это было нормально. Вы знаете, что позже эта система стала весьма популярной и MIT использует её до сих пор. И её создатели никогда не возвращались к пересмотру данного предположения. А затем, несколько лет назад группа студентов курса 6.858 выяснила, что «Цербер» достаточно просто взломать. На сегодня очень легко с помощью компьютера подобрать все ключи простым перебором значения 256. В результате эти ребята смогли с помощью аппаратного обеспечения определённого веб-сервиса (у вас будут ссылки на него в лекционном материале) смогли получить ключ от аккаунта «Цербера» за один день. Так что модель, которая была хороша в середине 80-х, на сегодня является не настолько хорошей. Поэтому при разработке модели угроз вы должны быть уверены, что идёте в ногу со временем. Возможно, более современным примером станет пример, когда вашим противником могут стать правительственные организации, использующие специально разработанное «железо», которому вы не должны доверять. В особенности это касается АНБ – вы наверняка знакомы с «откровениями» на тему того, на что способны эти парни. У них имеются специальные «жучки», которые они могут вставить в ваш компьютер. Ещё каких-то пару лет назад мы об этом не знали, поэтому разумно звучало предположение о том, что ваш ноутбук не может быть уязвим физически, так как невозможно скомпрометировать само «железо». Часть 2Если вы думаете, что правительство преследует вас, проблемы безопасности будут гораздо серьёзнее, потому что ваш компьютер может содержать в себе вредоносное физическое устройство независимо от того, какие программы на нём установлены. Поэтому вам следует тщательно подходить к созданию модели угрозы, сбалансировав её наилучшим образом против конкретного противника. Я думаю, что противостояние АНБ обойдётся вам слишком дорого, но если вы пытаетесь защитить свой домашний каталог «Афина» от других студентов, можно особо не беспокоиться. Так что создание оптимальной модели угроз выглядит как балансировка между различными требованиями безопасности. Другой пример плохой модели угрозы представляет собой способ обеспечение Интернет-безопасности проверкой сертификатов тех сайтов, на которые вы заходите. В этих SSL/TLS протоколах, когда вы подсоединяетесь к сайту, в адресной строке показывается значение HTTPS, то есть «защищённое соединение». Имеется в виду, что данный сайт получил сертификат безопасности, подписанный одним из центров сертификации, и который подтверждает, что да, этот ключ принадлежит действительно Amazon.com. С точки зрения архитектуры, ошибка модели угрозы заключается в том, что она предполагает, что все эти центры авторизации СА заслуживают доверия, и они никогда не совершат ошибку. Но на самом деле, существуют сотни СА: у Индийского почтового управления есть свой центр авторизации, и у китайского правительства он есть, и так далее. И любой из этих центров может сделать сертификат безопасности для любого хоста или домена. В результате, если вы плохой парень и хотите дискредитировать Gmail или взломать их сайт, вам нужно просто скомпрометировать один из этих центров авторизации. И для этого всегда можно найти СА в не слишком развитой стране. Таким образом, создавать модель угрозы на основе предположения, что все сертифицированные сайты безопасны, неправильно. Потому что неправильно предположение о том, что безопасны все 300 центров авторизации, разбросанные по всему земному шару. Однако, не смотря на это, современный протокол SSL до сих пор использует такое предположение в основе своего механизма безопасности для обозревателей Интернета. Существует множество примеров такого рода, когда угроза приходит оттуда, откуда её не ждали. Забавным примером из 80-х годов может служить проект, осуществляемый DARPA — Управлением перспективных исследовательских проектов Министерства обороны США. Тогда им очень хотелось создать неуязвимые операционные системы, и они привлекли к этому кучу университетов и исследователей, которые должны были разработать прототипы безопасных ОС. После этого они создали команду «плохих парней», которые должны были любым способом взломать эти безопасные системы. В результате оказалось, что сервер, на котором хранились исходные коды всех ОС, находился на компьютере в совершенно незащищённом офисе. «Плохие ребята» без труда взломали этот сервер, изменили исходный код и создали на его основе лазейку в ОС. Затем, когда исследователи построили свои операционные системы, эти фальшивые хакеры воспользовались лазейкой и взломали их без особого труда. Поэтому вам действительно нужно подумать обо всех возможных предположениях, касающихся того, откуда приходит ваше программное обеспечение, и того, как плохой парень может в него проникнуть, чтобы убедиться, что ваша система действительно безопасна. В конспектах лекций есть много примеров на эту тему. Вероятно, самая распространённая проблема встречается в механизмах безопасности. Отчасти это потому, что механизм осуществления безопасности – самая сложная её часть. Это совокупность программного и аппаратного обеспечения и других компонентов системы, которые осуществляют вашу политику безопасности. И причины, по которым этот механизм может сломаться, неисчислимы. Так что большая часть наших лекций будет посвящена именно механизмам безопасности, тому, как создавать механизмы, обеспечивающие правильное применение политик безопасности. Также мы поговорим о политике безопасности и моделях угроз. Выясняется, что намного проще создавать ясные и чёткие принципы разработки механизмов, понимать, как они работают или не работают, чем заниматься политиками и моделями угроз, которые вам необходимо «вписать» в содержимое конкретных систем. Рассмотрим некоторые примеры ошибок при создании механизмов безопасности. О последнем случае вы могли услышать пару дней назад – он касается проблем с механизмом безопасности облачного сервиса iCloud от Apple. Любой, у кого есть iPhone, может пользоваться сервисом iCloud. Этот сервис представляет собой облачное хранилище файлов, и если вы потеряете свой «айфон», то ваши файлы всё равно сохранятся в этом облаке, он поможет вам найти свой телефон, если вы его потеряли, и содержит в себе ещё множество полезных функций. Я думаю, что этот iCloud «родственник» сервиса me.com, который был создан по такой же схеме несколько лет назад. Проблема, которая была обнаружена в iCloud, заключалась в том, что создатели не использовали одинаковый механизм безопасности для всех интерфейсов. Рассмотрим, как выглядит этот сервис. К сервису iCloud подключены различные службы, такие как «Хранилище файлов», «Фотоархив», «Поиск телефона» и так далее. Все эти службы проверяют, являетесь ли вы правильным пользователем, прошли ли вы правильную аутентификацию. Вероятно, к разработке этого сложного сервиса были привлечены разные исполнители, которые создавали разные интерфейсы безопасности для входящих в него служб. Например, в службе «Find My iPhone» не отслеживалось, сколько раз подряд вы пытались авторизоваться в системе. Эта функция очень важна, так как ранее я уже упоминал, что люди не утруждают себя созданием действительно сильного пароля. На самом деле система, которая производит аутентификацию пользователей с помощью паролей, довольно «хитрая», мы поговорим об этом позже. Но существует одна хорошая стратегия, заключающаяся в том, что из миллиона подобранных паролей найдется один, который точно подойдёт к чему-то аккаунту. Так что если вы сможете создать миллион вариантов пароля для данного аккаунта, вероятно, вы сможете в него зайти, потому что люди не утруждают себя созданием сложных паролей. Поэтому одним из способов защиты от подбора пароля является то, что система не позволяет вам предпринимать неограниченное количество попыток входа в аккаунт несколько раз подряд. Возможно, что после 3-х или 10 неудачных попыток система активирует тайм-аут и предложит вам попытаться ввести пароль снова через 10 минут, а то и через час. Это действительно тормозит хакера, так как он может предпринять за день всего несколько попыток подбора пароля вместо миллиона. И даже если у вас не слишком трудный пароль, злоумышленнику будет трудно его подобрать из-за больших потерь времени. Но в интерфейсе «Find My iPhone» такая функция не предусмотрена. «Плохой парень» может отправить за день миллион пакетов с паролем, взломать эту службу и украсть конфиденциальные данные пользователя iCloud, что и имело место. Это пример того, что у вас была правильная политика безопасности – только правильный пользователь и правильный пароль может дать доступ к файлам. У вас даже была правильная модель угрозы безопасности, что плохие парни будут пытаться угадать пароль, поэтому нужно этому промешать, ограничив количество попыток ввода. Однако разработчик сервиса просто облажался, потому что его механизм безопасности содержал ошибку. Он просто забыл применить правильную политику и правильный механизм в одном интерфейсе. И такое появляется снова и снова в самых разных системах, где просто один раз ошиблись, и это отразилось на безопасности всей системы. Существует много ошибок такого рода, например, когда разработчик забыл проверить контроль доступа в целом. Например, у City Bank есть сайт, который позволяет вам просмотреть информацию вашей кредитной карты. То есть если у вас есть карта этого банка, вы заходите на сайт и он говорит вам: «да, у вас есть эта кредитная карта, вот ваши операции» и так далее. Несколько лет назад рабочая процедура заключалась в том, что вы заходите на какой-то сайт, вводите свой логин и пароль и вас перенаправляют на сайт, который, скажем, имеет адрес, например: citi.com/account?id=1234. Оказывается, какой-то парень догадался, что если вы просто поменяете эти цифры, то свободно зайдёте в чужой аккаунт. Вот и непонятно, что думать о разработчиках подобной системы? Возможно, эти парни думали правильно, но забыли проверить, как работает их функция безопасности на странице аккаунта, то есть что не только у меня может быть правильный ID номер, но он также может быть ID номером того парня, который только что авторизовался. Это важная проверка, но они про неё забыли. Или, может быть, разработчики думали, что никто не сможет воспользоваться этим URL, кроме вас? Может, у них просто была плохая модель угрозы? Может они подумали, что если они не напечатали этот адрес в виде ссылки, то никто не сможет по нему «кликнуть»? Это пример плохой модели угрозы. Может быть и так, в любом случае трудно сказать, чем они руководствовались, выпустив такой продукт. Такие ошибки часто случаются, причём даже маленький, незаметный промах в механизме безопасности способен привести к печальным последствиям. Ещё один пример, которых не так много в ошибках проверки идентификации, представлен проблемой, выявленной у смартфонов Android несколько месяцев назад. Эта проблема была связана с биткоинами, я уверен, что вы слышали о них – это электронная валюта, довольно популярная в наши дни. Способ, благодаря которому работает система Bitcoin, достаточно продвинутый – это то, что ваш баланс связан с использованием персонального ключа. Поэтому, если у вас есть чей-то персональный ключ, вы можете потратить его биткойны. Безопасность Bitcoin основывается на предположении, что никто не знает вашего ключа. Это как с паролем, только ещё важней, так как люди могут предпринять множество попыток разгадать ваш ключ. При этом нет никакого реального сервера для проверки ключей. Это просто шифрование. Поэтому любой компьютер может попробовать расшифровать ключ, и если это получится, они смогут перевести ваши биткоины кому-то другому. Поэтому чрезвычайно важно генерировать надёжные, сложные ключи, которые никто не сможет разгадать. Есть люди, которые пользуются своими биткоинами со смартфона под управлением Android. Существует мобильное приложение на API Java под названием SecureRandom, которое генерирует случайные значения ключей для Bitcoin. Однако люди выяснили, что в действительности это совсем не случайные числа. Это приложение содержит генератор псевдослучайных чисел, или PRNG. SecureRandom задаёт ему начальный массив из нескольких сотен случайных битов, которые PRNG может «растянуть» на столько случайных битов, сколько захотите. То есть сначала вы используете исходный массив, некий «посевной материал», а затем генерируете из него любое желаемое количество битов, то есть взращиваете свой урожай ключей. Благодаря различным криптографическим принципам, не буду в них углубляться, это действительно работает. Если вы изначально предоставите этому PRNG несколько сотен действительно случайных битов, будет чрезвычайно сложно угадать, какие псевдослучайные числа он сгенерирует. Но проблема заключается в том, что в этой библиотеке Java была небольшая ошибка. При определённых обстоятельствах она забывала снабдить PRNG исходными значениями, и массив состоял из одних нулей. Это означало, что любой мог узнать, какие случайные числа PRNG сгенерировал для ваших ключей. Если они начинаются с нулей, значит, они генерируют одинаковые значения, и взломщик с легкостью получает такой же персональный ключ, как у вас. То есть он просто генерирует ваш ключ и распоряжается вашими биткоинами. Это ещё один пример того, как вроде бы небольшая ошибка механизма безопасности может привести к катастрофическим результатам. Многие люди из-за этого потеряли свои биткоины. Конечно, такие ошибки на каком-то уровне исправляются, можно изменить имплементацию Java в приложении SecureRandom так, чтобы оно всегда заполняло PRNG случайным массивом исходных битов. Но в любом случае, это служит ещё одним примером ошибочной работы механизма безопасности. Вопрос из аудитории: — Объясните, отличается ли это от атаки на алгоритм создания цифровой подписи DSA? Ответ: — В действительности проблема намного сложнее, чем то, на что вы намекаете. Проблема в том, что даже если вы не сгенерировали свой ключ сначала на Android устройстве, конкретная схема подписи Bitcoin предполагает, что каждый раз, когда вы генерируете новую подпись с помощью этого ключа, вы используете одноразовый свежий, или как его ещё называют, «nonce» исходник для такой генерации. И если вы когда-нибудь сгенерируете две подписи на основе одного одноразового исходника, кто-то сможет повторить ваш ключ. Эти случаи похожи, но различаются деталями. Если вы сможете сгенерировать ключ где-то ещё, кроме Android, и это будет действительно надёжный ключ, то каждый раз, когда вы попытаетесь сгенерировать 2 подписи из одного и того же nonce или случайного значения, кто-то сможет путём сложных математических вычислений просчитать ваши подписи и извлечь из них ваш открытый ключ. Или, что ещё важнее, персональный ключ. Я хочу ещё раз отметить, что любая деталь в компьютерной безопасности имеет важное значение. Если вы совершите кажущуюся несущественной ошибку, например, забудете что-то проверить, или забудете инициализировать случайный массив исходных данных, это может иметь серьёзные последствия для всей системы. У вас должно быть чёткое представление того, каковы особенности, характеристики вашей системы, что она должна делать и какие неожиданности скрываются за углом. В этом смысле хорошо подумать о том, как можно взломать вашу систему, прощупать её со всех сторон, например, что произойдёт, если я предоставлю слишком свободный доступ, или какой самый «несвободный» из всех свободных доступов я могу оставить? Какого рода данные я должен поместить в свою систему, чтобы заткнуть всевозможные бреши? Одним из хороших примеров такой двусмысленности являются SSL сертификаты, которые зашифровывают имена в сам сертификат. Это проблема отличается от проблемы, связанной с доверием к СА. SSL-сертификаты – это просто последовательность байтов, которые вам посылает веб-сервер. Внутри этого сертификата находится имя ресурса, к которому вы подсоединяетесь, например, Amazon.com. Вы знаете, что вы не можете просто записать эти байты. Вам надо как-то зашифровать их и указать, что это Amazon.com, и разместить их в конце строки. Так вот, в SSL сертификатах используется определённая схема шифрования, которая записывает Amazon.com, сначала записав количество байтов в строке. Отлично, сначала их нужно записать. Пусть у меня будет 10 байтовая строка под названием Amazon.com, оно действительно состоит из 10 байтов. Замечательно. У нас имеется 10 байт, которые представляют собой буквы названия и точку, а за и перед этими 10 байтами в строке могут располагаться ещё какие-то знаки. Затем браузер, который написан на языке С, берёт эти байты в обработку. Этот язык представляет строки с 0, означающим конец строки. Таким образом, в С нет счётчика длины строки. Вместо этого он учитывает все байты, и конец строки для него – это просто нулевой байт. В конце строки С пишет его обратным слэшем – «\», то есть это выглядит как amazon.com\0. Всё это находится в памяти вашего браузера. Где-то в его памяти теперь имеется строка из 11 байт с нулём в конце. И когда браузер интерпретирует эту строку, он продолжает по ней идти, пока не увидит в конце строки нулевой маркер. Вопрос из аудитории: — У нас имеется 0 в середине строки? Ответ: — Да, это так. И в этом смысле существует некий разрыв в понимании того, как браузер интерпретирует строки. Предположим, что я владею доменом foo.com. Я могу получить сертификат на «что-угодно- foo.com». Таким образом, я могу попросить сертификат и на имя amazon.com\0x.foo.com. С точки зрения браузера это совершенно правильная строка. Это 20 байтовое имя, состоящее из 20 байтов. Раньше было так, что вы могли обратиться в центр авторизации СА и сказать: «Эй, я владею foo.com, дайте мне сертификат на эту штуку!». И они были бы совершенно готовы сделать это, потому что amazon.com0x — это поддомен foo.com, и он полностью твой. Но потом, когда браузер берет эту строку и загружает ее в память, он делает, то же самое, что я описал раньше — он копирует строку. amazon.com0x.foo.com и послушно добавит в завершение этой строки ещё один ноль — amazon.com\0x.foo.com\0. Но затем, когда остальное программное обеспечение браузера начнёт интерпретировать строку в этом месте памяти, оно начнёт её читать и, увидев 0 в середине строки, скажет: «отлично, это же конец строки!» и обнулит всё, что следует в адресе после него. Так что в результате мы получим сайт Amazon.com. Это служит примером того, как несогласованность между интерпретационной способностью языка С и способом шифрования SSL сертификатов вызвало проблему безопасности. Это было обнаружено несколько лет назад парнем по имени Мокси Марлинспайк (Moxie Marlinspike), и это довольно разумное замечание. Такие виды ошибок шифрования весьма распространены в программном обеспечении, за исключением случаев, когда вы очень тщательно подойдёте к кодировке, используя различные способы шифрования. И всякий раз, когда возникает подобная несогласованность, ею может воспользоваться «плохой парень». Одна система считает, что это удачное имя, другая считает, что это не так. И это является хорошим способом, чтобы подтолкнуть систему к неполадкам в работе. Последний пример отказа механизма безопасности, о котором я расскажу сегодня, это переполнение буфера. Некоторые из вас знакомы с этой проблемой по материалам курса 6.033. Остальным напомню, что представляет собой переполнение буфера. Кстати, это тема первой лабораторной работы, так что отнеситесь к этому серьёзно, потому что вашим заданием будет воспользоваться такой уязвимостью и испробовать этот вид атаки на реальном веб-сервере. Итак, наша система представляет собой компьютер, на котором развёрнут веб-сервер. Веб-сервер – это программа, которая будет воспринимать подключения из внешнего мира, принимать запросы, которые в основном представляют собой пакеты данных, обрабатывать их, и, вероятно, совершать какие-то проверочные действия. Если это неправильный URL или если кто-то пытается получить доступ к файлам, и он не авторизован для такого доступа, веб-сервер в качестве ответного действия выдаст сообщение об ошибке. В противном случае он предоставит доступ к файлам, возможно, расположенным на жёстком диске, и отправит их пользователю в виде какой-то формы ответа. Это наиболее распространённая схема серверов, которую вы наверняка знаете. В чём заключается её политика и каковы модели угроз? На самом деле довольно трудно определить политику и модели угроз, используемых во многих реальных системах. Это, в свою очередь, приводит к проблемам безопасности. В глобальном смысле политика работы веб-сервера заключается в выполнении того, что задумал программист. Это немного расплывчато, но в реальности веб-сервер должен делать именно то, что делает код, и если в этом коде имеется ошибка, её нужно отыскать. Итак, в связи с тем, что нам трудно чётко сформулировать политику веб-сервера, будем считать, что он должен делать именно то, что хотел от него программист. Модель угрозы состоит в том, что злоумышленник не имеет доступа к компьютеру, не может воспользоваться им ни удалённо, ни физически, но может отправить на него любой пакет данных, какой захочет. То есть работа сервера не ограничена определёнными видами пакетов. Честная игра заключается в том, что вы можете сформировать и отправить на сервер любой пакет. Имейте ввиду, что на практике это весьма разумная модель угроз. И я предполагаю, что цель состоит в том, чтобы этот веб-сервер не позволял, чтобы какие-либо условные процессы происходили неправильно. Я думаю, это согласуется с тем, что хотел от него программист. Вероятно, что программист не предусмотрел вариант, чтобы какой-то запрос давал доступ к чему-либо на этом сервере. Но вы знаете, что при написании программного обеспечения, которое является основным механизмом работы для веб-сервера, возможны ошибки. Программное обеспечение сервера – это вещь, которая принимает запросы, рассматривает их, убеждается, что они не собираются совершить ничего плохого, и если всё в порядке, посылает обратно ответ. В этом заключается механизм веб-сервера, который обеспечивает вашу политику. В результате, если программное обеспечение сервера даёт сбой, вы оказываетесь в беде. Как вы знаете, одна из самых распространённых ошибок при написании ПО на языке С (а многие программы написаны на этом языке, и, вероятно будут ещё на нём писаться), заключается в том, что вы можете неправильно управлять распределением памяти. Как мы видели в примере с SSL-сертификатами, даже один байт может вызвать значительные изменения в условиях происходящего. Для примера мы рассмотрим небольшой упрощённый отрывок программного кода. На этом слайде изображён такой отрывок, представляющий собой чтение запроса. Здесь вы видите, как выглядит запрос, поступающий на сервер из сети. Но в нашей лекции воображаемый сервер будет просто читать то, что я набираю на клавиатуре своего ноутбука. И он собирается хранить это в буфере, а затем он будет разбирать это целочисленное значение и возвращать это целочисленное значение. Реальный сервер работает далеко не так, но мы используем данный пример для демонстрации того, как происходит переполнение буфера и что при этом бывает не так. Давайте посмотрим, что произойдёт, если я запущу эту программу. Я могу её здесь скомпилировать. Вы видите, появилось сообщение: «Функция опасна и не может быть использована». И на самом деле, она показывает мне, что я где-то облажался. Через секунду мы увидим, почему компилятор нацелен на то, чтобы сказать мне подобное, и это на самом деле так. Но пока что предположим, что нас всё устраивает, и я разработчик, готовый проигнорировать данное предупреждение. Я запускаю функцию перенаправления, предоставляю некие входные данные, и это работает. Я ввожу 1234 и получаю х = 1234, я ввожу действительно большой набор из 28 случайных чисел и получаю ответ (х = 2147483647) из 10 чисел. Как видите, это всё ещё не катастрофа. Теперь я набираю 19 символов А и получаю х = 0. Система устроена так, что при вводе буквенных символов она выдаёт в ответ нулевое значение. Это не так уж и плохо. Но что произойдёт, если я введу огромное количество данных, типа этой серии букв А, растянувшейся на 3 строки? Система аварийно завершит работу! Мы не очень-то этому удивлены. То есть если вы посылаете на сервер «плохой», некорректный запрос, он ничего не может послать вам в ответ. Но давайте попробуем заглянуть вовнутрь и увидеть, что происходит, и попытаемся выяснить, как можно получить пользу от этой аварийной ситуации, или, возможно, сделать что-то более интересное, то, в чём может быть заинтересован хакер. Часть 3Запустим эту программу с помощью отладчика. Вы познакомитесь с этим подробно в первой лабораторной работе. А сейчас мы постараемся установить точку прерывания в этой функции перенаправления, запустим программу и посмотрим, что у нас получилось. Итак, я запустил программу, она начала исполнять основную функцию, и перенаправление происходит довольно быстро. Теперь отладчик остановлен в начале перенаправления. Мы можем увидеть, что здесь происходит, например, мы можем попросить показать нам текущие регистры CPU. Здесь мы будем рассматривать низший уровень, а не уровень исходного кода С. Мы собираемся посмотреть на настоящие инструкции, выполняемые моей машиной, чтобы увидеть, что происходит на самом деле. Язык С может действительно что-то от нас скрывать, поэтому мы попросим показать нам все регистры. В 32-х битных системах (х86), как вы помните, имеется указатель стека – регистр EBP (stack-frame Base Pointer, указатель на стековый фрейм). И моя программа, что неудивительно, тоже имеет стек. На х86 стек растёт вниз, это такой стек, как показано на слайде, и мы можем продолжать «запихивать» в него наши данные. В настоящий момент указатель стека показывает на конкретное расположение памяти ffffd010 (регистр ESP, адрес вершины стека). Здесь имеется некоторое значение. Как оно туда попало? Один из способов понять это – разобрать код функции перенаправления. Переменная Convenience должна иметь целочисленное значение. Итак, мы можем разобрать функцию по именам. Здесь видно, что делает эта функция. В первую очередь, она начинает производить какие-то действия с регистром EBP, это не очень интересно. Но затем она вычитает определённое значение из указателя стека. Это, по существу, создаёт пространство для всех переменных параметров, таких, как буфер и целое число, мы видели это в исходном коде С. Сейчас мы хотим понять работу данной функции. Значение указателя стека, которое мы видели раньше, теперь уже находится посередине стека, а над ним размещены сведения, что делается в буфере, каково целое значение и также находится обратный адрес в основную функцию, которая реализовывается в стеке. Так что где-то здесь у нас должен находиться обратный адрес. Сейчас мы просто стараемся выяснить, где находятся в стеке разные вещи. Мы можем дать команду напечатать адрес этой переменной буфера. Её адрес ffffd02c. Теперь выведем на экран адрес целочисленного значения i – он выглядит так: ffffd0ac. Таким образом, integer расположен выше стека, а буфер ниже. То есть мы видим, что наш буфер расположен в стеке вот на этом месте, сверху находится integer, и возможно, некоторые другие вещи, а в самом конце находится обратный адрес в основную функцию, который называется «перенаправлением». Мы видим, что стек растёт вниз, потому что выше него находятся вещи с более «высокими» адресами. Внутри нашего буфера элементы будут располагаться так: [0] внизу, а далее вверх по возрастающей до элемента [128], как я нарисовал на доске. Посмотрим, что произойдёт, если мы введём те же данные, что привели к аварийному завершению работы системы. Но перед этим мы должны определить, где именно находится наш обратный адрес, как он соотносится с указателем ebp. В х86 существует удобная вещь, называемая Convention, которая делает так, чтобы указатель EBP, или регистр, указывающий на нечто, происходящее в работающем стеке, помечался как «сохранённый EBP регистр» (saved EBP). Это отдельный регистр, расположенный после всех переменных, но перед обратным адресом, как показано на этом рисунке. Он сохраняется согласно нескольким инструкциям, размещённым сверху. Изучим, что собой представляет saved EBP. В отладчике GDB (GNU Debugger) можно исследовать некоторую переменную Х, например переменную указателя EBP. Вот его положение в стеке – ffffd0b8. Действительно, он расположен выше, чем наша переменная i (регистр edi). Это отлично. И она имеет некоторое другое значение, которое принимает EBP до того, как будет вызвана функция, а выше находится ещё одно местоположение памяти, которое и будет обратным адресом. Если мы напечатаем ebp+4, нам покажет содержимое стека 0x08048E5F. Посмотрим, на что это указывает. Это то, что вам предстоит проделать в лабораторной работе. Так что вы можете взять этот адрес и попробовать его разобрать. Что он из себя представляет и где заканчивается? Таким образом, GDB действительно помогает выяснить, какая функция содержит этот адрес.
Что такое 5f? Это то, на что указывает обратный адрес. Как вы видите, это инструкция следует сразу после вызова перенаправления Итак, где мы сейчас находимся? Чтобы подбить итог, мы можем попытаться дизассемблировать наш указатель инструкции. Вводим «disass $eip». Сейчас мы в самом начале перенаправления. Попробуем запустить функцию get() и и ввести команду «next». А далее печатаем нашу невообразимую величину, которая вызвала остановку программы – ААА…А, чтобы посмотреть, что при этом происходит. Итак, мы выполнили get(), но программа всё ещё работает. Сейчас мы выясним, что в настоящий момент происходит в памяти и почему потом всё станет плохо. Как вы думаете, ребята, что сейчас происходит? Я напечатал последовательность символов А. Что при этом команда get() сделала с памятью? Она разместила эту последовательность в стек памяти, который, если вы помните, содержит внутри себя элементы от [0] до [128]. И эта последовательность А принялась заполнять его снизу вверх, вот как я нарисовал, в направлении стрелки. Но у нас имелся всего один указатель – начало адреса, то есть мы указали, с какого места в буфере нужно начать располагать А. Но get() не известна длина стека, поэтому она просто продолжает заполнять память нашими данными, перераспределяя их вверх по стеку, возможно, минуя обратный адрес и всё, что расположено выше нашего стека. Вот я набираю команду для подсчёта повторов А и получаю значение «180», которое превышает наше значение «128». Это не так уж и хорошо. Мы можем опять проверить, что происходит с нашим указателем EBP, для этого я набираю $ebp. Получаем адрес 41414141. Отлично, дальше я набираю «показать расположение обратного адреса $ebp+4» и получаю тот же самый адрес 41414141. Это совсем не хорошо. Это показывает, что произойдёт, если программа вернётся сюда после перенаправления, то есть перескочит на регистр с адресом 41414141. А там ничего нет! И она остановится. То есть мы получили ошибку сегментации. Так что давайте просто подойдём сюда и посмотрим, что произойдёт. Наберём «next» и запустим программу дальше. Сейчас мы приближаемся к концу функции и можем переступить ещё через 2 инструкции. Снова набираем «nexti». Вы видите, что в конце функции имеется инструкция «leave», которая восстанавливает стек туда, где он был. Она как бы «толкает» указатель стека всё время назад к обратному адресу, используя тот же EBP, вот для чего она в основном нужна. И теперь стек указывает на обратный адрес, который мы собираемся использовать. Фактически, это все наши символы А. И если мы запустим ещё одну инструкцию, процессор перейдёт к этому конкретному адресу 41414141, начнёт там исполнять код и «обрушится», потому что это недопустимый адрес в таблице страниц. Давайте проверим, что там происходит. Ещё раз напечатаем содержимое нашего буфера и убедимся, что он полностью заполнен символами «А» в количестве 128 штук. Если вы помните, всего мы ввели в буфер 180 элементов «А». Итак, что-то ещё происходит после того, как произошло переполнения буфера. Если вы помните, мы выполнили преобразование А в целочисленное i в регистре integer. И если мы имеем только буквенные символы А, без всяких чисел, то в расположение памяти записывается 0, так как букву нельзя представить целым числом. А 0, как известно, на языке С означает конец строки. Таким образом, GDB думает, что у нас есть прекрасная, завершённая строка из 128 символов А. Но это не имеет особого значения, потому что у нас всё ещё имеются все эти А наверху, которые уже повредили стек. Отлично, это был действительно важный урок. Нужно учесть, что есть ещё и другой код, который будет выполняться после того, как вам удалось переполнить буфер и вызвать повреждение памяти. Вы должны убедиться, что этот код не совершает глупостей, например, не пытается конвертировать буквенные символы А в целочисленные величины i. Так, он должен предусматривать, что при обнаружении не числового значения, в нашем случае это А, мы не сможем перескочить к адресу 41414141. Таким образом, в некоторых случая вы должны ограничить вводные данные. Возможно, это не слишком важно в данном случае, но в других ситуациях вам нужно с осторожностью подходить к типу входных данных, то есть указывать, какого рода данные – числовые или буквенные – должна обрабатывать программа.
Сейчас мы посмотрим, что произойдёт дальше, и перепрыгнем ещё раз. Посмотрим на наш регистр. Прямо сейчас EIP, вид указателя инструкций, показывает на последний адрес перенаправления Действительно, программа выполняет наше указание, и если мы попросим GDB напечатать текущий набор регистров, то текущий указатель позиции будет представлять собой странное значение. Попробуем выполнить ещё одну инструкцию и наконец, получаем сбой программы. Это произошло, потому что программа попыталась следовать указателю инструкции, который не соответствует допустимой странице для данного процесса в таблице страниц операционной системы. Это понятно? Отлично, у меня к вам имеется вопрос. Так в чём же всё-таки заключается наша проблема? Аудитория: с этой программой можно делать всё, что хотите! Совершенно верно! Хотя, на самом деле, было довольно глупо вводить такое огромное число этих А. Но если бы вы хорошо знали, куда следует поместить эти величины, вы могли бы поместить туда другие значения и перейти по какому-нибудь другому адресу. Давайте посмотрим, сможем ли мы это сделать. Остановим нашу программу, перезапустим её и снова введём много символов А для переполнения буфера. Но я не собираюсь выяснять, какая А где располагается в стеке. Но предположим, что я переполняю стек в этой точке и потом пробую вручную изменить вещи в стеке так, чтобы функция перепрыгнула в то место, в какое мне надо. Поэтому я ввожу снова NEXTI. Где мы находимся? Мы снова находимся в самом конце перенаправления. Давайте посмотрим на наш стек. Если мы исследуем ESP, то увидим наш повреждённый указатель. Хорошо. Куда мы бы могли отсюда перескочить? Что интересного мы бы могли сделать? К сожалению, эта программа очень ограничена. В её коде нет ничего, что помогло бы нам перескочить и сделать что-то интересное, но мы всё равно попытаемся. Возможно, нам удастся найти функцию PRINTF, перепрыгнуть туда и заставить её напечатать какое-то значение, или эквивалентную чему-либо величину Х. Мы можем дизассемблировать основную функцию – disass main. А главная функция делает целую кучу вещей – инициацию, переадресацию вызовов, ещё много всего, и затем вызывает PRINTF. Так как насчёт того, чтобы перескочить в эту точку – <+26>, которая устанавливает аргумент для PRINTF, равный %eax в регистре <+22>? Таким образом, мы сможем взять значение в регистре <+26> и «приклеить» его к этому стеку. Это должно быть достаточно легко сделать при помощи отладчика, можно сделать этот набор {int} esp равным этому значению. Можно проверить ESP ещё раз, и действительно, он имеет это значение. Продолжим с помощью команды «C», и мы увидим, что функция распечатала Х равным какой-то ерунде, и я думаю, это случилось из-за содержимого этого стека, которое мы попытались вывести на печать. Мы неправильно настроили все аргументы, потому что прыгнули в середину этой вызывающей последовательности (последовательность команд и данных, необходимая для вызова данной процедуры). Да, мы напечатали эту величину, и после этого система дала сбой. Почему это произошло? Мы перепрыгнули к функции PRINTF, а потом что-то пошло не так. Мы изменили обратный адрес, так что когда мы вернулись из перенаправления, мы переходим на этот новый адрес, в ту же самую точку сразу после PRINTF. Так откуда взялся этот сбой? Аудитория: из-за возврата главной функции! Совершенно верно! Вот что происходит – вот та точка, куда мы прыгнули, в регистре <+26>. Она устанавливает некоторые параметры и вызывает PRINTF. PRINTF работает и готова к возврату. Пока всё нормально, потому что эта инструкция вызова «кладёт» обратный адрес в стек для того, что этот адрес использовала функция PRINTF. Главная функция продолжает работать, она готова запустить инструкцию LEAVE, которая не представляет собой ничего интересного, а затем сделать другой «return» в регистре <+39>. Но дело в том, что в этом стеке нет правильного обратного адреса. Поэтому, предположительно, мы возвращаемся к кому-то другому, кто знает расположение памяти выше стека, и прыгаем куда-нибудь ещё. Так что, к сожалению, здесь наши псевдоатаки не работают. Здесь запускается какой-то другой код. Но затем он «крашится». Это, вероятно, не то, что мы хотели сделать. Так что если вы действительно хотите быть осторожными, вы должны не только тщательно разместить в стеке обратный адрес, но и выяснить, от кого второй RET получит свой обратный адрес. Затем вам нужно постараться осторожно поместить в стек что-то ещё, чтобы быть уверенным, что ваша программа «чисто» продолжает выполняться после того, как была взломана, и так, что это вмешательство никто не заметит. Это всё вы попытаетесь проделать в лабораторной работе №1, только более подробно. Есть ещё одна вещь, о которой нам стоит подумать сейчас – об архитектуре стека при переполнении буфера. В данном случае наша проблема заключается в том, что обратный адрес там расположен вверху, правильно? Буфер продолжает расти и, в конечном счёте, перекрывает обратный адрес. Но что, если мы перевёрнём стек «вниз головой»? Вы знаете, некоторые машины имеют стеки, которые растут вверх. Так что мы могли бы представить себе альтернативный дизайн, где стек начинается снизу и продолжает расти вверх, а не вниз. Так что если вы переполните такой буфер, вы просто будете продолжать идти вверх по стеку, и в этом случае не случится ничего плохого. Сейчас я нарисую вам, чтобы объяснить, как это выглядит. Пусть обратный адрес располагается здесь, внизу стека. Выше расположены наши переменные, или saved EBP, затем переменные целочисленные integer, и на самом верху буфер от [0] до [128]. Если мы делаем переполнение, то оно идёт вверх по этой стрелке. Таким образом, переполнение буфера не повлияет на обратный адрес. Что нам нужно сделать в нашей программе, чтобы осуществить такой вариант работы? Правильно, сделать переадресацию! Расположим слева стек-фрейм, который осуществит такую переадресацию, и переадресуем вызов функции наверх. В результате наша схема будет выглядеть так: наверху на стеке расположен обратный адрес, затем saved EBP, и все остальные переменные расположатся сверху над ним. А потом мы начнём переполнять буфер командой get(S). Итак, работа функции всё ещё проблематична. В основном, потому что буфер окружён функциями возврата со всех сторон, и в любом случае вы можете что-то переполнить. Предположим, что наша машина имеет стек, растущий вверх. Тогда в какой момент вы сможете перехватить контроль над выполнением программы? На самом деле, в некоторых случаях это даже проще. Вам не нужно ждать, когда вернётся редирект. Возможно, там даже были такие вещи, как превращение А в i. На самом дело это проще, потому что команда get(S) переполняет буфер. Это изменит обратный адрес, а затем немедленно вернётся обратно и перепрыгнет туда, где вы попытались создать некую конструкцию. Что произойдёт, если у нас такая, довольно скучная программа для всяких экспериментов? Она вроде бы не содержит интересного кода для прыжка. Всё что вы можете сделать – это напечатать здесь, в PRINTF, другую величину Х. Давайте попробуем! Аудитория: если у вас есть дополнительный стек, вы можете поместить произвольный код, который, например, выполняет оболочку программы? Да-да-да, это действительно разумно, потому что тогда можно поддерживать другие величины «input». Но здесь имеется некоторая защита от этого, вы узнаете о ней в следующих лекциях. Но в принципе, вы бы могли иметь здесь обратный адрес, который перекрывается на обеих типах машин – со стеками вверх и со стеками вниз. И вместо того, чтобы указать его в имеющемся коде, например PRINTF в главной функции, мы могли бы иметь обратный адрес в буфере, так как это просто некое местоположение в буфере. Но вы можете «прыгнуть» туда и считать его исполняемым параметром. Как часть вашего запроса, вы посылаете несколько байтов данных на сервер, а затем получаете обратный адрес или вещь, которую вы расположили в этом месте буфера, и вы продолжите выполнение программы с этой точки. Таким образом вы сможете предоставить код, который хотите запустить, прыгнуть туда и использовать сервер для его запуска. И действительно, в системах Unix атакующие часто делают так – они просят операционную систему просто выполнить команду BIN SH, позволяющую вам выбрать тип произвольных команд оболочки, которые затем выполняются. В результате эта вещь, этот кусок кода, который вы вставили в буфер, по ряду исторических причин носит название «код оболочки», shell code. И в вашей лабораторной работе вы попытаетесь сконструировать нечто подобное. Аудитория: существует ли здесь разделение между кодом и данными? Исторически сложилось так, что многие машины не обеспечивали никакого разделения кода и данных, а имели просто плоское адресное пространство памяти: указатель стека указывает туда, указатель кода – сюда, и вы просто выполняете то, на что он указывает. Современные машины пытаются обеспечить некоторую защиту от такого рода атак, поэтому они часто создают разрешения, связанные с разными областями памяти, и одно из разрешений является исполняемым. Таким образом, часть вашего 32-разрядного или 64-разрядного адресного пространства, содержащая код, имеет разрешение на выполнение операций. И если ваш указатель инструкции показывает туда, процессор будет на самом деле управлять этими штуками. Но стек и другие области данных вашего адресного пространства обычно не имеют разрешения на исполнение. Так что если вам случится каким-то образом установить указатель инструкции в некоторое положение, соответствующее области памяти, где нет кода, процессор откажется выполнять эту инструкцию. Так что это довольно хороший способ защититься от некоторых видов атак, но он не предотвращает вообще их возможность. Так как бы вы обошли это препятствие, если бы у вас был неисполняемый стек? На самом деле вы видели этот пример раньше, когда мы просто «прыгнули» в середину главной функции. Таким образом, это был способ использования переполнения буфера без необходимости вводить новый собственный код. Поэтому, даже если бы данный стек был неисполняемым, я бы всё равно смог попасть в середину главной функции. В данном конкретном случае это довольно скучно, потому что достаточно ввести PRINT X, чтобы обрушить систему. Но в других ситуациях у вас могут быть другие части кода в вашей программе, позволяющие делать интересные вещи, которые вы действительно хотите выполнить. Это называется атакой «return to lib c» — атака возврата в библиотеку, связанная с переполнением буфера. При этом адрес возврата функции в стеке подменяется адресом другой функции, а в последующую часть стека записываются параметры для вызываемой функции. Это способ обойти меры безопасности. Таким образом, в контексте переполнения буфера нет действительно четкого решение, которое обеспечивает идеальную защиту от этих ошибок, потому что, в конце концов, программист сделал ошибку в написании этого исходного кода. И лучший способ исправить это, вероятно, просто изменить исходный код и убедится, что вы не ввели слишком много getS(), о чём вас как раз предупреждал компилятор. Но есть более тонкие вещи, о которых компилятор вас не предупреждает, но вы всё равно должны их учесть. Так как на практике трудно изменить все программное обеспечение, многие люди пробуют разработать методы для предупреждения подобных ошибок. Например, делают стек неисполняемым так, что вы не можете поместить в него код оболочки и должны сделать что-то более сложное, чтобы достичь своей цели. В следующих 2-х лекциях мы рассмотрим эти методы защиты. Они не идеальны, но на практике существенно затрудняют жизнь хакеру. Аудитория: будет ли тест на тему сегодняшней лекции и когда? Да, если вы посмотрите в расписание, то увидите там 2 теста. Итак, подведём итоги. Что нам делать с проблемами механизма переполнения буфера? Общий ответ должен звучать так – нужно иметь наименьшее количество механизмов. Как мы убедились, если вы полагаете применить политики безопасности в каждой части программного обеспечения, вы неизбежно будете совершать ошибки. И они позволят противнику обойти ваш механизм, чтобы использовать некоторые недочёты в веб-сервере. Во второй лабораторной работе вы попытаетесь сконструировать более совершенную систему, безопасность которой не будет зависеть от программного обеспечения, и которая будет обеспечивать соблюдение политики безопасности. Сама политика безопасности будет реализовываться небольшим количеством компонентов. И остальная часть системы, правильная она или нет, не имеет значения для безопасности, если это не будет нарушать саму политику безопасности. Так что своего рода минимизация надёжной вычислительной базы является довольно мощной технологией, позволяющей обойти ошибки механизма и проблемы, которые мы рассмотрели сегодня более-менее детально. На сегодня всё, приходите на лекцию в понедельник и не забывайте размещать свои вопросы на сайте. Продолжение следует… Полная версия курса здесь. Источники:
|
|
|
© Злыгостев А.С., 2001-2019
При использовании материалов сайта активная ссылка обязательна: http://informaticslib.ru/ 'Библиотека по информатике' |