Миллионы бинарников спустя. Как укреплялся Linux

Автор оригинала: Theofilos Petsios
  • Перевод
TL;DR. В этой статье мы исследуем защитные схемы (hardening schemes), которые из коробки работают в пяти популярных дистрибутивах Linux. Для каждого мы взяли конфигурацию ядра по умолчанию, загрузили все пакеты и проанализировали схемы защиты во вложенных двоичных файлах. Рассматриваются дистрибутивы OpenSUSE 12.4, Debian 9, CentOS, RHEL 6.10 и 7, а также Ubuntu 14.04, 12.04 и 18.04 LTS.

Результаты подтверждают, что даже основные схемы, такие как стековые канарейки и независимый от позиции код, ещё не всеми используются. Ситуация ещё хуже у компиляторов, когда речь идёт о защите от уязвимостей вроде столкновения стека (stack clash), которые попали в центр внимания в январе после публикации информации об уязвимостях в systemd. Но не всё так безнадёжно. В значительной части бинарников реализованы базовые методы защиты, и их число растёт от версии к версии.

Проверка показала, что наибольшее количество методов защиты реализовано в Ubuntu 18.04 на уровне ОС и приложений, затем следует Debian 9. С другой стороны, в OpenSUSE 12.4, CentOS 7 и RHEL 7 тоже реализованы базовые схемы защиты, а защита от столкновения стека применяется ещё шире при гораздо более плотном наборе пакетов по умолчанию.

Введение


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

Здесь в игру вступают методы защиты или усиления программ (hardening). Некоторые типы ошибок мы не в силах предотвратить, зато можем сделать жизнь злоумышленника сложнее и частично решить проблему, предотвратив или помешав эксплуатации этих ошибок. Такая защита используется во всех современных ОС, однако методы сильно отличаются по сложности, эффективности и производительности: от стековых канареек (stack canaries) и ASLR до полноценных защит CFI и ROP. В этой статье рассмотрим, какие методы защиты применяются в самых популярных дистрибутивах Linux в конфигурации по умолчанию, а также изучим свойства бинарников, которые распространяются через системы управления пакетами каждого дистрибутива.

CVE и безопасность


Все мы видели статьи с названиями вроде «Самые уязвимые приложения года» или «Самые уязвимые операционные системы». Обычно там приводят статистику по общему количеству записей об уязвимостях типа CVE (Common Vulnerability and Exposures), полученной из Национальной базы уязвимости (NVD) от NIST и других источников. Впоследствии эти приложения или ОС ранжируются по количеству CVE. К сожалению, хотя CVE очень полезны для отслеживания проблем и информирования поставщиков и пользователей, они мало говорят о реальной безопасности программного обеспечения.

Для примера, рассмотрим общее количество CVE за последние четыре года для ядра Linux и пяти наиболее популярных серверных дистрибутивов, а именно Ubuntu, Debian, Red Hat Enterprise Linux и OpenSUSE.


Рис. 1

Что нам говорит этот график? Означает ли большее количество CVE, что один дистрибутив более уязвим, чем другой? Ответа нет. Например, в этой статье вы увидите, что в Debian реализованы более жёсткие механизмы защиты по сравнению, скажем, с OpenSUSE или RedHat Linux, и всё же у Debian больше CVE. Однако они не обязательно означают ослабленную безопасность: даже наличие CVE не говорит, является ли уязвимость эксплуатируемой. Баллы серьёзности дают представление о том, насколько вероятно использование уязвимости, но в конечном итоге эксплуатируемость в значительной степени зависит от защиты, присутствующей в затронутых системах, а также от ресурсов и возможностей злоумышленников. Более того, отсутствие отчётов CVE ничего не говорит о других незарегистрированных или неизвестных уязвимостях. Разница в CVE может объясняться не качеством ПО, а другими факторами, включая ресурсы, выделенные на тестирование, или размер пользовательской базы. В нашем примере большее количество CVE у Debian может просто указывать на то, что Debian поставляет больше пакетов программного обеспечения.

