Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
OpenBSD позиционируетcя как защищённая ОС. Однако за последние несколько месяцев в системе найден ряд уязвимостей. Конечно, в этом нет ничего экстраординарного. Хотя некоторые уязвимости довольно необычные. Можно даже сказать, критические. У разработчиков OpenBSD несколько принципов, как обеспечить безопасность. Вот два из них:
Не все согласны, что этих принципов достаточно, чтобы строить защищённые системы. Мне кажется, есть смысл изучить, работает ли подход OpenBSD, или он изначально обречён.
Для иллюстрации я выбрал не все, а только несколько интересных багов, которые случайно совпадают с темой нашего разговора.
Функции auth запускают хелперы без проверки argv. Отчёт. Патч.
Это поразительно простая ошибка, но, вероятно, она не показалась слишком очевидной во время код-ревью. Мне кажется, отчасти причина в некоторой путанице в том, кто отвечает за проверку входных данных. Вы можете вызвать каждый из трёх задействованных компонентов: программу, библиотеку или login_passwd — и разумно предположить, что проверку выполняет кто-то другой. В конце концов, я думаю, виновной признали библиотеку, потому что именно для неё появился патч, но лично мне её код на первый взгляд не кажется однозначно неправильным.
Более интересная часть истории заключается в том, что даже с упомянутой ошибкой libc функция login_passwd не была бы уязвима таким образом, если бы не другой баг. В 2001 году login_passwd переписали для поддержки kerberos, и, возможно, именно тогда ввели то, что является настоящей причиной ошибки. Предложение аутентификации запросно-ответного типа (как в системе s/key) возвращает аутентифицированное состояние, а не молчание. Много лет спустя код kerberos удалили, но часть кода для его поддержки осталась, как и введённая ошибка.
Если бы код kerberos тщательно почистили, то баг с аутентификацией всё равно остался бы (там были и некоторые другие связанные проблемы с парсингом argv), но его влияние, безусловно, сильно бы уменьшилось.
Не так легко провести корректный парсинг argv в контексте безопасности. Многие логичные советы и подходы тут не работают. Я только отмечу, что эта уязвимость выскочила после очередного обсуждения проблемы с именами файлов в Unix/Linux/POSIX, хотя ведущий минус (дефис) на самом деле не относится к названию файла.
Из кода ld.so не удалены плохие переменные окружения. Отчёт. Патч.
Что-то похожее на ошибку памяти. Но нет. Баг здесь заключается в привязке успеха одной операции — разбиения переменной окружения — к удалению этой переменной. Весьма самоуверенно.
Конечно, сторонники различных систем типизации будут уверять, что обработают эти операции в правильном порядке, но не факт, что это поможет. Код C не вылетал с ошибкой из-за отсутствия обработки ошибок или потому что ошибка распределения памяти осталась незаметной.
ftp следует редиректам на локальные файлы. Отчёт. Патч.
Баг NetBSD ftp с редиректами — это же давно типичный пример бесконтрольных функций. И вот опять та же ошибка (к счастью, с незначительными последствиями)! Ребята, ну сидите дома. Не добавляйте в свои программы дополнительные функции.
smtpd не может проверить некоторые адреса отправителей. Отчёт. Патч. Комментарий.
Думаю, что в комментарии Жиля всё сказано, но напомню предысторию. Давным-давно вся почта хранилась локально у каждого пользователя в /var/mail в файлах mbox. Это не очень здорово, потому что существует вероятность повреждения, если mua удалит электронное письмо, пока mda доставляет новое (не говоря уже о других проблемах типа искажения поля From). Поэтому файл mbox нужно заблокировать. Но блокировка в сетевой файловой системе работает ненадёжно. Так что вместо блокировки мы задействуем lock-файлы. Однако нужно прийти к согласию по определённому протоколу блокировки, ведь файлами mbox владеет пользователь, а самим каталогом — рут. Таким образом, чтобы фактически изменить файл mbox, нужно каждый раз запускать вспомогательную функцию setuid. Ну, вот уже первая проблема. Ещё один пережиток старых времён заключается в настройках mda, которую можно использовать не просто как программу, а как конвейер оболочки. Народ прописывает что-то вроде
Здесь очень много сложностей, которые корнями уходят в прошлое. И, к сожалению, этот проблемный код нельзя просто заменить. Электронная почта используется давным-давно, и на её основе построены очень сложные системы и рабочие процессы, поэтому очень трудно просто вырезать один кусок кода — и вставить новый. Несмотря на различные уровни разделения привилегий, родительский процесс с правами рута по-прежнему во многом доверяет своим менее привилегированным дочерним процессам. Он выполнит команды и аргументы, которые получит.
Избежать этого кажется так же просто, как переключиться на формат хранения maildir, но это требует различных изменений во многих местах. Стандартный mua mail не понимает этот формат. Как по мне, так mbox давно отжил своё, но многих он по-прежнему устраивает, а процедура обновления, возможно, не полностью прозрачна и не пройдёт автоматически.
Чтение за пределами области в smtpd можно использовать для выполнения команд. Отчёт. Патч.
Здесь реально проблема безопасности памяти. Отправив несколько забавных строк состояния, удалённый smtp-сервер может внедрить в очередь smtpd команды для выполнения. Когда электронное письмо помещается в очередь для повторной доставки, smtpd добавляет в заголовок некоторую информацию о месте назначения, чтобы знать, какую команду следует выполнить (см. выше). Эта атака похожа на «контрабанду» http-запросов. Если вы можете сгенерировать «отлуп» с неожиданными командами в заголовке, то smtpd выполнит их при попытке повторной доставки.
Как и выше, одна из проблем заключается в том, что наиболее чувствительные части smtpd находятся слишком близко к поверхности атаки. Мне кажется, истинная проблема в том, что smtpd хранит собственные метаданные внутри данных электронной почты. Из-за этого становится возможной атака с рассинхронизацией парсинга. Если бы процитированный ответ с сервера хранился совершенно отдельно от файла с инструкциями по доставке, то чтение за пределами границ не нанесло бы особого вреда.
Эта уязвимость мне кажется показательной, потому что мы здесь видим опасность смешивания данных с разными уровнями доверия. Эту опасность никогда не обсуждали. А она точно есть.
Конечно, вывод был понятен с самого начала, но мы всё равно об этом скажем.
Думаю, некоторые из этих ошибок помогают продемонстрировать, насколько жизненно важны для безопасности такие принципы, как удаление устаревших интерфейсов и снижение сложности кода. По большей части, провал произошёл из-за того, что этим принципам не следовали до конца, а не потому, что сами принципы испорчены или несостоятельны. Некоторые вещи ускользают из виду, но я не согласен, что разработчикам необходима какая-то сверхчеловеческая бдительность. Тут легко давать бесполезные советы, типа будь внимательнее, не делай ошибок и git gud [слэнговое геймерское выражение означает 'get good', то есть «поправляйся, выздоравливай» — прим. пер.]. Но я думаю, что у OpenBSD более серьёзные проблемы.
Даже OpenBSD может рискнуть безопасностью ради утилитарной практичности. Вот почему долгое время не вносятся изменения в некоторые устаревшие проекты. Так что, возможно, урок состоит в том, что эффективных принципов нужно придерживаться всегда, а не только тогда, когда это удобно. Хотя зачастую трудно сделать правильный выбор.
Три самые серьёзные уязвимости, auth и две smtpd, более или менее пригодны для эксплуатации только из-за архитектурных проблем, которые выходят за рамки оригинального бага. Они должны были остаться просто мелкими недочётами, которые демонстрируют, что в хорошо защищённой системе не обязательно нужно стремиться к идеальному коду, то есть мелкие ошибки допустимы — и они не повляют на безопасность. Увы, бывает трудно выявить недостатки дизайна в абстрактном виде. И все части системы по отдельности выглядят защищёнными, но если их соединить, то могут появиться слабые места.
Разделение привилегий — ключевой компонент безопасности OpenBSD, а в его основе лежит межпроцессная коммуникация. Есть смысл более пристально присмотреться к тому, какие проблемы могут возникнуть с повреждёнными процессами. Защищённые браузеры всё больше усиляют защиту и затрудняют атаки. В частности, smtpd должен быть защищён от повреждения памяти в сетевых задачах. Но вызывает тревогу та лёгкость, с которой он способен управлять родительским процессом.
Только одну ошибку можно было предотвратить, используя более безопасный язык. Да, наверное, существует какая-то программная идиома, которая в некоторых случаях способна помочь, если следовать ей с религиозным упорством. Но я пока не уверен, что каждый программист по умолчанию корректно закодирует все соответствующие инварианты.
Написание почтового сервера — сложное дело. Особенно если вас поджимают рамки легаси.
- избегать ошибок;
- минимизировать риск ошибок.
Не все согласны, что этих принципов достаточно, чтобы строить защищённые системы. Мне кажется, есть смысл изучить, работает ли подход OpenBSD, или он изначально обречён.
Для иллюстрации я выбрал не все, а только несколько интересных багов, которые случайно совпадают с темой нашего разговора.
libc auth
Функции auth запускают хелперы без проверки argv. Отчёт. Патч.
Это поразительно простая ошибка, но, вероятно, она не показалась слишком очевидной во время код-ревью. Мне кажется, отчасти причина в некоторой путанице в том, кто отвечает за проверку входных данных. Вы можете вызвать каждый из трёх задействованных компонентов: программу, библиотеку или login_passwd — и разумно предположить, что проверку выполняет кто-то другой. В конце концов, я думаю, виновной признали библиотеку, потому что именно для неё появился патч, но лично мне её код на первый взгляд не кажется однозначно неправильным.
Более интересная часть истории заключается в том, что даже с упомянутой ошибкой libc функция login_passwd не была бы уязвима таким образом, если бы не другой баг. В 2001 году login_passwd переписали для поддержки kerberos, и, возможно, именно тогда ввели то, что является настоящей причиной ошибки. Предложение аутентификации запросно-ответного типа (как в системе s/key) возвращает аутентифицированное состояние, а не молчание. Много лет спустя код kerberos удалили, но часть кода для его поддержки осталась, как и введённая ошибка.
Если бы код kerberos тщательно почистили, то баг с аутентификацией всё равно остался бы (там были и некоторые другие связанные проблемы с парсингом argv), но его влияние, безусловно, сильно бы уменьшилось.
Не так легко провести корректный парсинг argv в контексте безопасности. Многие логичные советы и подходы тут не работают. Я только отмечу, что эта уязвимость выскочила после очередного обсуждения проблемы с именами файлов в Unix/Linux/POSIX, хотя ведущий минус (дефис) на самом деле не относится к названию файла.
ld.so
Из кода ld.so не удалены плохие переменные окружения. Отчёт. Патч.
Что-то похожее на ошибку памяти. Но нет. Баг здесь заключается в привязке успеха одной операции — разбиения переменной окружения — к удалению этой переменной. Весьма самоуверенно.
Конечно, сторонники различных систем типизации будут уверять, что обработают эти операции в правильном порядке, но не факт, что это поможет. Код C не вылетал с ошибкой из-за отсутствия обработки ошибок или потому что ошибка распределения памяти осталась незаметной.
ftp
ftp следует редиректам на локальные файлы. Отчёт. Патч.
Баг NetBSD ftp с редиректами — это же давно типичный пример бесконтрольных функций. И вот опять та же ошибка (к счастью, с незначительными последствиями)! Ребята, ну сидите дома. Не добавляйте в свои программы дополнительные функции.
smtpd from
smtpd не может проверить некоторые адреса отправителей. Отчёт. Патч. Комментарий.
Думаю, что в комментарии Жиля всё сказано, но напомню предысторию. Давным-давно вся почта хранилась локально у каждого пользователя в /var/mail в файлах mbox. Это не очень здорово, потому что существует вероятность повреждения, если mua удалит электронное письмо, пока mda доставляет новое (не говоря уже о других проблемах типа искажения поля From). Поэтому файл mbox нужно заблокировать. Но блокировка в сетевой файловой системе работает ненадёжно. Так что вместо блокировки мы задействуем lock-файлы. Однако нужно прийти к согласию по определённому протоколу блокировки, ведь файлами mbox владеет пользователь, а самим каталогом — рут. Таким образом, чтобы фактически изменить файл mbox, нужно каждый раз запускать вспомогательную функцию setuid. Ну, вот уже первая проблема. Ещё один пережиток старых времён заключается в настройках mda, которую можно использовать не просто как программу, а как конвейер оболочки. Народ прописывает что-то вроде
spam-assassin | mail.mda
, а это вы уже не сможете просто передать execve()
. Здесь очень много сложностей, которые корнями уходят в прошлое. И, к сожалению, этот проблемный код нельзя просто заменить. Электронная почта используется давным-давно, и на её основе построены очень сложные системы и рабочие процессы, поэтому очень трудно просто вырезать один кусок кода — и вставить новый. Несмотря на различные уровни разделения привилегий, родительский процесс с правами рута по-прежнему во многом доверяет своим менее привилегированным дочерним процессам. Он выполнит команды и аргументы, которые получит.
Избежать этого кажется так же просто, как переключиться на формат хранения maildir, но это требует различных изменений во многих местах. Стандартный mua mail не понимает этот формат. Как по мне, так mbox давно отжил своё, но многих он по-прежнему устраивает, а процедура обновления, возможно, не полностью прозрачна и не пройдёт автоматически.
smtpd read
Чтение за пределами области в smtpd можно использовать для выполнения команд. Отчёт. Патч.
Здесь реально проблема безопасности памяти. Отправив несколько забавных строк состояния, удалённый smtp-сервер может внедрить в очередь smtpd команды для выполнения. Когда электронное письмо помещается в очередь для повторной доставки, smtpd добавляет в заголовок некоторую информацию о месте назначения, чтобы знать, какую команду следует выполнить (см. выше). Эта атака похожа на «контрабанду» http-запросов. Если вы можете сгенерировать «отлуп» с неожиданными командами в заголовке, то smtpd выполнит их при попытке повторной доставки.
Как и выше, одна из проблем заключается в том, что наиболее чувствительные части smtpd находятся слишком близко к поверхности атаки. Мне кажется, истинная проблема в том, что smtpd хранит собственные метаданные внутри данных электронной почты. Из-за этого становится возможной атака с рассинхронизацией парсинга. Если бы процитированный ответ с сервера хранился совершенно отдельно от файла с инструкциями по доставке, то чтение за пределами границ не нанесло бы особого вреда.
Эта уязвимость мне кажется показательной, потому что мы здесь видим опасность смешивания данных с разными уровнями доверия. Эту опасность никогда не обсуждали. А она точно есть.
Выводы
Конечно, вывод был понятен с самого начала, но мы всё равно об этом скажем.
Думаю, некоторые из этих ошибок помогают продемонстрировать, насколько жизненно важны для безопасности такие принципы, как удаление устаревших интерфейсов и снижение сложности кода. По большей части, провал произошёл из-за того, что этим принципам не следовали до конца, а не потому, что сами принципы испорчены или несостоятельны. Некоторые вещи ускользают из виду, но я не согласен, что разработчикам необходима какая-то сверхчеловеческая бдительность. Тут легко давать бесполезные советы, типа будь внимательнее, не делай ошибок и git gud [слэнговое геймерское выражение означает 'get good', то есть «поправляйся, выздоравливай» — прим. пер.]. Но я думаю, что у OpenBSD более серьёзные проблемы.
Даже OpenBSD может рискнуть безопасностью ради утилитарной практичности. Вот почему долгое время не вносятся изменения в некоторые устаревшие проекты. Так что, возможно, урок состоит в том, что эффективных принципов нужно придерживаться всегда, а не только тогда, когда это удобно. Хотя зачастую трудно сделать правильный выбор.
Три самые серьёзные уязвимости, auth и две smtpd, более или менее пригодны для эксплуатации только из-за архитектурных проблем, которые выходят за рамки оригинального бага. Они должны были остаться просто мелкими недочётами, которые демонстрируют, что в хорошо защищённой системе не обязательно нужно стремиться к идеальному коду, то есть мелкие ошибки допустимы — и они не повляют на безопасность. Увы, бывает трудно выявить недостатки дизайна в абстрактном виде. И все части системы по отдельности выглядят защищёнными, но если их соединить, то могут появиться слабые места.
Разделение привилегий — ключевой компонент безопасности OpenBSD, а в его основе лежит межпроцессная коммуникация. Есть смысл более пристально присмотреться к тому, какие проблемы могут возникнуть с повреждёнными процессами. Защищённые браузеры всё больше усиляют защиту и затрудняют атаки. В частности, smtpd должен быть защищён от повреждения памяти в сетевых задачах. Но вызывает тревогу та лёгкость, с которой он способен управлять родительским процессом.
Только одну ошибку можно было предотвратить, используя более безопасный язык. Да, наверное, существует какая-то программная идиома, которая в некоторых случаях способна помочь, если следовать ей с религиозным упорством. Но я пока не уверен, что каждый программист по умолчанию корректно закодирует все соответствующие инварианты.
Написание почтового сервера — сложное дело. Особенно если вас поджимают рамки легаси.