Массачусетский Технологический институт. Курс лекций #6.858. «Безопасность компьютерных систем». Николай Зельдович, Джеймс Микенс. 2014 год
Computer Systems Security — это курс о разработке и внедрении защищенных компьютерных систем. Лекции охватывают модели угроз, атаки, которые ставят под угрозу безопасность, и методы обеспечения безопасности на основе последних научных работ. Темы включают в себя безопасность операционной системы (ОС), возможности, управление потоками информации, языковую безопасность, сетевые протоколы, аппаратную защиту и безопасность в веб-приложениях.
Лекция 1: «Вступление: модели угроз» Часть 1 / Часть 2 / Часть 3
Лекция 2: «Контроль хакерских атак» Часть 1 / Часть 2 / Часть 3
Лекция 3: «Переполнение буфера: эксплойты и защита» Часть 1 / Часть 2 / Часть 3
Лекция 4: «Разделение привилегий» Часть 1 / Часть 2 / Часть 3
Лекция 5: «Откуда берутся ошибки систем безопасности» Часть 1 / Часть 2
Лекция 6: «Возможности» Часть 1 / Часть 2 / Часть 3
Лекция 7: «Песочница Native Client» Часть 1 / Часть 2 / Часть 3
Лекция 8: «Модель сетевой безопасности» Часть 1 / Часть 2 / Часть 3
Лекция 9: «Безопасность Web-приложений» Часть 1 / Часть 2 / Часть 3
Лекция 10: «Символьное выполнение» Часть 1 / Часть 2 / Часть 3
Лекция 11: «Язык программирования Ur/Web» Часть 1 / Часть 2 / Часть 3
Лекция 12: «Сетевая безопасность» Часть 1 / Часть 2 / Часть 3
Лекция 13: «Сетевые протоколы» Часть 1 / Часть 2 / Часть 3
Лекция 14: «SSL и HTTPS» Часть 1 / Часть 2 / Часть 3
Лекция 15: «Медицинское программное обеспечение» Часть 1 / Часть 2 / Часть 3
Лекция 16: «Атаки через побочный канал» Часть 1 / Часть 2 / Часть 3
Лекция 17: «Аутентификация пользователя» Часть 1 / Часть 2 / Часть 3
Лекция 18: «Частный просмотр интернета» Часть 1 / Часть 2 / Часть 3
Лекция 19: «Анонимные сети» Часть 1 / Часть 2 / Часть 3
Лекция 20: «Безопасность мобильных телефонов» Часть 1 / Часть 2 / Часть 3
Лекция 21: «Отслеживание данных» Часть 1 / Часть 2 / Часть 3
Студент: а почему просто нельзя просканировать код, а не проверять его вручную?
Профессор: на практике так и происходит. Разработчики знают, что всякий раз, когда интерпретатор выполняет подобную работу, то при возврате обратного значения используется специальный код, который автоматически присваивает операции system.arraycopy() зараженное значение, которое должно быть с ним связано.
Студент: правильно, но в чём тогда состоит ручная часть работы?
Профессор: ручная часть в основном состоит в том, чтобы выяснить, какой должна быть политика выполнения проверки. Другими словами, если вы просто посмотрите на стандартный TaintDroid или стандартный Android, они будут что-то для вас делать, но они не смогут автоматически назначать Taint правильным образом. Так что кто-то должен вручную назначить политику отслеживания.
Не похоже, что на практике это будет большой проблемой. Но если бы количество приложений, которые используют машинно-ориентированные методы, постоянно увеличивалось, то у нас могут появиться небольшие проблемы.
Еще один тип данных, о которых стоит побеспокоиться в смысле присвоения заражения, это сообщения IPC. Сообщения IPC по существу рассматриваются как массивы. Поэтому каждое из этих сообщений будет ассоциироваться с одним единственным общим taint, которое является объединением заражений всех составных частей.
Это способствует эффективности системы, потому что нам нужно хранить только один тег taint для каждого из этих сообщений. В крайнем случае, просто произойдет переоценка степени заражения, но оно никогда не приведёт к ухудшению безопасности. Самое плохое, что при этом может произойти – это то, что в сеть не попадут данные, которые могли бы туда попасть без всяких опасных последствий для конфиденциальности.
Итак, когда вы создаете IPC сообщение, то оно получает объединенный taint. Когда вы читаете то, что получили в этом сообщении, то извлеченные данные получают заражение от самого сообщения, что имеет смысл. Вот как обрабатываются сообщения IPC.
Также стоит побеспокоиться о том, как обрабатывается файл. Поэтому что каждый файл получает один тег taint, и этот тег хранится вместе с файлом в его метаданных на стабильном носителе информации, таком, как карта памяти SD. Здесь имеет место тот же консервативный подход к заражению, что и в предыдущих случаях. Основная идея заключается в том, что приложение получает доступ к некоторым конфиденциальным данным, например, к местоположению GPS, и возможно, собирается записать эти данные в файл, поэтому TaintDroid обновляет тег taint этого файла флагом GPS, после чего приложение закрывается. Позднее, возможно, включается какое-то другое приложение, которое читает этот файл.
Когда он попадает в виртуальную машину, в приложение, то TaintDroid видит, что у него есть этот флаг, и поэтому любые данные, извлеченные во время чтения этого файла, тоже будут иметь этот GPS флаг. Я думаю, это довольно просто.
Итак, какие вещи мы можем заразить с точки зрения Java? В основном существуют пять типов объектов Java, которым нужны флаги taint. Во-первых, это локальные переменные Local variables, которые используются в методе. Возвращаясь к предыдущим примерам, мы можем считать, что такой переменной является char c.
Поэтому мы должны назначить этим элементам флаги. Второй тип – это аргументы метода Method arguments, они также должны иметь флаги заражения. Обе эти вещи обитают в стеке, поэтому TaintDroid должен отслеживать назначение флагов и еще много чего для этих типов объектов.
Мы также должны назначить флаги полям экземпляров объектов Object instance fields. Представим, что имеется некий объект С, это круг, и я хочу узнать его радиус. Таким образом, у нас имеется поле c.radius, и мы должны связать информацию о заражении каждого из этих полей: с и radius.
Четвёртый тип объектов Java — это поля статических классов Static class fields, для которых также нужна информация taint. Это может быть что-то вида circle.property, то есть описание свойств круга, для которого мы назначаем некую информацию taint.
Пятый тип — это массивы Arrays, о которых мы говорили ранее, и мы назначаем для всего массива одну общую часть информации о заражении.
Основная идея реализации хранения флагов taint для этих типов объектов Java состоит в том, что мы попытаемся хранить флаги taint для переменной рядом с самой переменной.
Скажем, у нас имеется некая целочисленная переменная, и мы хотим разместить вместе с ней какое-то загрязнение taint. Мы хотим попытаться сохранить это состояние как можно ближе к переменной, возможно, по причинам обеспечения эффективной работы кэша на уровне процессора. Если бы мы хранили taint очень далеко от этой переменной, это может вызвать проблемы, потому что после того, как интерпретатор посмотрит на значение памяти для этой фактической переменной Java, он захочет как можно быстрей ознакомиться с информацией о её заражении.
Если мы посмотрим на операцию move-op, то заметим, что в этих местах кода, dst и src, когда интерпретатор рассматривает значения, он также рассматривает и соответствующие заражения taint.
Таким образом располагая эти вещи как можно ближе друг к другу, вы пытаетесь обеспечить более эффективное использование кеша. Это делается довольно просто. Если посмотреть на то, что разработчики делают для аргументов методов и локальных переменных, которые обитают в стеке, можно увидеть, что по существу они выделяют флаги taint рядом с тем местом, где расположены переменные.
Допустим, что у нас имеется любимая вещь наших лекций, схема стека, которую вы, вероятно, скоро возненавидите за частое упоминание. Пусть в нашем стеке расположена локальная переменная 0, тогда TaintDroid будет хранить в памяти тег о заражении данной переменной прямо под ней. Если далее у вас следует другая переменная, её тег также будет расположен прямо под ней, и так далее. Это довольно просто. Все эти вещи будут расположены на одной линии кеша, что сделает доступ к памяти менее затратным.
Студент: мне интересно, как можно иметь один флаг для целого массива и разные флаги для каждого свойства объекта. Что, если один из методов объекта сможет получить доступ к данным, которые хранятся в его свойствах? Это было бы… понимаете, о чем я?
Профессор: вы спрашиваете о причине применения именно такой политики?
Студент: да, о причине использования такой политики.
Профессор: я думаю, что это сделано для обеспечения эффективности реализации. Вероятно, имеются и другие правила — например, они не сообщают длину массива данных, потому что возможна утечка информации, поэтому они не распространяют заражение на этот показатель. Так что я думаю, что некоторые решения приняты просто из соображений эффективности. В принципе нет ничего, что мешало бы при предоставлении доступа к каждому элементу массива указывать, что расположенная слева вещь получает taint только от каких-то конкретных элементов.
Однако не ясно, будет ли это правильно, потому что, по-видимому, если помещать в массив какую-то вещь, то эта вещь должна что-либо знать об этом массиве. Поэтому я думаю, что разработчики используют сочетание обеих политик. Будучи чрезмерно консервативными, вы не должны допускать утечки данных, которые вы хотите защитить, но в то же время, чтобы иметь доступ к массиву, вы должны что-то о нём знать. А когда нужно что-то о чем-то узнать, это обычно означает, что вы используете taint.
Итак, это основная схема, которую они используют для хранения всей этой информации рядом друг с другом. Можно представить, что для полей классов и для полей объектов делается то же самое. При объявлении класса у вас имеется некоторая память слота для конкретной переменной, а прямо рядом с этим слотом располагается информация о taint для этой переменной. Так что я думаю, что все это довольно разумно.
Таков принцип работы TaintDroid. При инициализации системы или в другое время работы системы, TaintDroid смотрит на все источники потенциально зараженной информации и присваивает флаг каждой из этих вещей — датчику GPS, камере и так далее. По мере выполнения программы он будет извлекать конфиденциальную информацию из этих источников, после чего интерпретатор рассмотрит все типы функций в соответствии с таблицей, приведённой в статье, чтобы выяснить, как распространять заражение taint по системе.
Самое интересное происходит, когда данные пытаются проникнуть за пределы системы. TaintDroid может контролировать сетевые интерфейсы и видеть все, что пытается через них пройти. Он смотрит на наличие тегов taint, и если данные, которые пытаются проникнуть в сеть, имеют один или несколько таких флагов, им будет запрещено пользоваться сетью. То, что происходит в этот момент, на самом деле зависит от приложения.
Например, TaintDroid может показать пользователю предупреждение, в котором будет сказано, что кто-то пытается отправить на сторону данные о его местоположении. Возможно, что TaintDroid содержит встроенные политики, которые позволят приложению выйти в сеть, но при этом обнулят все конфиденциальные данные, которые оно попытается передать, и так далее. В статье этот механизм не описан достаточно подробно, так как в первую очередь авторов волновал вопрос «просачивания» данных в сеть.
В разделе статьи под названием «Оценка» обсуждаются некоторые из вещей, найденных в процессе изучения работы системы. Так, авторы статьи обнаружили, что приложения Android будут пытаться извлечь данные способами, незаметными пользователю. Предположим, они будут пытаться использовать ваше местоположение для рекламы, они пошлют ваш номер на удалённый сервер и так далее. Важно отметить, что эти приложения, как правило, не «ломают» модель безопасности Android в том смысле, что пользователь должен сам разрешить им доступ к сети или позволить им использовать список контактов. Тем не менее, приложения не предоставляют в лицензионном соглашении EULA сведения о том, что они собираются отправить номер телефона на какой-то сервер Silk Road 8 или типа того. На самом деле это обман и ввод пользователей в заблуждение относительно истинных намерений приложения, потому что если бы они видели эти требования EULA и знали, чем они чреваты, то могли бы задуматься, устанавливать такое приложение на свой смартфон или нет.
Студент: можно предположить, что даже если бы они поместили данные требования в лицензионное соглашение, это ничего бы не дало, потому что люди обычно не читают EULA.
Профессор: это вполне разумное предположение, потому что даже специалисты компьютерных наук не всегда проверяют лицензионное соглашение. Однако подобная честность в EULA всё равно оказала бы пользу, потому что встречаются люди, которые действительно читают лицензионное соглашение. Но вы совершенно правы, предполагая, что пользователи не будут читать кучу страниц, написанных мелким шрифтом, они просто нажмут «согласен» и установят приложение.
Итак, я думаю, что правила прохождения информации через систему достаточно просты, как мы уже говорили, taint просто перемещается с правой стороны на левую сторону. Однако иногда эти правила движения потока информации могут иметь несколько противоречивые результаты.
Представьте, что приложение реализует собственный класс связанных списков. У нас имеется простой класс под названием ListNode, у него будет поле объекта для данных Object data и объект ListNode next, который представляет следующей список.
Предположим, что приложение присвоило полю Object data зараженные данные – конфиденциальную информацию, полученную от датчика GPS или чего-то еще. Вопрос состоит в том, что когда мы вычислим длину этого списка, должна ли она быть зараженной? Вас поразит, что ответ на ответ вопрос будет «нет», что объясняется тем, как TaintDroid и некоторые из подобных систем определяют информационный поток. Давайте рассмотрим, что значит добавить узел к связанному списку.
Добавление узла состоит из 3-х шагов. Итак, первое, что вы делаете, это выделяете новый узел списка, который содержит данные, которые требуется добавить – Alloc ListNode. Второй шаг — вы назначаете поле данных этого нового узла. И третье, что вы делаете, это используете некий вид патча для ListNode next, чтобы объединить узлы в список – это указатель «next» ptr.
Интересно то, что третий шаг вообще не связан с полем данных, он просто рассматривает следующее значение. Как только Object data будет заражено, мы начинаем вычислять длину списка, начав с какого-то головного узла, проходим через все указатели «next» ptr и просто подсчитываем, сколько указателей прошли. Так что алгоритм подсчёта не прикасается к заражённым данным.
Интересно, что если у вас есть связанный список, который заполнен зараженными данными и затем вычисляется его длина, это не приведет к генерации зараженного значения. Это может показаться немного нелогичным, хотя рассматривая массивы, мы уже говорили о том, что длина массива также не содержит taint. Здесь имеется та же причина. Ближе к концу лекции мы обсудим, как можно использовать язык, который позволяет вам как программисту определить собственные виды заражения, и тогда вы можете разработать свою собственную политику для таких вещей.
Хорошим свойством TaintDroid является то, что вы, как разработчик, не должны ничего маркировать, TaintDroid делает это за вас. Он отмечает все конфиденциальные вещи, которые могут быть источником информации, и все вещи, которые могут быть «поглотителями» информации, поэтому вы, как разработчик, готовы к работе. Но если вы хотите контролировать добавление узлов, вам, возможно, придется самостоятельно создать некоторые политики.
Как работа TaintDroid влияет на производительность системы? Существующие накладные расходы на самом деле кажутся довольно разумными. Накладные расходы памяти заключаются в хранении всех тегов заражений. Нагрузка на процессор в основном будет складываться из назначения, распространения и проверки этих заражений, причём следует заметить, что использование виртуальной машины Dalvik представляет собой дополнительную работу. Итак, глядя на источник, глядя на эту 32-битную информацию о заражении, мы совершаем операции, которые уже рассматривали. Это вычислительные издержки.
Эти накладные расходы кажутся довольно умеренными. Как сообщают авторы статьи, хранение тегов taint требует от 3% до 5% дополнительной памяти, так что это не так уж плохо. Нагрузка на процессор несколько выше и может достигать от 3% до 29% вычислительной мощности. Это происходит из-за того, что каждый раз при выполнении цикла интерпретатору необходимо взглянуть на эти теги и выполнить соответствующие операции. Хотя это всего лишь побитовые операции, их требуется выполнять все время. Это неплохо даже в случае нагрузки в 29%, потому что разработчики из Силиконовой Долины постоянно говорят о том, что для современных телефонов нужны четырёхъядерные процессоры. Единственная проблема может возникнуть с аккумулятором, потому что даже если у вас появятся дополнительные ядра процессора, вряд ли вам захочется иметь в кармане раскалённый телефон, который начнёт «взрыкивать» при попытках рассчитать эти вещи. Но если ваша батарея не особо страдает от таких вычислений, то всё не так уж плохо.
Итак, это был обзор работы TaintDroid. Есть вопросы?
Студент: тегами отмечается только то, что находится там все время? Или же тегами помечается каждая переменная?
Профессор: помечается все, так что теоретически вам ничто не мешает поместить любую информацию о заражении для вещей, которые вообще не имеют заражения. Я думаю, что как только что-то получает хотя бы один бит taint, вам нужно соорудить что-то вроде динамического макета изменений. Потому что если какой-то локальный параметр в стеке заражен, то вы выделяете весь стек, который теперь помечается как зараженный. Или у вас в куче есть этот дополнительный флаг taint, и вы смотрите, как он переписывает стек, а затем кто-то использует ваш код, то нужно будет заново проверить, как это работает. Таким образом, на практике типичное использование похоже на теневую память, поэтому каждый байт в приложении резервируется каком-то байтом дополнительной информации. В случае TaintDroid это затенение фактически обитает рядом с самой переменной. Данная система серьёзным образом отслеживает информацию на уровне инструкций виртуальной машины Dalvik.
Рассмотрим вопрос – можно ли отслеживать taint на уровне инструкций x86 или инструкций ARM? Это полезно, потому что тогда мы сможем понять, как потоки информации следуют через произвольные приложения, а не только через те, которые работают внутри виртуальной машины, которая требует, чтобы вы запустили Java и так далее. Так почему бы нам не отслеживать заражение на этом уровне?
Оказывается, что это можно сделать, потому что существуют проекты отслеживания заражений на низком уровне, которые мы уже рассмотрели. Положительным является то, что можно увеличить охват, не углубляясь в эвристику того, как, например, Java-код взаимодействует с машинно-ориентированными методами. Все это в конечном итоге приведет к выполнению инструкций x86, что избавит вас от ручного труда, связанного с тем, что вы, как разработчик, должны будете понять, как использовать семантику при создание собственных методов отслеживания заражения.
Но проблема заключается в том, что если отслеживать taint на таком низком уровне, то это может быть слишком трудоёмко, кроме того, вы можете получить много ложных срабатываний. Стоит ещё упомянуть низкую корректность таких методов.
Как известно, x86 — это сложный набор инструкций. Он позволяет делать совершенно сумасшедшие вещи. Не знаю, видели ли вы когда-нибудь руководства по x86, они огромны. Например, у вас будет одно огромное руководство, вот такое толстое, при чём в нём будут размещены статьи с литеры М по литеру P, и чтобы разобраться в вопросе, вам необходимо будет иметь целую серию таких томов, я не шучу!
Так что на самом деле довольно сложно думать о том, чтобы отслеживать taint на уровне инструкций x86. Потому что даже, казалось бы, такие простые инструкции, как некоторые команды AD, устанавливают все типы регистровых файлов процессора, флаги и тому подобное.
Так что во-первых, это очень сложно описать, что может привести к некорректному выполнению программы. Во-вторых, даже если бы вы смогли это сделать, это обойдётся очень дорого. Вы рассматриваете вещи на очень и очень низком уровне, поэтому количество состояний, которые вы должны отслеживать, может стремительно вырасти.
Это может быть очень чувствительный вычислительный процесс, чреватый ложными срабатываниями и серьёзными проблемами при некорректном исполнении. Например, если у вас имеются неправильно зараженные данные ядра. Если ваша инфраструктура попытается быть ультраконсервативной и не захочет ничего упустить, говоря себе, что лучше ошибиться в сторону увеличения безопасности, она заразит часть структуры данных ядра. После этого вы получите то, что авторы статьи назвали захватывающим термином «зараженный взрыв» Taint Explosion.
Это означает, что в определенный момент заражённые вещи включаются в такое огромное количество вычислений, что могут заразить всю программу. Похоже на то, что происходит в игре Dungeons and Dragons, когда вы прикасаетесь к опасной вещи и в конце концов смерть распространяется по всему вашему телу.
Это очень плохо, потому что если вы не можете жестко ограничить протекание заражения через систему, то через какое-то время система вообще откажется что-либо делать. Вы не сможете ничего отправить по сети, не сможете ничего отобразить на экране, потому что все в вашей системе будет помечено как зараженная конфиденциальная информация, даже если это не так.
Это может произойти, если каким-то образом заражены указатель стека esp или указатель прерывания esb. Если это произойдет, вы окажетесь в большой беде.
Представьте себе, что все инструкции x86, которые обращаются к стеку, проходят через esp. Таким образом, если стек регистра получает заражение, это плохо. Часто, когда вы хотите, чтобы ваши эквиваленты имели доступ к локальным переменным, это косвенно касается ebp, и если он будет заражен, считайте, что игра закончена. В лекционной статье имеется ссылка на документ, который анализирует все эти вещи и говорит, что мы должны быть очень осторожны, когда отслеживаем taint на таком низком уровне.
Потому что всё происходит очень быстро, особенно если используются определённые оптимизации ядра Linux для ускорения выполнения кода. Если это непреднамеренно приведёт к указателю стека или указателю прерывания, указатель становится зараженным. И как только это произойдет, вы не сможете сделать что-либо полезное с системой отслеживания заражения.
Студент: как это воплощено на программном уровне? Похоже, что у нас должны быть все эти регистровые файлы.
Профессор: все эти регистровые файлы имеют отношение к корректности написания программы. Если вы не очень хорошо разбираетесь в архитектуре x86, будут вещи, которые вы обязательно упустите. Я расскажу вам о том, в чём разбираюсь. Существуют штуки под названием Bochs – программы для эмуляции аппаратного обеспечения IBM PC, включая процессоры архитектуры х86. На самом деле у них есть нечто под названием TaintBochs, которое эмулирует поток на уровне системы x86 и работает как интерпретатор. Оно займет всю вашу ОС и все ваши приложения и будет рассматривать каждую инструкцию x86, пытаясь имитировать то, что должно делать соответствующее аппаратное обеспечение. Можно представить, что это будет происходить очень и очень медленно. Хорошее в этом то, что не потребуется никакой физической аппаратной поддержки, и то, что это относительно простой способ настройки программной модели того, как все работает, если вы обнаружили, что не отследили некоторые регистровые файлы или что-либо в этом роде.
54:10
Курс MIT «Безопасность компьютерных систем». Лекция 21: «Отслеживание данных», часть 3
Полная версия курса доступна здесь.
Спасибо, что остаётесь с нами. Вам нравятся наши статьи? Хотите видеть больше интересных материалов? Поддержите нас оформив заказ или порекомендовав знакомым, 30% скидка для пользователей Хабра на уникальный аналог entry-level серверов, который был придуман нами для Вас: Вся правда о VPS (KVM) E5-2650 v4 (6 Cores) 10GB DDR4 240GB SSD 1Gbps от $20 или как правильно делить сервер? (доступны варианты с RAID1 и RAID10, до 24 ядер и до 40GB DDR4).
VPS (KVM) E5-2650 v4 (6 Cores) 10GB DDR4 240GB SSD 1Gbps до января бесплатно при оплате на срок от полугода, заказать можно тут.
Dell R730xd в 2 раза дешевле? Только у нас 2 х Intel Dodeca-Core Xeon E5-2650v4 128GB DDR4 6x480GB SSD 1Gbps 100 ТВ от $249 в Нидерландах и США! Читайте о том Как построить инфраструктуру корп. класса c применением серверов Dell R730xd Е5-2650 v4 стоимостью 9000 евро за копейки?