Разумеется, система CVE даёт полезную информацию, которая позволяет создавать соответствующие защиты. Чем лучше мы понимаем причины сбоя программы, тем проще определить возможные способы эксплуатации и разработать соответствующие механизмы обнаружения и реагирования. На рис. 2 показаны категории уязвимостей для всех дистрибутивов за последние четыре года (источник). Сразу видно, что большинство CVE попадают в следующие категории: отказ в обслуживании (DoS), выполнение кода, переполнение, повреждение памяти, утечка (эксфильтрация) информации и эскалация привилегий. Хотя многие CVE учтены несколько раз в разных категориях, в целом одни и те же проблемы сохраняются из года в год. В следующей части статьи мы оценим использование различных схем защиты для предотвращения эксплуатации указанных уязвимостей.


Рис. 2

Задачи


В этой статье мы намерены ответить на следующие вопросы:

  • Какова безопасность различных дистрибутивов Linux? Какие защитные механизмы существуют в ядре и приложениях пользовательского пространства?
  • Как со временем изменилось принятие механизмов защиты для различных дистрибутивов?
  • Каковы средние зависимости пакетов и библиотек у каждого дистрибутива?
  • Какие защиты реализованы для каждого бинарника?

Выбор дистрибутивов


Оказывается, сложно найти точную статистику по установкам дистрибутивов, так как в большинстве случаев количество загрузок не указывает на количество реальных установок. Тем не менее, варианты Unix составляют большинство серверных систем (на веб-серверах 69,2%, по статистике W3techs и других источников), и их доля постоянно растёт. Таким образом, для нашего исследования мы сосредоточились на дистрибутивах, доступных из коробки на платформе Google Cloud. В частности, мы выбрали следующие ОС:

Дистрибутив/версия Ядро Билд
OpenSUSE 12.4 4.12.14-95.3-default #1 SMP Wed Dec 5 06:00:48 UTC 2018 (63a8d29)
Debian 9 (stretch) 4.9.0-8-amd64 #1 SMP Debian 4.9.130-2 (2018-10-27)
CentOS 6.10 2.6.32-754.10.1.el6.x86_64 #1 SMP Tue Jan 15 17:07:28 UTC 2019
CentOS 7 3.10.0-957.5.1.el7.x86_64 #1 SMP Fri Feb 1 14:54:57 UTC 2019
Red Hat Enterprise Linux Server 6.10 (Santiago) 2.6.32-754.9.1.el6.x86_64 #1 SMP Wed Nov 21 15:08:21 EST 2018
Red Hat Enterprise Linux Server 7.6 (Maipo) 3.10.0-957.1.3.el7.x86_64 #1 SMP Thu Nov 15 17:36:42 UTC 2018
Ubuntu 14.04 (Trusty Tahr) 4.4.0–140-generic
#166~14.04.1-Ubuntu SMP Sat Nov 17 01:52:43 UTC 20…
Ubuntu 16.04 (Xenial Xerus) 4.15.0–1026-gcp #27~16.04.1-Ubuntu SMP Fri Dec 7 09:59:47 UTC 2018
Ubuntu 18.04 (Bionic Beaver) 4.15.0–1026-gcp #27-Ubuntu SMP Thu Dec 6 18:27:01 UTC 2018
Таблица 1

Анализ


Изучим конфигурацию ядра по умолчанию, а также свойства пакетов, доступных через пакетный менеджер каждого дистрибутива из коробки. Таким образом, мы рассматриваем только пакеты из зеркал по умолчанию каждого дистрибутива, игнорируя пакеты из нестабильных репозиториев (например, зеркал ‘testing’ в Debian) и сторонние пакеты (например, пакеты Nvidia со стандартных зеркал). Кроме того, мы не рассматриваем пользовательские компиляции ядра или конфигурации с повышенной защитой.

Анализ конфигурации ядра


