19 смертных грехов, угрожающих безопасности программ
Шрифт:
Подверженные греху языки
Еще одна универсальная беда. Допустить ошибки при доступе к данным и встроить секретные данные в код можно на любом языке.
Как происходит грехопадение
Наверное, вы уже поняли, что грех распадается на два основных компонента, назовем их «грешками». Грешок № 1 – это слабый механизм контроля доступа. Грешок № 2 – хранение секретных данных в коде программы. Рассмотрим их по очереди.
Слабый контроль доступа к секретным данным
При решении задачи организации контроля доступа нужно принимать во внимание значительные различия между платформами. В современных версиях операционной системы Windows имеются богатые, но сложные списки управления доступом (ACL). Причем у этой сложности есть две стороны. Если вы понимаете, как правильно пользоваться ACL, то сможете решить трудные проблемы, которые в более простых системах не решаются.
Хотя ACL традиционно доступны на многих UNIX–системах, совместимых со стандартом POSIX, но в основе системы управления доступом там лежит понятие о триаде «владелец–группа–мир». В отличие от маски управления доступом в Windows, которая содержит сложный набор прав, в UNIX используются только три бита (не считая некоторых нестандартных), представляющих права на чтение, на запись и на исполнение. В силу простоты некоторые задачи решить трудно, а сведение сложных проблем к простым решениям может стать причиной ошибок. Преимущество в том, что чем система проще, тем легче реализовать защиту данных. Файловая система ext2, применяемая в Linux, поддерживает несколько дополнительных атрибутов защиты по сравнению со стандартными.
Еще одно отличие между системами на базе Windows и UNIX заключается в том, что в UNIX–системах все объекты рассматриваются как файлы: сокеты, устройства и т. д. В Windows же есть очень много разных объектов, и для каждого имеются свои биты управления доступом. Мы не будем вдаваться в технические детали того, какие биты относятся к мьютексам, событиям, потокам, процессам, маркерам процессов, службам, драйверам, участкам отображаемой памяти, ключам реестра, файлам, протоколам событий и каталогам. Как всегда, желая создать объект со специализированными правами, читайте документацию. Но есть и хорошая новость: в большинстве случаев права, которыми операционная система наделяет объекты, – это как раз то, что вам нужно.
ACL и ограничение прав
Система на базе парадигмы «владелец–группа–мир» при решении вопроса о том, разрешить ли доступ к файлу, в первую очередь анализирует действующий идентификатор пользователя (effective user id – EUID) того процесса, который создал файл. Права, предоставляемые группе, зависят от того, использует ли ОС действующий идентификатор группы процесса или идентификатор группы того каталога, в котором создан файл. Наконец, если создатель файла или привилегированный пользователь разрешит, то к файлу может иметь доступ кто угодно (мир). Когда процесс пытается открыть файл, сначала проверяется, является ли запустивший его пользователь владельцем этого файла, затем – принадлежит ли он группе, связанной с этим файлом, и в последнюю очередь – предоставлен ли доступ всему миру. Очевидное следствие такого подхода заключается в том, что пользователи должны быть правильно распределены по группам, а если системный администратор управляет группами некорректно, то многим файлам могут быть назначены избыточные права, разрешающие доступ кому угодно.
Хотя в Windows существует несколько типов записей управления доступом (access control entries – АСЕ), все они обладают тремя общими характеристиками:
□ идентификатор пользователя или группы;
□ маска управления доступом, описывающая, что именно контролирует данная запись (чтение, запись и т. д.);
□ бит, определяющий, разрешает данная запись доступ или запрещает его.
Парадигму «владелец–группа–мир» можно считать частным случаем ACL с тремя разными записями и ограниченным набором управляющих битов. При проверке ACL система смотрит, соответствует ли хранящийся в нем идентификатор пользователя или группы идентификатору пользователя или группы, записанному в маркере процесса. Затем применяется запись управления доступом, и запрошенный вид доступа сравнивается с тем, что хранится в этой записи. Если соответствие есть и запись разрешает доступ, то проверяется, соответствуют ли флаги запрошенного доступа флагам разрешенного доступа. Если да, то проверка пройдена. Если в исследуемой записи подняты не все необходимые биты, то система переходит к следующей записи и так до тех пор, пока не будут подтверждены все права или АСЕ не закончатся. Если система встретит АСЕ, запрещающую доступ, которая соответствует запрошенному доступу и указанному в маркере пользователю или группе (все равно, активированным или нет), то она отказывает в доступе и других АСЕ не просматривает. Как видите, порядок АСЕ важен, поэтому лучше пользоваться таким API, который возвращает АСЕ в правильном порядке.
Еще один аспект ACL, порождающий дополнительные сложности, – это наследование. В UNIX файлы иногда наследуют группу от объемлющего контейнера, а в Windows любой объект, который может содержать другие объекты, – каталог, ключ реестра, объект Active Directory и некоторые другие, – скорее всего, наследует часть записей АСЕ от родительского объекта. Не всегда безопасно
предполагать, что унаследованные записи управления доступом подходят для вашего объекта.Греховность элементов управления доступом
Поскольку неправильные элементы управления доступом не связаны с каким–то конкретным языком, мы сразу перейдем к вопросу о том, каковы возможные признаки проблемы. Но, учитывая тот факт, что для подробного описания механизмов, применяемых для корректного управления доступом, нужно было бы написать отдельную книгу, мы ограничимся лишь высокоуровневым обзором.
Самая серьезная – и при этом самая распространенная – ошибка заключается в создании чего–то, что дает полный доступ кому угодно (в Windows это группа Everyone, в UNIX – «мир») . Чуть менее греховный вариант той же ошибки – это предоставление полного доступа непривилегированным пользователям или группам. Нет ничего хуже создания исполняемого файла, в который могут писать обычные пользователи, а уж если вы хотите внести полный хаос, тогда создайте исполняемый файл с правами на запись, который запускается от имени root или LocalSystem. Немало эксплойтов добились успеха, потому что кто–то создал suid–сценарий, работающий от имени root, и забыл закрыть доступ на запись в него группе и миру. В Windows того же эффекта можно добиться, установив сервис, работающий от имени высокопривилегированной учетной записи, и включить для ее исполняемого файла такую запись АСЕ:
Everyone(Write)
На первый взгляд, это кажется полной нелепостью, но антивирусные программы снова и снова впадают в такой грех, a Microsoft выпустила на эту тему специальный бюллетень, поскольку в 2000 году подобная ошибка была обнаружена в одной из версий Systems Management Server (MS00–012). В описании бюллетеня CVE–2000–0100 в разделе «Примеры из реальной жизни» есть дополнительная информация по этому поводу.
Исполняемый файл с разрешением на запись – это самый прямой путь облегчить жизнь противнику, но нанести немалый вред можно, разрешив записывать в файл с конфигурационной информацией. В частности, возможность изменить путь к программе или библиотеке – это по существу то же самое, что разрешить запись в исполняемый файл. Примером такой проблемы в Windows может служить сервис, позволяющий непривилегированным пользователям изменять конфигурационные данные. Опасность тут двойная, поскольку один и тот же бит управляет и заданием пути к исполняемому файлу, и учетной записью, от имени которой работает сервис. Поэтому можно вместо непривилегированного пользователя сделать владельцем сервиса учетную запись LocalSystem и выполнить произвольный код. Для пущей забавы можно разрешить изменять конфигурационные данные по сети; это очень удобно для системного администратора, но если ACL сконфигурован неправильно, то не менее полезно и для противника.
Даже если изменить путь к исполняемому файлу нельзя, возможность модифицировать конфигурационные данные открывает ворота для множества атак. Самая очевидная – заставить процесс сделать нечто такое, чего он делать не должен. Вторая атака основана на том, что многие приложения предполагают, что конфигурационные данные обычно изменяет только сам процесс, поэтому они корректны. Выполнять синтаксический разбор трудно, программисты ленивы, а в результате противнику всегда есть чем заняться. Если вы не уверены на все сто процентов, что модифицировать конфигурационные данные может только привилегированный пользователь, считайте их не заслуживающим доверия источником, создавайте надежный и строгий анализатор, тестируйте программу, подавая на вход случайные данные.
Еще одно проявление той же проблемы – разделение памяти несколькими приложениями. Разделяемая память – это высокопроизводительный, но и небезопасный вид межпроцессных коммуникаций. Если приложение не рассматривает разделяемую память как источник не заслуживающих доверия данных, то наличие сегментов, в которые можно писать, часто открывает дверь эксплойтам.
Следующий великий грех – разрешить непривилегированным пользователям читать информацию «для служебного пользования». В качестве примера можно назвать протокол SNMP (Simple Network Management Protocol – простой протокол управления сетью; впрочем, эту аббревиатуру еще расшифровывают и так: Security Not My Problem – безопасность не моя проблема) в ранних вариантах Windows 2000 и предыдущих версиях ОС. В протоколе используется разделяемый пароль, который называется сообществом (community string). Он определяет, какие параметры можно читать или модифицировать, и передается по сети по существу в открытом виде. В зависимости от установленных агентов можно прочитать или изменить массу интересной информации. Один забавный пример: можно отключить сетевой интерфейс или «интеллектуальный» источник бесперебойного питания. Но мало того что даже корректная реализация SMNP может стать источником бед, так еще многие производители, включая и Microsoft, допустили ошибку, сохраняя имена сообществ в ключах реестра, которые мог читать кто угодно. Локальный пользователь мог прочитать имя сообщества и начать администрировать не только свою систему, но и значительную часть сети в целом.