Мы применили скрипт анализа на основе свободного чекера kconfig. Рассматриваем параметры защиты из коробки у названных дистрибутивов и сравниваем их со списком от Проекта самозащиты ядра (KSPP). Для каждого параметра конфигурации таблица 2 описывает желаемую настройку: галочка стоит для дистрибутивов, которые соответствуют рекомендациям KSSP (разъяснение терминов см. здесь; в будущих статьях мы расскажем, как появились многие из этих методов защиты и как взломать систему в их отсутствие).





В целом, в новых ядрах более строгие настройки из коробки. Например, у CentOS 6.10 и RHEL 6.10 на ядре 2.6.32 нет большинства критических функций, реализованных в новых ядрах, таких как SMAP, строгие разрешения RWX, рандомизация адресов или защита copy2usr. Следует отметить, что многие из вариантов конфигурации из таблицы отсутствуют в более старых версиях ядра и не применимы в реальности — в таблице это всё равно указано как отсутствие должной защиты. Аналогично, если параметр конфигурации отсутствует в данной версии, а для безопасности этот параметр нужно отключить, это считается разумной конфигурацией.

Ещё один момент при интерпретации результатов: некоторые конфигурации ядра, которые увеличивают поверхность атаки, одновременно могут использоваться для безопасности. Такие примеры включают uprobes и kprobes, модули ядра и BPF/eBPF. Наша рекомендация состоит в том, чтобы использовать вышеуказанные механизмы для обеспечения реальной защиты, поскольку они нетривиальны для использования, а их эксплуатация предполагает, что вредоносные субъекты уже закрепились в системе. Но если эти параметры включены, системный администратор должен активно следить за злоупотреблениями.

Изучая далее записи таблицы 2, мы видим, что современные ядра предоставляют несколько вариантов для защиты от эксплуатации таких уязвимостей, как утечка информации и переполнение стека/кучи. Однако мы замечаем, что даже самые последние популярные дистрибутивы ещё не реализовали более сложную защиту (например, с патчами grsecurity) или современную защиту от атак повторного использования кода (например, сочетание рандомизации со схемами типа R^X для кода). Что ещё хуже, даже эти более продвинутые средства защиты не защищают от полного спектра атак. Таким образом, системным администраторам крайне важно дополнять разумные конфигурации решениями, предлагающими обнаружение и предотвращение эксплойтов во время выполнения.

Анализ приложений


Неудивительно, что у разных дистрибутивов разные характеристики пакетов, параметры компиляции, зависимости библиотек и т. д. Различия существуют даже для родственных дистрибутивов и пакетов с небольшим количеством зависимостей (например, coreutils в Ubuntu или Debian). Чтобы оценить различия, мы загрузили все доступные пакеты, извлекли их содержимое и проанализировали бинарные файлы и зависимости. Для каждого пакета мы отслеживали другие пакеты, от которых он зависит, и для каждого бинарника отслеживали его зависимости. В этом разделе кратко изложим выводы.

Дистрибутивы


В общей сложности мы загрузили 361 556 пакетов для всех дистрибутивов, извлекая только пакеты с зеркал по умолчанию. Мы игнорировали пакеты без исполняемых файлов ELF, такие как исходные коды, шрифты и т. д. После фильтрации осталось 129 569 пакетов, содержащих в общей сложности 584 457 бинарных файлов. Распределение пакетов и файлов по дистрибутивам показано на рис. 3.


Рис. 3

Можно заметить, что чем современнее дистрибутив, тем больше в нём пакетов и двоичных файлов, что логично. При этом пакеты Ubuntu и Debian включают гораздо больше двоичных файлов (как исполняемых, так и динамических модулей и библиотек), чем CentOS, SUSE и RHEL, что потенциально влияет на поверхность атаки Ubuntu и Debian (нужно заметить, что цифры отражают все бинарники всех версий пакета, то есть некоторые файлы анализируются несколько раз). Это особенно важно, если учесть зависимости между пакетами. Таким образом, уязвимость в бинарнике одного пакета может повлиять на многие части экосистемы, как уязвимая библиотека может повлиять на все бинарные файлы, импортирующие её. В качестве точки отсчёта посмотрим на распределение числа зависимостей по пакетам в различных ОС:


Рис. 4

Почти во всех дистрибутивах у 60% пакетов минимум по 10 зависимостей. Кроме того, у некоторых пакетов количество зависимостей значительно больше (более 100). То же самое относится и к обратным зависимостям пакетов: как и ожидалось, несколько пакетов используются многими другими пакетами в дистрибутиве, поэтому уязвимости в этих немногих избранных имеют высокий риск. В качестве примера в следующей таблице перечислены 20 пакетов с максимальным количеством обратных зависимостями в SLES, Centos 7, Debian 9 и Ubuntu 18.04 (в каждой ячейке указан обозначает пакет и количество обратных зависимостей).


Таблица 3

Интересный факт. Хотя все анализируемые ОС построены для архитектуры x86_64, а у большинства пакетов архитектура определена как x86_64 и x86, но пакеты часто содержат двоичные файлы для других архитектур, как показано на рис. 5.


Рис. 5

В следующем разделе углубимся в характеристики анализируемых бинарников.

Статистика защиты бинарных файлов


Как абсолютный минимум, нужно изучить базовый набор вариантов защиты для имеющихся бинарных файлов. Несколько дистрибутивов Linux поставляются со скриптами, которые выполняют такие проверки. Например, в Debian/Ubuntu есть такой скрипт. Вот пример его работы:

$ hardening-check $(which docker)
/usr/bin/docker:
 Position Independent Executable: yes
 Stack protected: yes
 Fortify Source functions: no, only unprotected functions found!
 Read-only relocations: yes
 Immediate binding: yes

Скрипт проверяет пять функций защиты:

  • Position Independent Executable (PIE): указывает, можно ли переместить в памяти текстовый раздел программы, чтобы добиться рандомизации, если в ядре включен ASLR.
  • Stack Protected: включены ли стековые канарейки для защиты от атак на столкновение стека.
  • Fortify Source: заменяются ли небезопасные функции (например, strcpy) их более безопасными аналогами, а проверяемые в рантайме вызовы — их не проверяемыми аналогами (например, memcpy вместо __memcpy_chk).
  • Read-only relocations (RELRO): помечены ли записи таблицы перемещения как «только для чтения», если они сработали до начала выполнения.
  • Immediate binding (немедленная привязка): разрешает ли компоновщик среды выполнения все перемещения перед началом выполнения программы (это эквивалентно полному RELRO).

Достаточно ли вышеперечисленных механизмов? К сожалению, нет. Известны способы обхода всех вышеперечисленных защит, но чем более жёсткая защита, тем выше планка для атакующего. Например, методы обхода RELRO труднее применить, если действует PIE и немедленная привязка. Аналогично, полный ASLR требует дополнительной работы для создания рабочего эксплойта. Однако изощрённые злоумышленники уже готовы встретить такие защиты: их отсутствие по сути ускорит взлом. Поэтому крайне важно, чтобы эти меры рассматривались как необходимый минимум.

Мы хотели изучить, как много бинарных файлов в рассматриваемых дистрибутивах защищены этими, а также ещё тремя методами:

  • Неисполняемый бит (NX) предотвращает выполнение в любом регионе, который не должен быть исполняемым, например в куче стека и т. д.
  • RPATH/RUNPATH обозначает путь выполнения, используемый динамическим загрузчиком для поиска соответствующих библиотек. Первый является обязательным для любой современной системы: его отсутствие позволяет злоумышленникам произвольно записывать полезную нагрузку в память и выполнять её как есть. Для второго неверные конфигурации пути выполнения помогают в введении ненадёжного кода, который может привести к ряду проблем (например, эскалация привилегий, а также другие проблемы).
  • Защита от столкновения стека обеспечивает защиту от атак, которые заставляют стек наложиться на другие области памяти (например, на кучу). Учитывая недавние эксплойты, злоупотребляющие уязвимостями со столкновением кучи в systemd, мы сочли уместным включить этот механизм в наш набор данных.

Итак, без дальнейших церемоний, перейдём к числам. Таблицы 4 и 5 содержат выжимку анализа исполняемых файлов и библиотек различных дистрибутивов, соответственно.

  • Как можно заметить, защита NX реализована везде, за редкими исключениями. В частности, можно отметить несколько более низкое её использование в дистрибутивах Ubuntu и Debian по сравнению с CentOS, RHEL и OpenSUSE.
  • Стековые канарейки много где отсутствуют, особенно в дистрибутивах со старыми ядрами. Некоторый прогресс наблюдается в последних дистрибутивах Centos, RHEL, Debian и Ubuntu.
  • За исключением Debian и Ubuntu 18.04, в большинстве дистрибутивов плохая поддержка PIE.
  • Защита от столкновений стека слабо реализована в OpenSUSE, Centos 7 и RHEL 7 и практически отсутствует у остальных.
  • Все дистрибутивы с современными ядрами имеют некоторую поддержку RELRO, при этом лидирует Ubuntu 18.04, а второе место занимает Debian.

Как уже упоминалось, метрики в этой таблице — средние по всем версиям бинарного файла. Если смотреть только последние версии файлов, то цифры будут другими (например, см. прогресс Debian с внедрением PIE). Более того, большинство дистрибутивов обычно при подсчёте статистики проверяют защиту только нескольких функций в двоичном коде, а в нашем анализе указан истинный процент укреплённых функций. Поэтому если в бинарнике защищены 5 из 50 функций, мы укажем ему оценку 0,1, что соответствует 10% укреплённых функций.


Таблица 4. Характеристики защиты для исполняемых файлов, показанных на рис. 3 (реализация соответствующих функций в процентах от общего количества исполняемых файлов)


Таблица 5. Характеристики защиты для библиотек, показанных на рис. 3 (реализация соответствующих функций в процентах от общего количества библиотек)

Так прогресс есть? Определённо есть: это видно из статистики по отдельным дистрибутивам (например, Debian), а также из приведённых выше таблиц. В качестве примера в рис. 6 показано внедрение защитных механизмов в трёх последовательных дистрибутивах Ubuntu LTS 5 (мы опустили статистику защиты от столкновения стека). Мы замечаем, что от версии к версии всё больше файлов поддерживают стековых канареек, а также последовательно всё больше бинарных файлов поставляются с полной защитой RELRO.


Рис. 6

К сожалению, ряд исполняемых файлов в разных дистрибутивах по-прежнему не обладает ни одной из вышеуказанных защит. Например, взглянув на Ubuntu 18.04, можно заметить бинарник ngetty (замена getty), а также оболочки mksh и lksh, интерпретатор picolisp, пакеты nvidia-cuda-toolkit (популярный пакет для приложений с GPU-ускорением, таких как фреймворки машинного обучения) и klibc-utils. Аналогично, бинарник mandos-client (административный инструмент, позволяющий автоматически перезагружать машины с зашифрованными файловыми системами), а также rsh-redone-client (повторная реализация rsh и rlogin) поставляются без защиты NX, хотя у них права SUID :(. Кроме того, в нескольких suid-бинарниках нет базовой защиты, такой как стековые канарейки (например, бинарный файл Xorg.wrap из пакета Xorg).

Резюме и заключительные замечания


В этой статье мы выделили несколько свойств безопасности современных дистрибутивов Linux. Анализ показал, что в последнем дистрибутиве Ubuntu LTS (18.04) реализована в среднем самая сильная защита уровня ОС и приложений среди дистрибутивов с относительно новыми ядрами, таких как Ubuntu 14.04, 12.04 и Debian 9. Однако рассмотренные дистрибутивы CentOS, RHEL и OpenSUSE в нашем наборе данных по умолчанию выдают более плотный набор пакетов, а в последних версиях (CentOS и RHEL) имеют более высокий процент реализации защиты от столкновения стека, по сравнению с конкурентами на основе Debian (Debian и Ubuntu). Сравнивая версии CentOS и RedHat, мы замечаем большие улучшения во внедрении стековых канареек и RELRO с версий 6 до 7, но в среднем в CentOS реализовано больше функций, чем в RHEL. В целом, всем дистрибутивам следует уделить особое внимание защите PIE, которая, за исключением Debian 9 и Ubuntu 18.04, реализована менее чем в 10% бинарных файлов из нашего набора данных.

Наконец, следует отметить: хотя мы провели исследование вручную, существует множество инструментов безопасности (например, Lynis, Tiger, Hubble), которые выполняют анализ и помогают избежать небезопасных конфигураций. К сожалению, даже сильная защита в разумных конфигурациях не гарантирует отсутствие эксплойтов. Вот почему мы твёрдо убеждены, что жизненно важно обеспечить надёжный мониторинг и предотвращение атак в реальном времени, сосредоточив внимание на моделях эксплуатации и предотвращая их.
  • +10
  • 5,2k
  • 7
Поддержать автора
Поделиться публикацией

Похожие публикации

Комментарии 7

    0
    А что такое:
    стековые канарейки
    ?
      0
      Механизм проверки повреждения стека
        +2
        Технология предотвращения эксплуатации переполнения буферов на стеке (название такое из за канареек, которых использовали в шахтах как индикатор уровня CO).

        Цитата из статьи «Как устроены дыры в безопасности: переполнение буфера»:
        Определяемое на этапе исполнения значение, называемое „канарейкой“ (canary) пишется в конец стека рядом с адресом возврата. В конце каждой функции, это значение проверяется перед выполнением инструкции возврата. Если значение канарейки изменилось (по причине перезаписи в ходе переполнения), программа немедленно рухнет вместо продолжения.
        0
        m1rko, спасибо за перевод.

        некоторые конфигурации ядра, которые увеличивают поверхность атаки, одновременно могут использоваться для безопасности. Такие примеры включают uprobes и kprobes, модули ядра и BPF/eBPF. Наша рекомендация состоит в том, чтобы использовать вышеуказанные механизмы для обеспечения реальной защиты, поскольку они нетривиальны для использования, а их эксплуатация предполагает, что вредоносные субъекты уже закрепились в системе. Но если эти параметры включены, системный администратор должен активно следить за злоупотреблениями.

        Неудачный перевод очень интересного абзаца про анализ опций безопасности ядра.

        Действительно, есть ряд опций ядра Linux, при включении которых поверхность атаки для ядра увеличивается. При этом данные опции могут использоваться для предоставления дополнительных средств безопасности для пользовательского пространства, а именно: CONFIG_USER_NS для контейнерной изоляции, ftrace для live patching, eBPF для фильтрации трафика и пр.

        Рекомендация автора оригинальной статьи в том, что следует включать данный функционал, только если польза для общей безопасности системы превышает потенциальную угрозу эксплуатации уязвимостей в нем.
          0
          Интересно как обстоят дела с российскими дистрибутивами.
            0
            До тех пор пока для создания ПО будет использоватся негодный инструментарий, (т.е. языки типа Си, Си++ и т.д., принципиально не позволяющие написать надёжную программу), ситуация с надёжностью ПО будет только ухудшатся. И всевозможные костыли тут не помогут. Что говорить о ПО, производители процессоров к этой теме подключились. Скоро станет нормой ситуация, когда выезжаеш за ворота и отваливаются колёса. А тебе говорят: — «Ну ничего, бывает. Это же очень сложное устройство.» Автор, не воспринимай на свой счёт, статья хорошая.
              0
              Но вряд ли можно ожидать, что средний программист вдруг станет знаком с теорией формальных доказательств, например.

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

            Самое читаемое